【Linux C 多线程编程】互斥锁与条件变量

一、互斥锁

 

互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。

  1) 初始化:

  在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:

  对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.

  对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.

  原型:

  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);

  int pthread_mutex_destroy(pthread_mutex_t *mutex);

  头文件:#include<pthread.h>

  返回值: 成功则返回0, 出错则返回错误编号.

  说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解。

  2) 互斥操作:

  对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁。

  首先说一下加锁函数:

  头文件:#include<pthread.h>

  原型:

  int pthread_mutex_lock(pthread_mutex_t *mutex);

  int pthread_mutex_trylock(pthread_mutex_t *mutex);

  返回值: 成功则返回0, 出错则返回错误编号.

  说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

  再说一下解所函数:

  头文件:

  原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);

  返回值: 成功则返回0, 出错则返回错误编号.

  3) 死锁:

  死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。

  总体来讲, 有几个不成文的基本原则:

  对共享资源操作前一定要获得锁。

  完成操作以后一定要释放锁。

  尽量短时间地占用锁。

  如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

  线程错误返回时应该释放它所获得的锁。

  互斥锁最简单的使用是这样的:

1 pthread_mutex_t mutex;                   //定义锁
2 pthread_mutex_init(&mutex, NULL);   //默认属性初始化锁
3 pthread_mutex_lock(&mutex);           //申请锁
4 ...
5 pthread_mutex_unlock(&mutex);       //释放锁

  pthread_mutex_lock阻塞线程直到pthread_mutex_unlock被调用。
  还有另外两个函数可以用:int pthread_mutex_trylock (pthread_mutex_t *__mutex), 该函数立即返回,根据返回状态判断加锁是否成功。int pthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec *__restrict),该函数超时返回。
  互斥锁主要用来互斥访问临界区。用于线程的互斥。

 

设置锁的属性
函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。
前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。用法如下:

pthread_mutexattr_t mutexAttr;
pthread_mutexattr_init(&mutexAttr);
pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_PRIVATE);
pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_DEFAULT);
pthread_mutex_init(&mutex, &mutexAttr);

pthread_mutexattr_setpshared设置互斥锁范围

用法

1 int    pthread_mutexattr_setpshared(pthread_mutexattr_t* mattr,int pshared);

pthread_mutexattr_setpshared() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

pthread_mutexattr_getpshared(&mattr, &pshared)获取互斥锁范围

1 int    pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr,int *pshared)

此函数可为属性对象 mattr 获取 pshared 的当前值。该值为 PTHREAD_PROCESS_SHARED 或 PTHREAD_PROCESS_PRIVATE

  PTHREAD_PROCESS_PRIVATE

  如果互斥锁的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。

  PTHREAD_PROCESS_SHARED

  互斥锁变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享互斥锁,可以在共享内存中创建互斥锁,并将 pshared 属性设置为 PTHREAD_PROCESS_SHARED

 

pthread_mutexattr_settype设置互斥锁类型的属性

用法

1 int pthread_mutexattr_settype(pthread_mutexattr_t  *attr , int type);

类型属性的缺省值为 PTHREAD_MUTEX_DEFAULT

如果运行成功,pthread_mutexattr_settype 函数会返回零。否则,将返回用于指明错误的错误号。

 pthread_mutexattr_gettype获取互斥锁类型的属性

用法

1 int pthread_mutexattr_gettype(pthread_mutexattr_t  *attr , int  *type);

pthread_mutexattr_getpshared() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

  PTHREAD_MUTEX_NORMAL

  此类型的互斥锁不会检测死锁。如果线程在不首先解除互斥锁的情况下尝试重新锁定该互斥锁,则会产生死锁。尝试解除由其他线程锁定的互斥锁会产生不确定的行为。如果尝试解除锁定的互斥锁未锁定,则会产生不确定的行为。

  PTHREAD_MUTEX_ERRORCHECK

  此类型的互斥锁可提供错误检查。如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则会返回错误。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。

  PTHREAD_MUTEX_RECURSIVE

  如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则可成功锁定该互斥锁。 与 PTHREAD_MUTEX_NORMAL 类型的互斥锁不同,对此类型互斥锁进行重新锁定时不会产生死锁情况。多次锁定互斥锁需要进行相同次数的解除锁定才可以释放该锁,然后其他线程才能获取该互斥锁。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。 如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。

  PTHREAD_MUTEX_DEFAULT

  如果尝试以递归方式锁定此类型的互斥锁,则会产生不确定的行为。对于不是由调用线程锁定的此类型互斥锁,如果尝试对它解除锁定,则会产生不确定的行为。对于尚未锁定的此类型互斥锁,如果尝试对它解除锁定,也会产生不确定的行为。允许在实现中将该互斥锁映射到其他互斥锁类型之一。对于 Solaris 线程,PTHREAD_PROCESS_DEFAULT 会映射到 PTHREAD_PROCESS_NORMAL

 

 pthread_mutexattr_setprotocol设置互斥锁属性的协议

1 int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

如果成功完成,pthread_mutexattr_setprotocol() 会返回 0。其他任何返回值都表示出现了错误。

pthread_mutexattr_getprotocol设置互斥锁属性的协议

1 int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);

如果成功完成,pthread_mutexattr_getprotocol() 会返回 0。其他任何返回值都表示出现了错误。

  • PTHREAD_PRIO_NONE

    线程的优先级和调度不会受到互斥锁拥有权的影响。

  • PTHREAD_PRIO_INHERIT

    此协议值(如 thrd1)会影响线程的优先级和调度。如果更高优先级的线程因 thrd1 所拥有的一个或多个互斥锁而被阻塞,而这些互斥锁是用 PTHREAD_PRIO_INHERIT 初始化的,则 thrd1 将以高于它的优先级或者所有正在等待这些互斥锁(这些互斥锁是 thrd1 指所拥有的互斥锁)的线程的最高优先级运行。

    如果 thrd1 因另一个线程 (thrd3) 拥有的互斥锁而被阻塞,则相同的优先级继承效应会以递归方式传播给 thrd3

    使用 PTHREAD_PRIO_INHERIT 可以避免优先级倒置。低优先级的线程持有较高优先级线程所需的锁时,便会发生优先级倒置。只有在较低优先级的线程释放该锁之后,较高优先级的线程才能继续使用该锁。设置 PTHREAD_PRIO_INHERIT,以便按与预期的优先级相反的优先级处理每个线程。

    如果为使用协议属性值 PTHREAD_PRIO_INHERIT 初始化的互斥锁定义了 _POSIX_THREAD_PRIO_INHERIT,则互斥锁的属主失败时会执行以下操作。属主失败时的行为取决于 pthread_mutexattr_setrobust_np() 的 robustness 参数的值。

    • 解除锁定互斥锁。

    • 互斥锁的下一个属主将获取该互斥锁,并返回错误 EOWNERDEAD

    • 互斥锁的下一个属主会尝试使该互斥锁所保护的状态一致。如果上一个属主失败,则状态可能会不一致。如果属主成功使状态保持一致,则可针对该互斥锁调用 pthread_mutex_init() 并解除锁定该互斥锁。


      注 –

      如果针对以前初始化的但尚未销毁的互斥锁调用 pthread_mutex_init(),则该互斥锁不会重新初始化。


    • 如果属主无法使状态保持一致,请勿调用 pthread_mutex_init(),而是解除锁定该互斥锁。在这种情况下,所有等待的线程都将被唤醒。以后对 pthread_mutex_lock() 的所有调用将无法获取互斥锁,并将返回错误代码 ENOTRECOVERABLE。现在,通过调用pthread_mutex_destroy() 来取消初始化该互斥锁,即可使其状态保持一致。调用 pthread_mutex_init() 可重新初始化互斥锁。

    • 如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主将获取该锁及错误代码 EOWNERDEAD

  • PTHREAD_PRIO_PROTECT

    当线程拥有一个或多个使用 PTHREAD_PRIO_PROTECT 初始化的互斥锁时,此协议值会影响其他线程(如 thrd2)的优先级和调度。thrd2 以其较高的优先级或者以 thrd2 拥有的所有互斥锁的最高优先级上限运行。基于被 thrd2 拥有的任一互斥锁阻塞的较高优先级线程对于 thrd2 的调度没有任何影响。

  如果某个线程调用 sched_setparam() 来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。

  • 线程拥有使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁

  • 线程解除锁定使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁

  一个线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。

 

