● 互斥锁
互斥锁用来保证同一时间内只有一个线程在执行某段代码(临界区)。多线程编程最容易出问题的地方,就是临界区的界定和访问
控制。下面是一个生产者,消费者的简单例子。生产者、消费者公用一个缓冲区,这里假定缓冲区只能存放一条消息。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> static char buff[50]; int have_msg=0; pthread_mutex_t mutex; int delay=1; void consumeItem(char *buff) { printf("consumer item "); } void produceItem(char *buff) { printf("produce item "); } void *consumer(void *param) { while (1) { pthread_mutex_lock(&mutex); if (have_msg>0) { have_msg--; consumeItem(buff); } pthread_mutex_unlock(&mutex); sleep(delay); } return NULL; } void *producer(void *param) { while (1) { pthread_mutex_lock(&mutex); if (have_msg==0) { have_msg++; produceItem(buff); } pthread_mutex_unlock(&mutex); sleep(delay); } return NULL; } int main() { pthread_t tid_c, tid_p; void *retval; pthread_mutex_init(&mutex, NULL); 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; }
输出一定是这样的:
互斥锁最简单的使用是这样的:
pthread_mutex_t mutex; //定义锁
pthread_mutex_init(&mutex, NULL); //默认属性初始化锁
pthread_mutex_lock(&mutex); //申请锁
...
pthread_mutex_unlock(&mutex); //释放锁
设置锁的属性
函数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_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_cond_t
最简单的使用例子:
pthread_cond_t cond; pthread_cond_init(&cond, NULL); pthread_cond_wait(&cond, &mutex); ... pthread_cond_signal(&cond);
调用pthread_cond_wait的线程会按调用顺序进入等待队列,当有一个信号产生时,先进入阻塞队列的线程先得到唤醒。条件变量在某些方面看起来差不多。
正如上面所说的,条件变量弥补了互斥锁的不足。
接着上面列举的生产者、消费者例子中,我们这里增加消费者(并假设缓冲区可以放任意多条信息),比如有3个消费者线程,如果都使用互斥锁,那么三个线程都要不断的去查看缓冲区是否有消息,有就取走。这无疑是资源的极大浪费。如果我们用条件变量,三个消费者线程都可以放心的“睡觉”,缓冲区有消息,消费者会收到通知,那时起来收消息就好了。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> static char buff[50]; pthread_mutex_t mutex; pthread_mutex_t cond_mutex; pthread_cond_t cond; void consumeItem(char *buff) { printf("consumer item "); } void produceItem(char *buff) { printf("produce item "); } void *consumer(void *param) { int t = *(int *)param; while (1) { pthread_cond_wait(&cond, &cond_mutex); pthread_mutex_lock(&mutex); printf("%d: ", t); consumeItem(buff); pthread_mutex_unlock(&mutex); } return NULL; } void *producer(void *param) { while (1) { pthread_mutex_lock(&mutex); produceItem(buff); pthread_mutex_unlock(&mutex); pthread_cond_signal(&cond); sleep(1); } return NULL; } int main() { pthread_t tid_c, tid_p, tid_c2, tid_c3; void *retval; pthread_mutex_init(&mutex, NULL); pthread_mutex_init(&cond_mutex, NULL); pthread_cond_t cond; pthread_cond_init(&cond, NULL); int p[3] = {1, 2, 3}; //用来区分是哪个线程 pthread_create(&tid_p, NULL, producer, NULL); pthread_create(&tid_c, NULL, consumer, &p[0]); pthread_create(&tid_c2, NULL, consumer, &p[1]); pthread_create(&tid_c3, NULL, consumer, &p[2]); pthread_join(tid_p, &retval); pthread_join(tid_c, &retval); pthread_join(tid_c2, &retval); pthread_join(tid_c3, &retval); return 0; }
要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如缓冲区到底有多少条信息等。
与条件变量有关的其它函数有:
/* 销毁条件变量cond */
int pthread_cond_destroy (pthread_cond_t *cond);
/* 唤醒所有等待条件变量cond的线程 */
int pthread_cond_broadcast (pthread_cond_t *cond);
/* 等待条件变量直到超时 */
int pthread_cond_timedwait (pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
/* 初始化条件变量属性 attr*/
int pthread_condattr_init (pthread_condattr_t *attr);
/* 销毁条件变量属性 */
int pthread_condattr_destroy (pthread_condattr_t *attr)
/* 读取条件变量属性attr的进程共享标志 */
int pthread_condattr_getpshared (const pthread_condattr_t *
attr,
int *pshared);
/* 更新条件变量属性attr的进程共享标志 */
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条消息。用信号量来实现如下:
#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 "); } void produceItem(char *buff) { printf("produce item "); } 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; }