多线程编程总结(二)——条件变量和互斥锁

2019-07-14 11:38发布


                                             互斥锁

1.什么叫互斥锁?

       互斥锁(也成互斥量)可以用于保护关键代码段,以确保其独占式的访问,类似于二元信号量。二者都可以称为挂起等待锁———锁资源得不到满足,就会被挂起,在信号量或互斥锁上等待。 注:当前线程的PCB在互斥量的等待队列等待以便快速唤醒。进程等待的本质是将PCB列入某个队列等待。

2.使用方式

         当进入关键代码段时,需要申请互斥锁,如果失败就挂起等待,将PCB列入互斥锁的等待队列,成功就将其加锁,等价于二元信号量的P操作;离开关键代码时,对互斥锁进行解锁,以便唤醒其他等待该互斥锁的线程,这等价二元信号量的V操作。

3.互斥锁相关的五个函数


#include //初始化 int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t* mutexattr); //销毁 int pthread_mutex_destroy(pthread_mutex_t * mutex); //加锁阻塞版 int pthread_mutex_lock(pthread_mutex_t * mutex); //加锁非阻塞版 int pthread_mutex_trylock(pthread_mutex_t * mutex); //解锁 int pthread_mutex_unlock(pthread_mutex_t * mutex);         参数解析:
MU 1.mutex指向操作的目标锁,互斥锁的类型是pthread_mutex_t的结构体;        2. 初始化时mutexattr参数指定互斥锁的属性。一般设置为NULL取它的默认属性。   
3.如果mutex变量是静态分配的(全局变量或static变量),则可以使用宏定义PTHREAD_MUTEX_INITIALIZER初始化一个互斥锁。宏定义实际上是把互斥锁的每个字段都初始化为0。
4.销毁互斥锁是为了释放其占用的内核资源,不能销毁一个已经加锁的互斥锁。


5. pthread_mutex_lock函数以原子操作给一个互斥锁加锁。一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用 pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。     6.如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
7.上面的函数成功时返回0,失败时返回错误码。

4.互斥锁产生的问题——死锁

死锁:集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。
线程死锁情形


死锁产生的四个必要条件(缺一不可):
(1)非抢占。不能强行抢占进程中已有的资源。
(2)互斥。一次只能有一个进程使用资源。其他进程不能访问已经分配给其他进程的资源。
(3)占有且等待。当一个进程在等待分配其他资源时,其继续占有已分配得到的资源
(4)循环等待。存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需的资源。
 解决死锁的方法: (1)预防死锁。即破坏死锁产生的四个条件。
(2)避免死锁。保证按相同顺序获得锁。(如果一个进程的请求会导致死锁,则不启动该进程;如果一个进程增加资源的请求会导致死锁,则不允许分配)
                                           条件变量
1.什么叫条件变量? 互斥锁用于同步线程对共享数据的访问的话,那么条件变量则用于在线程之间同步共享数据的值。
      条件变量提供一种机制:当某个数据达到某个值的时候,唤醒等待该共享数据的资源。这样就能保证顺序访问
  一般与互斥锁一起使用:
(1)因为互斥只能保持访问不出错。(但是由于优先级或其他特性可能导致其他线程得不到资源)
(2)同步保证访问不出错的同时,一般还让多个线程或执行流协同(访问临界资源具有顺序性)


2.与条件变量相关的五个函数


#include int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr); int pthread_cond_destroy(pthread_cond_t* cond); int pthread_cond_broadcast(pthread_cond_t* cond); int pthread_cond_signal(pthread_cond_t* cond); int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);

(1)第一个参数与互斥锁类似,cond指向要操作的目标条件变量,条件变量的类型是pthread_cond_t结构体

(2)cond_attr参数指定条件变量的属性如果设置为NULL采用默认属性。和互斥锁类似,也可以采用           PTHREAD_COND_INITIALIZER初始化静态分配的cond变量
(3)一个Condition Variable(条件变量)总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在这             个条件变量上阻塞等待,这个函数做以下操作                     a: 释放Mutex          b: 阻塞等待   c: 当被唤醒时,重新获得Mutex并返回

(4)以上函数成功返回0,失败返回错误码


3.用条件变量和互斥锁完成同步与互斥机制

模型:        生产者生产一个结构体串在链表的表头上,消费者 从表头取走结构体。

#include #include #include #include #include typedef int DataType; typedef struct listNode { struct listNode* _next; DataType _data; }node, *pnode, *plist; pnode listHead = NULL; //定义成全局,初始化简单 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t need_product = PTHREAD_COND_INITIALIZER; pnode CreateNode(DataType data) { pnode newnode = (pnode)malloc(sizeof(node)); newnode->_data = data; newnode->_next = NULL; return newnode; } void Init(plist *list) { if (NULL != list) { *list = NULL; } } void PushFront(plist *list, DataType d) { assert(list != NULL); pnode newnode = CreateNode(d); pnode cur = *list; if (cur == NULL) { *list = newnode; newnode->_next = NULL; } else { *list = newnode; newnode->_next = cur; } return; } void PopFront(plist *list) { assert(list != NULL); pnode cur = *list; if (cur == NULL) { return; } else if (cur->_next == NULL) { cur = cur->_next; free(cur); *list = NULL; } else { *list = cur->_next; free(cur); cur = NULL; } return; } void Destroy(plist *list) { pnode cur = *list; while (cur != NULL) { *list = cur->_next; cur = *list; } return; } void ShowList(plist list) { pnode cur = list; while (cur != NULL) { printf("%d ", cur->_data); cur = cur->_next; } printf(" "); } void *product(void * _val) { while (1) { sleep(1); //加锁 pthread_mutex_lock(&lock); int num = rand() % 100; Init(&listHead); PushFront(&listHead, num); printf("call consum:product success and the value is %d ", num); //解锁 pthread_mutex_unlock(&lock); //唤醒等待目标变量的线程 pthread_cond_signal(&need_product); } } void *consum(void *_val) { while (1) { pthread_mutex_lock(&lock); while (listHead == NULL) { //等待目标条件变量,锁使得pthread_cond_wait操作的原子性 pthread_cond_wait(&need_product, &lock); } printf("call product:consum success and the value is %d ", listHead->_data); PopFront(&listHead); ShowList(listHead); pthread_mutex_unlock(&lock); } return NULL; } int main() { pthread_t t_product; pthread_t t_consum; pthread_create(&t_product, NULL, product, NULL); pthread_create(&t_consum, NULL, consum, NULL); //回收线程 pthread_join(t_product, NULL); pthread_join(t_consum, NULL); return 0; }


运行结果:

实验结果分析:   如果不采用互斥锁使得操作的原子性,有可能造成数据的二义性;不使用条件变量使得操作按照一定顺序执行,可能导致生产者一直生产或者消费者一直消费,可能导致程序效率低下等问题。