【线程】多线程同步互斥-条件变量与信号量,生产者与消费者问题
条件变量
条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。
而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。
使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
一般说来,条件变量被用来进行线程的同步。
条件变量的结构为pthread_cond_t
最简单的使用例子:
pthread_cond_t cond; pthread_cond_init(&cond, NULL); pthread_cond_wait(&cond, &mutex); ... pthread_cond_signal(&cond);
调用pthread_cond_wait的线程会按调用顺序进入等待队列,当有一个信号产生时,先进入阻塞队列的线程先得到唤醒。条件变量在某些方面看起来差不多。
正如上面所说的,条件变量弥补了互斥锁的不足。
接着前一章列举的生产者、消费者例子中,我们这里增加消费者(并假设缓冲区可以放任意多条信息),比如有3个消费者线程,如果都使用互斥锁,那么三个线程都要不断的去查看缓冲区是否有消息,有就取走。这无疑是资源的极大浪费。如果用条件变量,三个消费者线程都可以放心的“睡觉”,缓冲区有消息,消费者会收到通知,那时起来收消息就好了。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include <sys/time.h> 5 6 static char buff[50]; 7 pthread_mutex_t mutex;//互斥锁 8 pthread_cond_t cond;//条件变量 9 pthread_mutex_t cond_mutex; 10 11 void consumeItem(char *buff) 12 { 13 printf("consumer item\n"); 14 } 15 16 void produceItem(char *buff) 17 { 18 printf("produce item\n"); 19 } 20 21 void *consumer(void *param)//消费者进程回调函数 22 { 23 int t = *(int *)param; 24 while (1) 25 { 26 pthread_cond_wait(&cond, &cond_mutex);//加入到等待队列中去 27 pthread_mutex_lock(&mutex); 28 printf("%d: ", t); 29 consumeItem(buff); 30 pthread_mutex_unlock(&mutex); 31 } 32 return NULL; 33 } 34 35 void *producer(void *param)//生产者进程回调函数 36 { 37 while (1) 38 { 39 pthread_mutex_lock(&mutex); 40 produceItem(buff); 41 pthread_mutex_unlock(&mutex); 42 pthread_cond_signal(&cond);//唤醒等待队列中的进程 43 sleep(1); 44 } 45 return NULL; 46 } 47 48 int main() 49 { 50 pthread_t tid_c, tid_p, tid_c2, tid_c3; 51 void *retval; 52 53 pthread_mutex_init(&mutex, NULL);//初始化互斥锁 54 pthread_mutex_init(&cond_mutex, NULL); 55 56 pthread_cond_t cond; 57 pthread_cond_init(&cond, NULL);//初始化条件变量 58 59 60 int p[3] = {1, 2, 3}; //用来区分是哪个线程 61 62 pthread_create(&tid_p, NULL, producer, NULL); 63 pthread_create(&tid_c, NULL, consumer, &p[0]); 64 pthread_create(&tid_c2, NULL, consumer, &p[1]); 65 pthread_create(&tid_c3, NULL, consumer, &p[2]); 66 67 68 pthread_join(tid_p, &retval); 69 pthread_join(tid_c, &retval); 70 pthread_join(tid_c2, &retval); 71 pthread_join(tid_c3, &retval); 72 73 return 0; 74 }
要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如缓冲区到底有多少条信息等。
与条件变量有关的其它函数有:
1 /* 销毁条件变量cond */ 2 int pthread_cond_destroy (pthread_cond_t *cond); 3 4 /* 唤醒所有等待条件变量cond的线程 */ 5 int pthread_cond_broadcast (pthread_cond_t *cond); 6 7 /* 等待条件变量直到超时 */ 8 int pthread_cond_timedwait (pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime); 9 10 /* 初始化条件变量属性 attr*/ 11 int pthread_condattr_init (pthread_condattr_t *attr); 12 13 /* 销毁条件变量属性 */ 14 int pthread_condattr_destroy (pthread_condattr_t *attr) 15 16 /* 读取条件变量属性attr的进程共享标志 */ 17 int pthread_condattr_getpshared (const pthread_condattr_t *attr,int *pshared); 18 19 /* 更新条件变量属性attr的进程共享标志 */ 20 int pthread_condattr_setpshared (pthread_condattr_t *attr,int pshared);
一般来说,条件变量被用来进行线程间的同步。
信号量
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
当公共资源增加时,调用函数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条消息。用信号量实现
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include <semaphore.h> 5 6 static char buff[50]; 7 pthread_mutex_t mutex;//互斥锁 8 pthread_mutex_t cond_mutex; 9 pthread_cond_t cond;//条件变量 10 11 sem_t msg_cnt; //缓冲区消息数 12 sem_t space_cnt; //缓冲区空闲数 13 14 void consumeItem(char *buff) 15 { 16 printf("consumer item\n"); 17 } 18 19 void produceItem(char *buff) 20 { 21 printf("produce item\n"); 22 } 23 24 void *consumer(void *param)//消费者进程回调函数 25 { 26 while (1) 27 { 28 sem_wait(&msg_cnt);//等待一个消息 29 pthread_mutex_lock(&mutex); 30 consumeItem(buff); 31 pthread_mutex_unlock(&mutex); 32 sem_post(&space_cnt);//增加一个空位 33 } 34 return NULL; 35 } 36 37 void *producer(void *param)//生产者进程回调函数 38 { 39 while (1) 40 { 41 sem_wait(&space_cnt);//减少一个空位 42 pthread_mutex_lock(&mutex); 43 produceItem(buff); 44 pthread_mutex_unlock(&mutex); 45 sem_post(&msg_cnt);//增加一个消息 46 47 } 48 return NULL; 49 } 50 51 int main() 52 { 53 pthread_t tid_c, tid_p; 54 void *retval; 55 56 pthread_mutex_init(&mutex, NULL);//以默认属性初始化 57 pthread_mutex_init(&cond_mutex, NULL); 58 59 pthread_cond_t cond; 60 pthread_cond_init(&cond, NULL); 61 62 sem_init(&msg_cnt, 0, 0); //信号量初始化,初始缓冲区没有消息 63 sem_init(&space_cnt, 0, 10); //初始缓冲区能放10条消息 64 65 pthread_create(&tid_p, NULL, producer, NULL); 66 pthread_create(&tid_c, NULL, consumer, NULL); 67 68 69 pthread_join(tid_p, &retval); 70 pthread_join(tid_c, &retval); 71 72 return 0; 73 }