您的位置:首页 > 运维架构 > Linux

Linux 线程同步与互斥

2013-11-20 14:16 246 查看

简述

线程同步有三种常用的机制: 互斥量(mutex) ,读写锁(rwlock)条件变量(cond).

互斥量:有两种状态:lockunlock, 它确保同一时间只有一个线程访问数据;

读写锁:有三种状态: 读加锁, 写加锁, 不加锁. 只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.

条件变量:则给多个线程提供了一个会合的场所, 与互斥量一起使用时, 允许线程以无竞争的方式等待特定条件的发生.

互斥量

  互斥量从本质上说就是一把锁, 提供对共享资源的保护访问.

初始化:

  在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:

  对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.

  对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.

头文件:

<pthread.h>

原型:

int pthread_mutex_init(pthread_mutex_t *restrictmutex, const pthread_mutexattr_t
*restricattr);

intpthread_mutex_destroy(pthread_mutex_t *mutex);

返回值:

成功则返回0, 出错则返回错误编号.

说明:

如果使用默认的属性初始化互斥量, 只需把attr设为NULL.

互斥操作:

  对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁

原型:

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, 出错则返回错误编号.

说 明:

具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限;

如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态.

死锁:

  死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西.

  总体来讲, 有几个不成文的基本原则:

   a: 对共享资源操作前一定要获得锁.

b: 完成操作以后一定要释放锁.

c: 尽量短时间地占用锁.

d: 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC.

e: 线程错误返回时应该释放它所获得的锁. 读写锁

读写锁

特性:

有3种状态, 所以可以有更高的并行性.

  一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.

  正是因为这个特性,当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.

当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须阻塞直到所有的线程释放锁.

  通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞.

适用性:

  读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁

初始化和销毁:

   #include <pthread.h>

  int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

  int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

成功则返回0, 出错则返回错误编号.

  同互斥量一样, 在释放读写锁占用的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.

读和写:

  #include <pthread.h>

  int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

  int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

  int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

  成功则返回0, 出错则返回错误编号.

这3个函数分别实现获取读锁, 获取写锁和释放锁的操作.

获取锁的两个函数是阻塞操作, 同样, 非阻塞的函数为:

#include <pthread.h>

  int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

  int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  

成功则返回0, 出错则返回错误编号.非阻塞的获取锁操作

如果可以获取则返回0, 否则返回错误的EBUSY.

条件变量

  条件变量分为两部分: 条件变量. 条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量.

初始化:

  条件变量采用的数据类型是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);

  成功则返回0, 出错则返回错误编号.

  当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量

等待条件:

  #include <pthread.h>

  int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);

  int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

  

成功则返回0, 出错则返回错误编号.

  这两个函数分别是阻塞等待和超时等待.

  等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数.

函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.

  注意:pthread_cond_wait函数内部是先解锁,等待一段时间后判断条件是否满足。若满足,则再加锁,执行数据操作; 若不满足,则继续等待。当pthread_cond_wait返回时, 互斥量再次被锁住.

通知条件:

  #include <pthread.h>

  int pthread_cond_signal(pthread_cond_t *cond);

  int pthread_cond_broadcast(pthread_cond_t *cond);

  成功则返回0, 出错则返回错误编号.

  这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号.

  注意, 一定要在改变条件状态以后再给线程发送信号.

举例

设有两个共享的变量 x 和 y,通过互斥量 mut 保护,当 x > y 时,条件变量 cond 被触发。

int x,y;

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

等待直到 x > y 的执行流程:

pthread_mutex_lock(&mut); +

while (x <= y) {

pthread_cond_wait(&cond, &mut);-,+

}

/* 对 x、y 进行操作 */

pthread_mutex_unlock(&mut); -

对 x 和 y 的修改可能导致 x > y,应当触发条件变量:

pthread_mutex_lock(&mut);+

/* 修改 x、y */

if (x > y) pthread_cond_broadcast(&cond);

pthread_mutex_unlock(&mut);-

如果能够确定最多只有一个等待线程需要被唤醒(例如,如果只有两个线程通过 x、y 通信),则使用 pthread_cond_signal 比 pthread_cond_broadcast 效率稍高一些。如果不能确定,应当用 pthread_cond_broadcast。

说明

条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。

条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件

pthread_cond_init 使用 cond_attr 指定的属性初始化条件变量 cond,当 cond_attr 为 NULL 时,使用缺省的属性。LinuxThreads 实现条件变量不支持属性,因此 cond_attr 参数实际被忽略。

pthread_cond_t 类型的变量也可以用 PTHREAD_COND_INITIALIZER 常量进行静态初始化。

pthread_cond_signal 使在条件变量上等待的线程中的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动,但不能指定哪一个

pthread_cond_broadcast 重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。

pthread_cond_wait 自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait 之前,应用程序必须加锁互斥量。pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了 pthread_lock_mutex)。

互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。

pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在 abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁,且 pthread_cond_timedwait 返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970 年 1 月 1 日 00:00:00 GMT。

pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。在 LinuxThreads 的实现中,条件变量不联结资源,除检查有没有等待的线程外,pthread_cond_destroy 实际上什么也不做。

3. 取消

pthread_cond_wait 和 pthread_cond_timedwait 是取消点。如果一个线程在这些函数上挂起时被取消,线程立即继续执行,然后再次对 pthread_cond_wait 和 pthread_cond_timedwait 在 mutex 参数加锁,最后执行取消。因此,当调用清除处理程序时,可确保,mutex 是加锁的。

4. 异步信号安全(Async-signal Safety)

条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: