多线程同步(互斥量、条件变量)

多线程同步(互斥量、条件变量)

互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁错字,在访问完成后释放互斥量上的锁。确保同一个时间只有一个线程访问数据。
      对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。

相关函数:  
pthread_mutex_init() 初始化互斥锁   
pthread_mutex_destroy() 删除互斥锁   
pthread_mutex_lock():占有互斥锁(阻塞操作)   
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。当互斥锁空闲时将占有该锁;否则立即返回  
pthread_mutex_unlock(): 释放互斥锁 

避免死锁:
产生死锁的情况:如果线程试图对同一个互斥变量加锁两次,那么它自身就会陷入死锁状态,使用互斥量时,还有其他更不明显的方式也能产生死锁。例如,程序中使用多个互斥量时,如果允许一个线程一直占有第一个互斥量,并且在试图锁住第二个互斥量时处于阻塞状态,但是第二个互斥量的线程也在试图锁住第一个互斥量,这时就会发生死锁。

解决办法:
      可以通过小心地控制互斥量加锁的顺序来避免死锁的发生。例如,假设需要对两个互斥量A和B同时加锁,如果所有线程总是对互斥量B加锁之前锁住互斥量A,那么使用两个互斥量就不会产生死锁。
      使用pthread_mutex_trylock接口避免死锁,如果已经占有某些锁而且pthread_mutex_trylock接口返回成功,那么久可以前进,但是,如果不能获取锁,可以先释放已经占有的锁,做好清理工作,然后过一段时间重新尝试。

条件变量

     互斥量读写锁解决了多线程访问共享变量产生的竞争问题,那么条件变量的作用何在呢??
    条件变量的作用是用于多线程之间关于共享数据状态变化的通信当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量

条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知
  互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定
        而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。

  使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。

  一般说来,条件变量被用来进行线程的同步。
    举例:假设没有条件变量,对于一个生产者消费者问题,消费线程在得知队列中没有产品时,将阻塞自己。生产线程给队列中放入产品,但是没有办法**消费线程,而消费线程处于阻塞状态也没有办法自**。如果消费线程使用忙等的方式,通过不断地查询来判断是否有产品将大量的浪费CPU时间。消费线程可以使用睡眠+查询的方式,即发现队列中没有产品时,sleep一段时间,然后再查询。问题是睡眠多长时间?时间太长,实时性不好,时间太短,还是浪费CPU时间。
        条件变量是与互斥量相关联的一种用于多线程之间关于共享数据状态改变的通信机制。它将解锁和挂起封装成为原子操作。等待一个条件变量时,会解开与该条件变量相关的锁,因此,使用条件变量等待的前提之一就是保证互斥量加锁。线程醒来之后,该互斥量会被自动加锁,所以,在完成相关操作之后需要解锁。

条件变量的结构为pthread_cond_t
初始化条件变量pthread_cond_init

  1. #include <pthread.h>
  2. int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
返回值:函数成功返回0;任何其他返回值都表示错误
可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

  1. pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

阻塞在条件变量上pthread_cond_wait

  1. #include <pthread.h>
  2. int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
  3. 返回值:函数成功返回0;任何其他返回值都表示错误
了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。 

首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能: 

锁定互斥对象时,线程将调用 pthread_cond_wait(&counter_nonzero,&counter_lock)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。 

pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁于是其它线程可以修改已链接列表),并等待条件 counter_nonzero 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。 【要求解锁并阻塞是一个原子操作

此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。 
现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&counter_lock) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 counter_lock。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。 

释放阻塞的所有线程pthread_cond_broadcast

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

解除在条件变量上的阻塞pthread_cond_signal

  1. #include <pthread.h>
  2. int pthread_cond_signal(pthread_cond_t *cv);
  3. 返回值:函数成功返回0;任何其他返回值都表示错误
它对 counter_lock解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号),它就会苏醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事:重新锁定 counter_lock。
 
阻塞直到指定时间pthread_cond_timedwait

#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。

 释放条件变量pthread_cond_destroy

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。
注意:条件变量占用的空间并未被释放

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. pthread_mutex_t counter_lock;
  5. pthread_cond_t counter_nonzero;
  6. int counter = 0;
  7. int estatus = -1;
  8. void *decrement_counter(void *argv);
  9. void *increment_counter(void *argv);
  10. int main(int argc, char **argv)
  11. {
  12. printf("counter: %d\n", counter);
  13. pthread_t thd1, thd2;
  14. int ret;
  15. ret = pthread_create(&thd1, NULL, decrement_counter, NULL);
  16. if(ret){
  17. perror("del:\n");
  18. return 1;
  19. }
  20. ret = pthread_create(&thd2, NULL, increment_counter, NULL);
  21. if(ret){
  22. perror("inc: \n");
  23. return 1;
  24. }
  25. int counter = 0;
  26. while(counter != 10){
  27. printf("counter(main): %d\n", counter);
  28. sleep(1);
  29. counter++;
  30. }
  31. return 0;
  32. }
  33. void *decrement_counter(void *argv)
  34. {
  35. printf("counter(decrement): %d\n", counter);
  36. pthread_mutex_lock(&counter_lock);
  37. printf("mutex: counter(decrement): %d\n", counter);
  38. while(counter == 0)
  39. pthread_cond_wait(&counter_nonzero, &counter_lock); //进入阻塞(wait),进入wait后会对counter_lock解锁,等待**(signal)---等待操作2进行**,**后该函数返回前最后一个动作是重新锁住counter_lock
  40. printf("decrement:counter--(before): %d\n", counter);
  41. counter--; //等待signal**后再执行
  42. printf("decrement:counter--(after): %d\n", counter);
  43. pthread_mutex_unlock(&counter_lock);
  44. return &estatus;
  45. }
  46. void *increment_counter(void *argv)
  47. {
  48. sleep(2);
  49. printf("counter(increment): %d\n", counter);
  50. pthread_mutex_lock(&counter_lock);
  51. printf("mutex: counter(increment): %d\n", counter);
  52. if(counter == 0)
  53. pthread_cond_signal(&counter_nonzero); //**(signal)阻塞(wait)的线程(先执行完signal线程,然后再执行wait线程)---操作2
  54. printf("increment:counter++(before): %d\n", counter);
  55. counter++;
  56. printf("increment:counter++(after): %d\n", counter);
  57. pthread_mutex_unlock(&counter_lock);
  58. return &estatus;
  59. }
执行结果:

多线程同步(互斥量、条件变量)
多线程同步(互斥量、条件变量)