pthread_mutexattr_setprioceiling设置互斥锁的优先级上限

1 int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr,int prioceiling, int oldceiling);

attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。

prioceiling 指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于 SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。

oldceiling 包含以前的优先级上限值。

pthread_mutexattr_getprioceiling获取互斥锁的优先级上限

1 int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex,int *prioceiling);

注 –

仅当定义了 _POSIX_THREAD_PRIO_PROTECT 符号时,attr 互斥锁属性对象才会包括优先级上限属性。

 

pthread_mutexattr_setrobust_np设置互斥锁的强健属性

1 int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr,int robustness);

注 –

仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_setrobust_np() 才适用。

attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。

robustness 定义在互斥锁的属主失败时的行为。pthread.h 中定义的 robustness 的值为 PTHREAD_MUTEX_ROBUST_NP 或PTHREAD_MUTEX_STALLED_NP。缺省值为 PTHREAD_MUTEX_STALLED_NP

  • PTHREAD_MUTEX_ROBUST_NP

    如果互斥锁的属主失败,则以后对 pthread_mutex_lock() 的所有调用将以不确定的方式被阻塞。

  • PTHREAD_MUTEX_STALLED_NP

    互斥锁的属主失败时,将会解除锁定该互斥锁。互斥锁的下一个属主将获取该互斥锁,并返回错误 EOWNWERDEAD


    注 –

    应用程序必须检查 pthread_mutex_lock() 的返回代码,查找返回错误 EOWNWERDEAD 的互斥锁。


    • 互斥锁的新属主应使该互斥锁所保护的状态保持一致。如果上一个属主失败,则互斥锁状态可能会不一致。

    • 如果新属主能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np(),并解除锁定该互斥锁。

    • 如果新属主无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_consistent_np(),而是解除锁定该互斥锁。

      所有等待的线程都将被唤醒,以后对 pthread_mutex_lock() 的所有调用都将无法获取该互斥锁。返回代码为 ENOTRECOVERABLE。通过调用 pthread_mutex_destroy() 取消对互斥锁的初始化,并调用 pthread_mutex_int() 重新初始化该互斥锁,可使该互斥锁保持一致。

    如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主获取该锁时将返回代码 EOWNERDEAD

pthread_mutexattr_getrobust_np设置互斥锁的强健属性

1 int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, int *robustness);

注 –

仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_getrobust_np() 才适用。


attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。

robustness 是互斥锁属性对象的强健属性值。

 

  下面给个测试小程序进一步了解互斥,mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock, 执行了第一次pthread_mutex_lock(testlock)之后,在unlock(testlock)之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock(testlock),这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的。

 1 #include<stdio.h>
 2 #include<pthread.h>
 3 
 4 static pthread_mutex_t testlock;
 5 pthread_t test_thread;
 6 
 7 void *test()
 8 {
 9 pthread_mutex_lock(&testlock);
10 printf("thread Test() \n");
11 pthread_mutex_unlock(&testlock);
12 }
13 
14 int main()
15 {
16 pthread_mutex_init(&testlock, NULL);
17 pthread_mutex_lock(&testlock); 
18 
19 printf("Main lock \n");
20 
21 pthread_create(&test_thread, NULL, test, NULL);
22 sleep(1); //更加明显的观察到是否执行了创建线程的互斥锁
23 printf("Main unlock \n");
24 pthread_mutex_unlock(&testlock); 
25 
26 sleep(1);
27 pthread_join(test_thread,NULL); 
28 pthread_mutex_destroy(&testlock); 
29 return 0;
30 }
31 
32 make
33 gcc -D_REENTRANT -lpthread -o test test.c
34 
35 结果:
36 Main lock 
37 Main unlock 
38 thread Test()

 

