1 条件变量简介
在服务器编程中常用的线程池,多个线程会操作同一个任务队列,一旦发现任务队列中有新的任务,子线程将取出任务;这里因为是多线程操作,必然会涉及到用互斥锁保护任务队列的情况(否则其中一个线程操作了任务队列,取出线程到一半时,线程切换又取出相同任务)。但是互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。设想,每个线程为了获取新的任务不断得进行这样的操作:锁定任务队列,检查任务队列是否有新的任务,取得新的任务(有新的任务)或不做任何操作(无新的任务),释放锁,这将是很消耗资源的。
而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
对应于线程池的场景,我们可以让线程处于等待状态,当主线程将新的任务放入工作队列时,发出通知(其中一个或多个),得到通知的线程重新获得锁,取得任务,执行相关操作。
2 条件变量相关API
2.1初始化条件变量pthread_cond_init
#include <pthread.h> int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr);
2.2 等待条件变量pthread_cond_wait
#include <pthread.h> int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
2.3 等待条件变量到指定时间pthread_cond_timedwait
#include <pthread.h> #include <time.h> int pthread_cond_timedwait(pthread_cond_t *cv,pthread_mutex_t *mp, const structtimespec * abstime);
2.4 通知等待条件变量的单个线程pthread_cond_signal
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cv);
2.5 通知等待条件变量的所有线程pthread_cond_broadcast
#include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cv);
2.6 销毁条件变量pthread_cond_destroy
#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cv);
3 条件变量不恰当例子
#include <pthread.h> #include <sys/socket.h> #include <sys/types.h> #include <stdlib.h> #include <netinet/in.h> #include <list> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <iostream> #define WORKER_THREAD_NUM 5 using namespace std; typedef struct thread_param { int fd; sockaddr_in addr; socklen_t addr_len; }thread_param,*pthread_param; list<pthread_param> g_listClients; pthread_mutex_t g_mutex; pthread_cond_t g_cond; void* worker_thread_func(void* arg) { while(1) { pthread_mutex_lock(&g_mutex); //如果任务队列空,则进入等待状态 if(g_listClients.empty()) { pthread_cond_wait(&g_cond,&g_mutex); } pthread_param p = g_listClients.front(); g_listClients.pop_front(); pthread_mutex_unlock(&g_mutex); //TODO cout<<"Work Done!!!"<<endl; close(p->fd); //free(p); } } int main() { int serverfd; pthread_t childid; serverfd = socket(AF_INET,SOCK_STREAM,0); if(-1 ==serverfd) return -1; struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); serveraddr.sin_port = htons(80); if(-1 == bind(serverfd,(sockaddr*)&serveraddr,sizeof(serveraddr))) return -1; //监听 if(-1 == listen(serverfd,100)) return -1; pthread_mutex_init(&g_mutex,NULL); pthread_cond_init(&g_cond,NULL); //启动接受线程,然后处于等待状态 for(int i=0;i<WORKER_THREAD_NUM;i++) { ::pthread_create(&childid, NULL, worker_thread_func, NULL); cout<<"Thread ID:"<<childid<<endl; } cout << "Start accepting..." << endl; while(1) { pthread_param p = (pthread_param)malloc(sizeof(thread_param)); memset(p, 0, sizeof(thread_param)); p->fd = accept(serverfd, (struct sockaddr *)&p->addr, &p->addr_len); pthread_mutex_lock(&g_mutex); g_listClients.push_back(p); pthread_mutex_unlock(&g_mutex); //通知子线程来了新任务 pthread_cond_signal(&g_cond); } pthread_mutex_destroy(&g_mutex); pthread_cond_destroy(&g_cond); return 0; }
别人总结:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。