线程(2)
线程同步
当多个控制线程共享相同内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取或修改的,那么就不在一致性问题。
当两个或多个线程试图在同一时间修改同一变量时,也需要进行同步。考虑变量递增操作的情况:增量操作通常分三步:
1. 从内存单元读入寄存器
2. 在寄存器中进行变量值的增加
3. 把新的值写回内存单元
互斥量
可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量从本质上说是一把锁,在访问资源前对互斥量加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到线程释放该互斥锁。
互斥变量用pthread_mutex_t数据类型来表示,在使用互斥量以前,必须首先对其进行初始化,可以把它置为PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量,那么释放内存前需要调用pthread_mutex_destroy。用默认属性初始化互斥量,只需要把attr设置为NULL。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,constpthread_mutexattr_t *restrict attr);
intpthread_mutex_destroy(pthread_mutex_t *mutex);
若成功返回0,错误返回错误编号。
对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock( pthread_mutex_t *mutex );
int pthread_mutex_unlock(pthread_mutex_t *mutex);
若成功返回0,错误返回错误编号。
如果线程不希望不阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
避免死锁
如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。
如果程序中使用多个互斥量时,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是游泳第二个互斥量的线程也试图锁住第一个互斥量,这是就会发生死锁。
可以通过小心地控制互斥量加锁的顺序来避免死锁的发生。只有一个线程试图以与另一个线程相反的顺序锁住互斥量时,才可能出现死锁。
读写锁
读写锁有三种状态:度模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占用写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。读写锁非常适合于对数据结构读的次数远大于写的情况。读写锁也叫做共享——独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。
与互斥量一样,读写锁在使用之前必须初始化,在释放它们底层的内存前必须销毁。
#include <pthread.h>
intpthread_rwlock_init(pthread_rwlock* restrict rwlock, const pthread_rwlockattr_t* restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock;要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock。不管以何种方式锁住读写锁,都可以调用pthreead_rwlock_unlock进行解锁。
#include <pthread.h>
intpthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
所有的返回值都是:若成功则返回0,否则返回错误编号
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
条件变量
条件变量是线程可用的另一种同步机制。条件变量给多线程提供了一个回合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会觉察到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量使用之前必须首先进行初始化,pthread_cond_t数据类型代表的条件变量以两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量时动态分配的,可以使用pthread_cond_init函数进行初始化。
在释放底层的内存空间之前,可以使用pthread_cond_destroy函数对条件变量进行去初始化。
#include <pthread.h>
int pthread_cond_init (pthread_cond_t* restrict cond,pthread_condattr_t * restrict attr);
int pthread_cond_destroy (pthread_cond_t* cond);
除非需要创建一个非默认属性的条件变量,否则pthread_cond_init函数的attr参数可以设置NULL。
使用pthread_cond_wait等待条件变为真,如果在给定时间内条件不能满足,那么会生成一个代表出错误码的返回值。
intpthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
intpthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, conststruct timespec *abstime)
条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(RaceCondition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。