二、条件变量

条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。

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

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。 

条件变量的结构为pthread_cond_t

最简单的使用例子:

1 pthread_cond_t cond;
2 pthread_cond_init(&cond, NULL);
3 pthread_cond_wait(&cond, &mutex);
4     ...
5 pthread_cond_signal(&cond);

1)创建和注销   

条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:     
pthread_cond_t   cond=PTHREAD_COND_INITIALIZER     

动态方式调用pthread_cond_init()函数,API定义如下:     

1 int   pthread_cond_init(pthread_cond_t   *cond,pthread_condattr_t   *cond_attr)

尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。   
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:     

1 int   pthread_cond_destroy(pthread_cond_t   *cond)   

  

2)等待和激发

等待:

1 int   pthread_cond_wait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex)   
2 int   pthread_cond_timedwait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex,   const   struct   timespec   *abstime)     

等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。 且进入wait时会把传入的互斥锁解锁。

 无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race   Condition)。

mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。   执行pthread_cond_wait()时自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。

因此,全过程可以描述为:

(1)pthread_mutex_lock()上锁,

(2)pthread_cond_wait()等待,等待过程分解为为:解锁--条件满足--加锁

(3)pthread_mutex_unlock()解锁。 

 

激发:

1 /* 唤醒一个等待该条件变量cond的线程 */
2 int pthread_cond_signal (pthread_cond_t *cond);
3 /* 唤醒所有等待条件变量cond的线程 */
4 int pthread_cond_broadcast (pthread_cond_t *cond);

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。 两者 如果没有等待的线程,则什么也不做。

 

属性设置:

/* 初始化条件变量属性对象  */
int pthread_condattr_init (pthread_condattr_t * attr);
/* 销毁条件变量属性对象  */
int pthread_condattr_destroy (pthread_condattr_t * attr);
/* 获取条件变量属性对象在进程间共享与否的标识  */
int pthread_condattr_getpshared (const pthread_condattr_t* attr,int*  pshared);
/* 设置条件变量属性对象,标识在进程间共享与否 */
int pthread_condattr_setpshared (pthread_condattr_t* attr, int  pshared) ;

  PTHREAD_PROCESS_PRIVATE

  如果条件变量的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该条件变量。

  PTHREAD_PROCESS_SHARED

  条件变量变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享条件变量,可以在共享内存中创建条件变量,并将 pshared 属性设置为 PTHREAD_PROCESS_SHARED

 

实例:

 1 #include <pthread.h>  
 2     using namespace std;  
 3   
 4     pthread_cond_t qready = PTHREAD_COND_INITIALIZER;    //初始构造条件变量  
 5     pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;    //初始构造锁  
 6     pthread_t tid1,tid2,tid3;  
 7   
 8     int x = 10;  
 9     int y = 20;  
