第3章 互斥锁和条件变量
3.1 概述
默认情况下互斥锁和条件变量用于线程间同步,若将它们放在共享内存区,也能用于进程间同步。
对一个公共对象的访问若不能原子进行,就需要某种形式的同步。
3.2 互斥锁
静态分配的互斥锁变量可以用常值PTHREAD_MUTEX_INITIALIZER初始化,动态分配和用于共享内存的互斥锁变量要用pthread_mutex_init初始化。
pthread_mutex_lock:阻塞模式上锁。
pthread_mutex_trylock:非阻塞模式上锁。
pthread_mutex_unlock:解锁,只能由上锁的线程执行。
互斥锁是协作性锁,无法禁止绕过这种机制的访问。
3.3 生产者-消费者
也称有界缓冲区问题,若干个生产者和若干个消费者共享使用固定数目的缓冲区,因而带来的同步和通信问题。
各种IPC手段本身就是一个生产者-消费者问题的实例。
管道、FIFO和消息队列的同步是隐式同步,使用者只能通过指定的接口来使用这些IPC方式,其中的同步都由内核完成。
共享内存作为IPC,需要使用者进行显式同步,线程间共享全局数据也需要显式同步。
3.4 条件变量
互斥锁只能用于上锁,实现对某个共享对象的互斥访问,无法用于对某事件的等待。条件变量则用于等待。静态分配的条件变量可以用常值PTHREAD_COND_INITIALIZER初始化。
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
每个条件变量都需要关联一个互斥锁,用来提供对等待条件的互斥访问。调用pthread_cond_wait前mptr需要是上锁状态,Posix保证pthread_cond_wait返回后mptr仍是上锁状态,但调用期间会自动将mptr解锁。pthread_cond_wait返回时要先测试等待条件,避免假唤醒。
pthread_cond_signal:唤醒某个等待中的线程。
pthread_cond_broadcast:唤醒等待的所有线程,例子是读写锁中等待写完成的所有读者。
pthread_cond_timedwait:带超时的等待,超时时间是绝对时间。返回超时错误后,仍要先测试等待条件。
3.5 互斥锁和条件变量的属性
互斥锁和条件变量可设置的属性只有进程间共享属性,值可为:PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED。
对互斥锁、条件变量使用init函数初始化的话,使用完后要用destroy函数摧毁,对它们的属性对象也有相同的要求。
3.6 互斥锁的非正常终止
若进程在持有互斥锁时终止,内核不会负责自动释放持有的锁。内核自动清理的唯一同步锁类型是fcntl记录锁。
若被锁住的互斥锁的持有进程或线程终止,会造成这个互斥锁无法解锁,因而死锁。线程可以安装线程清理程序,用来在被取消时能释放持有的锁。但这种释放可能会导致共享对象的状态被部分更新,造成不一致。