线程概述:
其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这 个进程中执行线程函数。不过这个copy过程和fork不一样。copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在 copy后的进程中便能体现出来。
大部分多线程程序需要在线程间共享数据。如果两个线程同时访问共享数据就可能有问题,因为一个线程可能在另一个线程修改共享数据的过程中使用该数据;并认为共享数据保持不变。下面讨论如何防止此类问题;
Pthread使用类一种特殊形式的Edsger Dijkstra信号灯-------互斥量。 互斥量(mutex)是由单词互相(mutual)的首部"mut" 和 排斥(exclusion)的首部 “ex” 组合而成的;
典型理解为: 用来确保一次只能有一个人舀水这一不变量;
创建和销毁互斥量:
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITALIZER;
int pthread_mutex_init(pthread_mutex_t *my_mutex, pthread_mutexattr_t *attr );
int pthread_mutex_destroy( pthread_mutex_t *mutex);
程序中的互斥量是用pthread_mutex_t类型的变量来表示的。
不能拷贝互斥量变量,因为使用拷贝的互斥量是不确定的。 但是可以拷贝指向互斥量的指针,这样就可以使用多个函数或线程共享互斥量来实现同步;
大部分时间你可能在 “文件范围” 内,即函数体外,声明互斥量为外部或静态存储类型。
如果有其他文件使用互斥量,则将其声明为外部类型,
如果仅在本文件内使用,则将其声明为静态类型,使用宏PTHREAD_MUTEX_INITALIZER 来声明具有默认属性的静态类型。
初始化互斥量的两种方法及适用范围:
1、静态初始化互斥量,即使用宏PTHREAD_MUTEX_INITALIZER来初始化互斥量,使之具有静态属性;这种方式初始化的互斥量不需要释放;
2、通常不能静态地初始化一个互斥量, 而是使用pthread_mutex_init来动态初始化互斥量;这种初始化方式在不需要使用互斥量时,需要调用pthread_mutex_destroy来释放它;
互斥量的创建:
我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性
就够了,但我们还是有必要来了解一下线程的有关属性。属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义。
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
进程的绑定:
关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分
配、对线程的控制是通过轻进程实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为
非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,
它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
1 下面的代码即创建了一个绑定的线程。 2 #include <pthread.h> 3 pthread_attr_t attr; 4 pthread_t tid; 5 /*初始化属性值,均设为默认值*/ 6 pthread_attr_init(&attr); 7 pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); 8 pthread_create(&tid, &attr, (void *) my_function, NULL); 9 10 线程的分离状态决定一个线程以什么样的方式来终止自己。
在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。
线程分离:
设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。
注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调
用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段
等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对
取得的值修改后再存放回去。下面即是一段简单的例子。
1 #include <pthread.h> 2 #include <sched.h> 3 pthread_attr_t attr; 4 pthread_t tid; 5 struct sched_param param; 6 int newprio=20; 7 pthread_attr_init(&attr); 8 pthread_attr_getschedparam(&attr, ¶m); 9 param.sched_priority=newprio; 10 pthread_attr_setschedparam(&attr, ¶m); 11 pthread_create(&tid, &attr, (void *)myfunction, myarg);
线程数据:
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可
以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都
应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。
要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同
的,在同一个线程里,它代表同样的数据内容。和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
创建键的函数原型为:
extern int pthread_key_create __P ((pthread_key_t *__key, void (*__destr_function) (void *)));
第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数。
如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。
函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。
在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型
由于各个线程都会调用这个函数,所以我们使用线程数据。
1 /* 声明一个键*/ 2 pthread_key_t myWinKey; 3 /* 函数 createWindow */ 4 void createWindow ( void ) { 5 Fl_Window * win; 6 static pthread_once_t once= PTHREAD_ONCE_INIT; 7 /* 调用函数createMyKey,创建键*/ 8 pthread_once ( & once, createMyKey) ; 9 /*win指向一个新建立的窗口*/ 10 win=new Fl_Window( 0, 0, 100, 100, "MyWindow"); 11 /* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/ 12 setWindow(win); 13 /* 将窗口指针值绑定在键myWinKey上*/ 14 pthread_setspecific ( myWinKey, win); 15 } 16 /* 函数 createMyKey,创建一个键,并指定了destructor */ 17 void createMyKey ( void ) { 18 pthread_key_create(&myWinKey, freeWinKey); 19 } 20 /* 函数 freeWinKey,释放空间*/ 21 void freeWinKey ( Fl_Window * win){ 22 delete win; 23 }
这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。
在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
extern void *pthread_getspecific __P ((pthread_key_t __key));
这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete
用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的
destructor函数。线程数据的释放必须在释放键之前完成。
加锁和解锁互斥量
当知道没有线程在互斥量上阻塞,且互斥量也没有被锁住时,可以安全地释放它。 获得此信息的最好方式是在刚刚解锁互斥量的线程内,程序逻辑确保随后不再有线程会加锁该互斥量。
当线程在某个堆栈数据结构中锁住互斥量,以从列表中删除该结构并释放内存时,在释放互斥量占有的空间之前先将互斥量解锁 和释放 是个不错的办法;
int pthread_mutex_lock (pthread_mutex_t *my_mutex);
int pthread_mutex_trylock(pthread_mutex_t *my_mutex);
int pthread_mutex_unlock(pthread_mutex_t *my_mymutex);
在最简单的情况下,使用互斥量就是:通过调用 pthread_mutex_lock 或 pthread_mutex_trylock 锁住互斥量, 处理共享数据,然后调用pthread_mutex_unlock解锁互斥量。
为确保线程能够读取一组变量的一致的值,需要在任何读写这些变量的代码段周围锁住互斥量;
当调用线程已经锁住互斥量之后,就不能再加锁该互斥量。试图这样做的结果可能是返回错误(EDEADLK),或者可能陷入“自死锁”,使不幸的线程永远等待下去。
不能解锁一个已经解锁的互斥量, 也不能解锁由其他线程锁住的互斥量。 锁住的互斥量是属于加锁线程的。
非阻塞式互斥锁:
当调用pthread_mutex_lock加锁互斥量时,如果此时互斥量应经被锁住,则调用线程将被阻塞。这通常是加锁之后希望要的结果,但有时可能希望如果互斥量已被锁住,则执行另外的 代码路线,程序可能做其他一些有益的工作而不仅仅是等待; 为了实现这个效果,Pthreads提供了pthread_mutex_trylock函数,当调用互斥量已被锁住时调用该函数将返回错误代码 EBUSY;
使用非阻塞式互斥量加锁函数时,需要确保只有当pthread_mutex_trylock函数调用成功,才能解锁互斥量;有关如何 将非线程代码改成线程安全代码是个有趣的话题:
实现线程安全的函数库的一个相对简单的做法是创建一个互斥量; 在每次进入函数库时锁住它,在退出库的时候解锁它。这样函数库就变成了一个串行区域,从而防止了线程见的任
何冲突。使用多个互斥量,有时候,一个互斥量是不够的,特别是代码需要跨越软件体系内部的界限时。
典型例子:当多个线程同时访问一个队列结构时,需要两个互斥量,一个用来保护队列头,一个用来保护对列元素内的数据。当为多线程建立一个树型结构时,可能需要为每个节点设置一
个互斥量;
固定加锁层次:所有需要同时加锁互斥量A和互斥量B的代码,必须首先加锁互锁A,然后加锁互斥量B
试加锁和回退:在锁住某个集合中的第一个互斥量后,使用pthread_mutex_trylock来加锁集合中的其他互斥量, 如果失败则将集合中所有已加锁顺序。例如,如果有两个互斥量,一个
护队列头,一个保护队列元素内的数据,则很显然的一种固定加锁层次就是先将队列头互斥量加锁,然后再加锁另一个互斥量。
条件变量:
使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互
斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个
正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
条件变量的结构是pthread_cond_t , 函数pthread_cond_init( )被用来初始化一个条件变量。它的原型:
int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t* cond_attr);
其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构;
和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才
能重新初始化或被释放。
释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。
函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型:int pthread_cond_wait( pthread_cond_t * cond , pthread_mutex_t * mutex);
线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条
件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤
醒。这个过程一般用while语句实现。
另一个用来阻塞线程的函数是pthread_cond_timedwait(), 它的原型:
int thread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, const struct timespac * abstime);
它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
函数pthread_cond_signal()的原型为:
extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。
要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数
pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。
1 pthread_mutex_t count_lock; 2 pthread_cond_t count_nonzero; 3 unsigned count; 4 decrement_count () 5 6 { 7 pthread_mutex_lock (&count_lock); 8 while(count==0) 9 pthread_cond_wait( &count_nonzero, &count_lock); 10 count=count -1; 11 pthread_mutex_unlock (&count_lock); 12 } 13 14 15 increment_count(){ 16 pthread_mutex_lock(&count_lock); 17 if(count==0) 18 pthread_cond_signal(&count_nonzero); 19 count=count+1; 20 pthread_mutex_unlock(&count_lock); 21 }
count 值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数 increment_count时,pthread_cond_signal()函数改变条件变量,告知
decrement_count()停止阻塞。可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。
信号量:
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数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的值减一。
1 函数sem_destroy(sem_t *sem)用来释放信号量sem。 2 下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。 3 /* File sem.c */ 4 #include <stdio.h> 5 #include <pthread.h> 6 #include <semaphore.h> 7 #define MAXSTACK 100 8 int stack[MAXSTACK]; 9 int size=0; 10 sem_t sem; 11 /* 从文件1.dat读取数据,每读一次,信号量加一*/ 12 void ReadData1(void){ 13 FILE *fp=fopen("1.dat","r"); 14 while(!feof(fp)){ 15 fscanf(fp,"%d %d",&stack[0],&stack[1]); 16 sem_post(&sem); 17 ++size; 18 } 19 fclose(fp); 20 } 21 /*从文件2.dat读取数据*/ 22 void ReadData2(void){ 23 FILE *fp=fopen("2.dat","r"); 24 while(!feof(fp)){ 25 fscanf(fp,"%d %d",&stack[0],&stack[1]); 26 sem_post(&sem); 27 ++size; 28 } 29 fclose(fp); 30 } 31 /*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/ 32 void HandleData1(void){ 33 while(1){ 34 sem_wait(&sem); 35 printf("Plus:%d+%d=%d\n",stack[0],stack[1], 36 stack[0]+stack[1]); 37 --size; 38 } 39 } 40 void HandleData2(void){ 41 while(1){ 42 sem_wait(&sem); 43 printf("Multiply:%d*%d=%d\n",stack[0],stack[1], 44 stack[0]*stack[1]); 45 --size; 46 } 47 } 48 int main(void){ 49 pthread_t t1,t2,t3,t4; 50 sem_init(&sem,0,0); 51 pthread_create(&t1,NULL,(void *)HandleData1,NULL); 52 pthread_create(&t2,NULL,(void *)HandleData2,NULL); 53 pthread_create(&t3,NULL,(void *)ReadData1,NULL); 54 pthread_create(&t4,NULL,(void *)ReadData2,NULL); 55 /* 防止程序过早退出,让它在此无限期等待*/ 56 pthread_join(t1,NULL); 57 } 58 在Linux下,我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果: 59 Multiply:-1*-2=2 60 Plus:-1+-2=-3 61 Multiply:9*10=90 62 Plus:-9+-10=-19 63 Multiply:-7*-8=56 64 Plus:-5+-6=-11 65 Multiply:-3*-4=12 66 Plus:9+10=19 67 Plus:7+8=15 68 Plus:5+6=11 69 从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。
条件变量是用来通知共享数据状态信息的,可以使用条件变量来通知队列已空,或队列非空,或任何其他需要有线程处理的共享数据状态。
当一个线程互斥地访问共享状态时,它可能法系那在其他线程改变状态之前它什么也做不了。状态可能是对的和一致的,即没有破坏不变量,此时线程对当前的状态不感兴趣。
等待条件变量总是返回锁住的互斥量
这就是为什么要使用条件变量的原因。条件变量是与互斥量相关,也与互斥量保护的共享数据相关的信号机制。在一个条件变量上等待导致以下原子操作:释放相关互斥量,等待其他线程发给该条件变 量的信号(唤醒一个等待者)或广播该条件变量(唤醒所有等待者)。当等待条件变量时,互斥量必须始终锁住;当线程从条件变量等待中醒来,它重新继续锁住互斥量。
条件变量的作用是发信号,而不是互斥。
条件变量不提供互斥。需要一个互斥量来同步对共享数据的访问,这就是为什么在等待条件变量时必须指定一个互斥量。
创建条件变量 和 释放条件变量
pthread_cond_t cod = PTHREAD_CODE_INITIALIZER;
int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *condattr );
int pthread_cond_destroy(pthread_cond_t *cond);
程序中由pthread_cond_t 类型的变量来表示条件变量。永远不要拷贝条件变量,因为使用条件变量的备份是不可知的, 这就像是打一个断线的电话号码并等待回答一样。
为了获得最好的结果,应该将条件变量与相关的谓词“链接”在一起对待;
建议将一组不便量,谓词和它们的互斥量, 以及一个或多个条件变量封装为 一个数据结构的元素 ;(结构体)
当动态初始化条件变量时,应该在不需要它时调用pthread_cond_destroy来释放它,
而通过PTHREAD_COND_INITIALIZER宏静态初始化的条件变量, 则不需要pthread_cond_destroy来释放;
等待条件变量
int pthead_cond_wait(pthread_cond_t *cond, pthread_cond_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec * expiration);
pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。
pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或
pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那 么是根据各等待线程优先级的高低确定哪个线程接收到信号
开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一 个pthread_cond_signal调用最多发信一次。
但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个
pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
另外,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐对pthread_cond_wait() 使用
while循环来做条件判断;