简 述: 在 Linux 中,使用互斥量(互斥锁🔐) Mutex 来给保证多线程 ,在访问公共变量的时候能够 “串行” 代码。从而使得多线程正确的同步执行。关于多线程创建和使用可以参考前面几篇的文章,争取早日把 Linux 系统篇之 系统编程给发布完系列的教程。

PS:好几天没有接着学习 Linux 的系统函数和理论知识了。可能是前面几天有点忙了吧;时间流逝啊,总是这么得快,,,,

关于互斥锁的使用如下:

  • pthread_mutex_t *mutex; //创建一个锁
  • pthread_mutex_init(); //初始化一个互斥锁
  • pthread_mutex_lock(); //上锁🔓,或者使用 pthread_mutex_trylock()
  • pthread_mutex_unlock(); //解锁🔐
  • pthread_mutex_destroy; //销毁互斥锁

[TOC]


编程环境:

  💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

  💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


背景铺垫:

  • 程序:

    先写一个多线程程序,A 和 B 两个子线程一起数出输出到 100000;然后在没有锁🔐的情况下,每一线程轮流执行 10 毫秒,模拟时间片轮转切片。

  • 代码程序:

    这个代码还没有加锁,保护共享变量。完整代码如下;

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    int g_num = 0; //在全局区域,共享
    #define MAXNUMBER 100000
    
    void* funA(void* arg);
    void* funB(void* arg);
    
    int main(int argc, char *argv[])
    {
        pthread_t pthreadA = 0;
        pthread_t pthreadB = 0;
        pthread_create(&pthreadA, nullptr, funA, nullptr);  //创建两个子线程
        pthread_create(&pthreadB, nullptr, funB, nullptr);
    
        pthread_join(pthreadA, nullptr);  //阻塞,回收资源
        pthread_join(pthreadB, nullptr);
      
        return 0;
    }
    
    void* funA(void* arg)
    {
        for (int i = 0; g_num < MAXNUMBER; i++) {
            int a = g_num;  //27-29行,增加寄存器和内存之间的数据交换操作,使得出现内存没来得及保存数据的现象的概率大一点
            a++;
            g_num = a;
            printf("A thread id: %ld,   num = %d\n", pthread_self(), g_num);
    
            usleep(10); //沉睡 10 毫秒, 模拟时间片轮转,效果更明显
        }
    
        return nullptr;
    }
    
    void* funB(void* arg)
    {
        for (int i = 0; g_num < MAXNUMBER; i++) {
            int b = g_num;
            b++;
            g_num = b;
            printf("B thread id: %ld,   num = %d\n", pthread_self(), g_num);
    
            usleep(10); //沉睡 10 毫秒, 模拟时间片轮转,效果更明显
        }
    
        return nullptr;
    }
  • 结果分析:

    下面截图里面,在 MacOS 10.14.6 和 Ubuntu 20.04 里面跑,都是正确的结果;

    [疑惑]可能是因为系统系统内核或者底层作了修改???又或者是数数的间隔过小,数的数字不多? 预判的结果是 实际数数的大小会比实际正确结果要小一点(原因是时间片轮转的原因),待日后挖坟考古?难道是系统比较新?所以连续出现几次都是最正确的结果。。。

[解答]没有出现内存来不及保存增加的数据,cpu 的时间片就被其他资源占去了,导致最后出现的小数有可能出现在大数的后面打印,这是一个概率问题;而 27-29 行和 42-44 行,是刻意增加寄存器和内存之间的数据交换操作次数,使得出现内存没来得及保存数据的现象的概率大一点。

  • 如果在 Ubuntu 16.04 里面跑改程序,如果出现了最后一个数字比 100000 小一点,那么解释的可能如下:

    其中会发现结果有点混乱。这是两个子线程互相操作同一个数据,时间片切换的时候,还新的数据还没有来得及保存进内存里面,就切换到了下一个线程中去了。

    • [实际结果]多数了一位,最后一个结果是100001,且有时候还会下面还会再打印一个小的数字
    • [期望结果]只数数到 100000;
    • 分析图如下:

使用互斥量(锁) Mutex:

如果我们想使用互斥锁同步线程,那么所有线程,只要是有访问同一个共享公共变量的线程都要加锁。

  • 使用流程步骤:
  1. 创建一把互斥锁

    pthread_mutex_t *mutex
  1. 对互斥锁进程初始化 init

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  1. 使用互斥锁:每个子线程要访问公共变量的时候,都加上锁,使用完后就解锁

    //若果没有上锁,就上锁;若果已经上锁,直接返回,不阻塞
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    //若果没有上锁,就上锁;若果已经上锁,就阻塞该线程
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    //解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
  1. 使用完毕,销毁互斥锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

改写例子,使用互斥量(锁)实例:

将上面的例子改了改,然后在 A、B 两个子线程里面,使用互斥量(锁),会发现也会得到取其正确的结果。结果符合预期。

  • 修改后的加锁例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int g_num = 0; //在全局区域,共享
#define MAXNUMBER 100000
pthread_mutex_t g_mutex; //创建全局的互斥锁

void* funA(void* arg);
void* funB(void* arg);

int main(int argc, char *argv[])
{
    pthread_mutex_init(&g_mutex, nullptr); //对锁进行初始化

    pthread_t pthreadA = 0;
    pthread_t pthreadB = 0;
    pthread_create(&pthreadA, nullptr, funA, nullptr);  //创建两个子线程
    pthread_create(&pthreadB, nullptr, funB, nullptr);

    pthread_join(pthreadA, nullptr);  //阻塞,回收资源
    pthread_join(pthreadB, nullptr);

    pthread_mutex_destroy(&g_mutex);  //释放互斥锁资源
    return 0;
}

void* funA(void* arg)
{
    for (int i = 0; g_num < MAXNUMBER; i++) {
        pthread_mutex_lock(&g_mutex);
        int a = g_num;
        a++;
        g_num = a;
        printf("A thread id: %ld,   num = %d\n", pthread_self(), g_num);
        pthread_mutex_unlock(&g_mutex);

        usleep(10); //沉睡 10 毫秒, 模拟时间片轮转,效果更明显
    }

    return nullptr;
}

void* funB(void* arg)
{
    for (int i = 0; g_num < MAXNUMBER; i++) {
        pthread_mutex_lock(&g_mutex);
        int b = g_num;
        b++;
        g_num = b;
        printf("B thread id: %ld,   num = %d\n", pthread_self(), g_num);
        pthread_mutex_unlock(&g_mutex);

        usleep(10); //沉睡 10 毫秒, 模拟时间片轮转,效果更明显
    }

    return nullptr;
}
  • 运行结果:

    实际结果和预期结果一致。


下载地址:

18_mutex

欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。