条件变量:前面讲述了如何使用互斥锁实现线程间数据的共享和通信,互斥锁有个明显的缺点就是只有非锁定和锁定两种状态。而条件变量通过允许线程阻塞和另一线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开互斥锁并等待条件的变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量用于线程间的同步。
条件变量结构为pthread_cond_t,函数pthread_cond_init用于初始化一个条件变量,它的原型为extern int pthread_cond_init_P(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()使线程阻塞在条件变量上,函数原型为:
Extern int pthread_cond_wait__P(pthread_cond_t *__cond , pthread_mutex_t * __mutex),线程解开mutex指向的锁并被条件变量cond阻塞,线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是条件变量只起阻塞和呼唤线程的作用,具体判断条件还需用户给出。
函数pthread_cond_signal()的原型为
extern int pthread_cond_signal_P(pthread_cond_t * __cond);它用来释放阻塞在条件变量cond上的线程,多线程阻塞在此条件变量时,哪一个线程被唤醒有现成的调度策略决定,注意必须使用保护条件变量的互斥锁保护这个函数,否则条件满足信号有可能在测试条件和调用pthread_cond_wait之间被发出,从而造成无限制等待。下面是使用函数pthread_cond_wait和pthread_signal的例子。
pthread_mutex_t count_lock ;
pthread_cond_t count_nonzero ;
unsigned count = 0;
decrement_count () {
pthread_mutex_lock(&count_lock) ;
while(count == 0)
pthread_cond_wait(&count_nonzero, &count_lock) ;
count = count - 1 ;
pthread_mutex_unlock(&count_lock);
}
increment_count ( ) {
pthread_mutex_lock(&count_lock) ;
if(count == 0)
pthread_cond_signal(&count_nonzero) ;
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
count值为0时,decrement函数在pthread_cond_wait处阻塞,并打开互斥锁count_lock,此时调用函数 pthread_cond_signa()改变条件变量,告知decrement_count停止阻塞。函数pthread_cond_broadcast用于唤醒所有被阻塞在条件变量cond上的线程,这些线程被唤醒后将再次竞争相应的互斥锁。
信号量:
信号量本质上是一个非负的计数器,它被用来控制对公共资源的访问,当公共资源增加时,调用函数sem_post()增加信号量,只有当信号量大于零时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和sem_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本,它们都在头文件/usr/include/semaphore.h中定义。
信号量的数据类型为struct 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的值大于零,解除阻塞后将sem的值减一,表明公共资源经使用后减少,函数sem_trywait(sem_t *sem)是函数sem_wait()的非阻塞版本,直接将信号量减一。
函数sem_destroy(sem_t *sem)用于释放信号量sem。
下面我们看一下使用信号量的例子,共计4个线程,其中两个线程负责从文件读取数据到公共缓冲区,另外两个线程从缓冲区读取数据做不同处理。
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
int size = 0 ;
sem_t sem ;
void ReadData1(void ) {//从文件1.dat读取数据,每读一次信号量加1
FILE *fp = fopen(“1.dat”, “r”) ;
while(!feof(fp)) {
fscanf(fp , “%d, %d”, &stack[SIZE][0], &stack[SIZE][1] ) ;
sem_post(&sem) ;
++size ;
}
fclose(fp) ;
}
void ReadData2(void) {//从文件2读取数据
FILE *fp = fopen(“2.dat” , “r”) ;
while (!feof(fp)) {
fscanf(fp , “%d%d”, &stack[size][0], &stack[size][1]) ;
sem_post(&sem) ;
++size;
}
fclose(fp) ;
}
void HandleData1(void) {
while(1) { //阻塞等待缓冲区数据,读取数据后释放空间继续等待
sem_wait(&sem) ;
printf(“plus: %d + %d = %d”, stack[size][0], stack[size][0], stack[size][1] + stack[size][0]) ;
size--;
}
}
void HandleData2(void) {
while(1) {
sem_wait(&sem) ;
printf(“Mul: %d * %d = %d ” , stack[size][0] , stack[size][1], stack[size][0]*stack[size][1]);
size--;
}
}
int main (void) {
pthread_t t1 , t2 , t3 , t4 ;
sem_init(&sem, 0, 0) ;
pthread_create(&t1 , NULL , (void *)HandleData1 , NULL) ;
pthread_create(&t2 , NULL , (void *)HandleData2 ,NULL ) ;
pthread_create(&t3 , NULL , (void *)HandleData3 ,NULL ) ;
pthread_create(&t4 , NULL , (void *)HandleData4 ,NULL ) ;
pthread_join(t1, NULL); //防止程序退出过早
}
在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,运行得到如下结果
从中看出数据并未按我们原先的数据显示出来这是由于size这个值被各个线程任意修改的缘故。