10   
11   
12     void *thrd_1(void *arg)  
13     {  
14         pthread_mutex_lock(&qlock);  
15         while(x<y)  
16         {  
17             pthread_cond_wait(&qready,&qlock);  
18         }  
19         pthread_mutex_unlock(&qlock);  
20         cout<<"1"<<endl;  
21         sleep(5);  
22     }  
23   
24     void *thrd_2(void *arg)  
25     {  
26         pthread_mutex_lock(&qlock);  
27         x = 20;  
28         y = 10;  
29         cout<<"has change x and y"<<endl;  
30   
31         pthread_mutex_unlock(&qlock);  
32         if(x > y)  
33         {  
34             pthread_cond_signal(&qready);  
35         }  
36         cout<<"2"<<endl;  
37     }  
38   
39     void *thrd_3(void *arg)  
40     {  
41         pthread_join(tid1,NULL);  
42         cout<<"3"<<endl;  
43     }  
44   
45     int main(int argc,char **argv)  
46     {  
47         int err;  
48         err = pthread_create(&tid1,NULL,thrd_1,NULL);  
49         if(err != 0)  
50         {  
51             cout<<"pthread 1 create error"<<endl;  
52         }  
53         err = pthread_create(&tid2,NULL,thrd_2,NULL);  
54         if(err != 0)  
55         {  
56             cout<<"pthread 2 create error"<<endl;  
57         }  
58         err = pthread_create(&tid3,NULL,thrd_3,NULL);  
59         if(err != 0)  
60         {  
61             cout<<"pthread 3 create error"<<endl;  
62         }  
63         while(1)  
64         {  
65             sleep(1);  
66         }  
67         return 0;  
68           
69     }

  可以看到,创建了3个线程后,执行顺序2,1,3,即打印出的数字是213。为什么是这个顺序呢?我们接下去看,当创建tid1线程的时候,进入线程函数,并且加上了锁,然后进入pthread_cond_wait函数,这个函数的功能是等待qready这个条件变量成功,这个条件是什么呢?我们稍后在看,现在我们只要知道,这个函数在qready条件没满足的时候会卡在这里,并且,会把传入的互斥锁解锁,为什么要解锁的,拟可以想想,如果不解锁的话,那外部就没有可以对x,y的修改权,应为其他两个线程想要修改这两个值的话都需要对qclock进行枷锁。

  好了线程1就这样,那之后就会运行线程2,我们看线程2的线程函数,该函数一开始也加了锁,但当线程1的pthread_cond_wait解锁之后,他就可以继续运行了,并且,在之后,它对x,y进行了修改,改好之后进行了解锁,并且调用了pthread_cond_signal通知线程1,现在可以知道了吧。这个满足的条件就是要x>y。
   现在这里有个问题,一定要在发送通知之前解锁吗?答案是肯定的,为什么,因为如果先发送通知信号给线程1的时候,pthread_cond_wait可能在线程2的解锁之前就返回,而当它返回的时候,会再次将这个所进行锁定,而这个所还没有在线程2中解锁,应次会使其在次卡住。虽然这个卡住在线程2运行到解锁处会消除,但这并不符合我们有时的需求,所以最好还是在解锁之后在发送信号。(如果看不懂的话,可以参考下面红色字体的部分!!!)
    所以可以看出为什么线程2总是在线程1之前执行完毕,线程3就更不用说了,pthread_join你们懂的!!!
 
三、信号量

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。
只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数
pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。

下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t *sem)用来释放信号量sem。

在上面的生产者、消费者例子中,我们假设缓冲区最多能放10条消息。用信号量来实现如下:

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

static char buff[50];
pthread_mutex_t mutex;
pthread_mutex_t cond_mutex;
pthread_cond_t cond;
sem_t msg_cnt;      //缓冲区消息数
sem_t space_cnt;   //缓冲区空闲数

void consumeItem(char *buff)
{
    printf("consumer item\n");
}
void produceItem(char *buff)
{
    printf("produce item\n");
}
void *consumer(void *param)
{
    while (1)
    {
        sem_wait(&msg_cnt);
        pthread_mutex_lock(&mutex);
        consumeItem(buff);
        pthread_mutex_unlock(&mutex);
        sem_post(&space_cnt);
    }
    return NULL;
}

void *producer(void *param)
{
    while (1)
    {
        sem_wait(&space_cnt);
        pthread_mutex_lock(&mutex);
        produceItem(buff);        
        pthread_mutex_unlock(&mutex);
        sem_post(&msg_cnt);
        
    }
    return NULL;
}

int main()
{
    pthread_t tid_c, tid_p;
    void *retval;
    pthread_mutex_init(&mutex, NULL);
    pthread_mutex_init(&cond_mutex, NULL);
    
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);

    sem_init(&msg_cnt, 0, 0);        //初始缓冲区没有消息
    sem_init(&space_cnt, 0, 10);  //初始缓冲区能放10条消息
    
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c, NULL, consumer, NULL);
    
        
    pthread_join(tid_p, &retval);
    pthread_join(tid_c, &retval);
            
    return 0;
}
复制代码

 

 
posted @ 2017-06-08 17:14  Boblim  阅读(4045)  评论(0编辑  收藏  举报