线程同步
当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。
同样,如果变量是只读的,多个线程同时读取该变量也不会有一致性,但是,当一个线程可以修改的变量,其他线程也可以读取和修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容是不会访问到无效值的。
为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量(互斥访问)。
互斥量
可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能回去再次等待它重新变为可用。在这种情况下,每次只有一个线程可以向前执行。
互斥变量是用pthread_mutex_t数据类型表示的。在使用互斥变量之前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以调用pthread_mutex_init函数进行初始化。如果动态分配互斥量(例如,通过调用malloc函数),在释放内存前需要调用pthread_mutex_destroy。
|
|
如果使用默认初始化互斥量,只需把attr设置为NULL;
对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将会阻塞到互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock。
|
|
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁(如果互斥量被加锁,函数返回EBUSY,线程不会被阻塞)。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则失败不能锁住互斥量,而返回EBUSY;使用pthread_mutex_trylock()接口可以避免死锁。
示例
使用互斥量来实现三个线程分别打印出ABC,循环五次。 主要思路如下
|
|
|
|
|
|
注意:pthread_mutex_lock和pthread_mutex_unlock必须成对出现,不然pthread_mutex_destory函数调用出错。
避免死锁
1.线程A试图对用一个互斥量mutexA加锁两次,那么它自身就会陷入死锁状态,
用伪代码表示就是:
|
|
2.程序中使用多个互斥量时,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时就发生死锁。因为两个线程都在互相请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是产生死锁。
用伪代码表示就是:
|
|
有时候,应用程序的结构使得对互斥量进行排序是很困难的。如果涉及了太多的锁和数据结构,可用的函数并不能把它转换成简单的层次,那么就需要采用另外的方法。在这种情况下,可以先释放占有的锁,然后过段时间再试。这种情况可以使用pthread_mutex_trylock接口避免死锁。如果已经占有某些锁而且pthread_mutex_trylock接口返回成功,那么就可以前进。但是,如果不能获取锁,可以先释放已经占有的锁,做好清理工作,然后过一段时间再重新试。
函数pthread_mutex_timedlock
当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock函数与pthread_mutex_lock是基本等价的,但是在达到超时时间值时,pthread_mutex_timelock不会对互斥量进行加锁,而是返回错误码ETIMEDOUT。
|
|
超时指定愿意等待的绝对时间(某个具体的时间)(与相对时间对比而言,指定在时间X之前可以阻塞等待,而不是说愿意阻塞Y秒)。这个超时时间是用timespec结构来表示,它用秒和纳秒来描述时间。
示例
下面示例给出了如何用pthread_mutex_timedlock避免永久阻塞。
|
|
|
|