线程同步与互斥(一)

来源:互联网 发布:免费服务器防火墙软件 编辑:程序博客网 时间:2024/05/22 08:14

mutex (互斥量) (互斥锁)
多个线程同时访问共享数据时可能会冲突,这跟前面讲信号时所说的可重入性是同样的问 题。
比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:
(1). 从内存读变量值到寄存器
(2). 寄存器的值加1
(3)将寄存器的值写回内存
假设四个线程在多处理器平台上同时执行这三条指令,则可能导致下图所示的结果,最后变量只加了一次而非四次。
代码如下:

#include <stdio.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t mutex;static int g_val = 0;void *print_msg(void *arg){     int count = 500;    while(count)    {         int val = 0;        val = g_val;        printf("output:%d\n", g_val);        count--;        g_val = val+1;    }        return NULL;}int main(){     pthread_t id1;    pthread_t id2;    pthread_t id3;    pthread_t id4;    pthread_mutex_init(&mutex, NULL);    pthread_create(&id1, NULL, print_msg, NULL);    pthread_create(&id2, NULL, print_msg, NULL);    pthread_create(&id3, NULL, print_msg, NULL);    pthread_create(&id4, NULL, print_msg, NULL);    pthread_join(id1, NULL);    pthread_join(id2, NULL);    pthread_join(id3, NULL);    pthread_join(id4, NULL);    pthread_mutex_destroy(&mutex);    return 0;}

运行结果图:
与我们期望的值不符合

对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引⼊入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么 都执行,要么都不执行, 不会执行到中间被打断,也不会在其它处理器上并行做这个操作。 Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:

这里写图片描述

返回值:成功返回0,失败返回错误号。
这里写图片描述
pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果attr为NULL则表示 缺省属性,
用 pthread_mutex_init函数初始化的Mutex
如果Mutex变量 是静态分配的(全局变量 或static变量),也可以⽤用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。
这里写图片描述

Mutex的加锁和解锁
操作可以⽤用下列函数:
这里写图片描述

返回值:成功返回0,失败返回错误号。
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用 pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调⽤用 pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已 经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
好,解决上⾯面的问题!
代码如下:
这里写图片描述
这里写图片描述
源代码如下:

#include <stdio.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t mutex;static int g_val = 0;void *print_msg(void *arg){     pthread_mutex_lock(&mutex);  //加锁    int count = 500;    while(count)    {         int val = 0;        val = g_val;        printf("output:%d\n", g_val);        count--;        g_val = val+1;    }    pthread_mutex_unlock(&mutex);  //解锁        return NULL;}int main(){     pthread_t id1;    pthread_t id2;    pthread_t id3;    pthread_t id4;    pthread_mutex_init(&mutex, NULL);    pthread_create(&id1, NULL, print_msg, NULL);    pthread_create(&id2, NULL, print_msg, NULL);    pthread_create(&id3, NULL, print_msg, NULL);    pthread_create(&id4, NULL, print_msg, NULL);    pthread_join(id1, NULL);    pthread_join(id2, NULL);    pthread_join(id3, NULL);    pthread_join(id4, NULL);    pthread_mutex_destroy(&mutex);    return 0;}

运行结果:
这里写图片描述

看到这里,读者一定会好奇:Mutex的两个基本操作lock和unlock是如何实现的呢?
假设Mutex变量的值为1表示互斥锁空闲,这时某个进程调用lock可以获得锁,而Mutex的值为0表示互斥锁已经被 某个线程获得,其它线程再调用lock只能挂起等待。
那么lock和unlock的伪代码如下:

lockif(mutex>0)    {        mutex = 0;        return 0;    }   //挂起等待    else    {         //挂起等待    }unlock:    mutex = 1;    唤醒等待mutex   的线程    return 0

unlock操作中唤醒等待线程的步骤可以有不同的实现,可以只唤醒一个等待线程,也可以唤醒 所有等待该Mutex的线程,然后让被唤醒的这些线程去竞争获得这个Mutex,竞争失败的线程继续挂起等待。

细心的读者应该已经看出问题了:对Mutex变量的读取、判断和修改并不是原子操作。如果两个线程同时调用lock,这时Mutex是1,两个线程都判断mutex>0成立,然后其中一个线程置 mutex=0,⽽而 另⼀一个线程并不知道这一情况,也置mutex=0,于是两个线程都以为自己获得了锁。

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

“挂起等待”和“唤醒等待线程”的操作如何实现?

每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一 项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。 一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁, 因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。

另一种典型的死锁情形是这样:线 程A获得了锁1, 线程B获得了锁2, 这时线程A调用lock试图获得锁2, 结果是需要挂起等待线程 B释放锁2, 而这时线程B也调用lock试图获得锁1, 结果是需要挂起等待线程A释放锁1, 于是线 程A和B都永远处于挂起状态了。
写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:如果所有线程 在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。⽐比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3, 那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。

如果要为所有的锁确定一个先后顺序⽐比较困难,则应该尽量使⽤用pthread_mutex_trylock调⽤用代替
pthread_mutex_lock 调 ⽤用,以免死锁。

0 0