第11、12章 线程及其控制
主要内容
互斥量
非递归互斥量
递归互斥量
读写锁
条件变量
自旋锁
屏障
互斥量
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
//也可以把互斥量设置为常量PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量)进行初始化。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//如果互斥量处于未锁住状态,则锁住互斥量,否则返回EBUSY。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
pthread_mutex_timedlock
允许绑定线程阻塞时间,如果超过设定的时间点,pthread_mutex_timedlock
不会对互斥量进行加锁,而是返回错误码ETIMEDOUT
, 注意tsptr表示的是某个时间点,而不是一段时间长度。
产生死锁的四个条件
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的互斥资源保持不放
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
预防死锁的几个方法:
对于每个进程,将其需要的所有资源一次性分配,若失败,系统收回其保持的所有资源(对应第二个条件)
每个进程或线程申请资源时,按照相同的顺序(对应第四个条件)
互斥量属性
主要内容
-
进程共享属性
PTHREAD_PROCESS_PRIVATE
(默认)
PTHREAD_PROCESS_SHARED
-
健壮属性
PTHREAD_MUTEX_STALLED
(默认)
PTHREAD_MUTEX_ROBUST
类型属性
PTHREAD_MUTEX_NORMAL
默认
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
(递归锁)
PTHREAD_MUTEX_DEFAULT
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
pshared可以设置为两个值:PTHREAD_PROCESS_PRIVATE(默认),PTHREAD_PROCESS_SHARED
(从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些线程的同步)
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,int robust);
PTHREAD_MUTEX_STALLED: 持有互斥量的进程终止时不需要采取特别的动作;
PTHREAD_MUTEX_ROBUST: 当进程P1的T1线程或其他Pi进程持有锁且终止时没有释放锁时,P1中的T2线程此时利用
ptread_mutex_lock
函数获取锁时, T2线程不会阻塞且成功获取锁,但返回的值是EOWNERDEAD而不是0
注意: 当同一个进程内,如果一个线程退出时没有释放非健壮性锁,那么其他的线程会被阻塞,而不是返回错误!!!
int pthread_mutex_consistent(pthread_mutex_t *mutex);
pthread_mutex_lock返回EOWNERDEAD时,需要调用 pthread_mutex_consistent 函数来清除这种状态
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);
四种类型
[Figure 0]
情景一
使用递归锁往往不是一个好主意,特别是和条件变量一起使用时。如下图所示,若线程A在第a步之前已经对mutex上锁
那么第b.1步并没有真正解锁,当执行到b.2时,线程A休眠等待线程B的信号,但是线程B第a步无法获取mutex,则产生了死锁
[Figure 1]
情景二
若mutex不是递归锁,当func1调用func2时,显然会产生死锁。
[Figure 2]
在不利用递归锁情况下,可以采取如下方法避免死锁:
1.修改func2接口,指示mutex是否已经被调用者func1锁定,当锁定时,显然无须再次上锁.
2.生成一个func2_locked函数,相比func2, func2_locked无须第a步和第c步,然后,将func1和func2的第b步替换为func2_locked函数
读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrwrock(pthread_rwlock_t *rwlock);
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个所加锁的线程都会被阻塞。
当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,
但是任何希望以写模式对此进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止
读写锁属性
读写锁的唯一属性是进程共享属性
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,
int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);
条件变量
建立在互斥量上的同步: 先来先得(FCFS),先获取锁的线程或进程解锁时,唤醒阻塞的相关线程或进程
建立在条件变量上的同步:人为设定先后执行关系,如Figure 1所示,线程A的执行取决于线程B的唤醒
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
如果超过到期时条件还是没有出现,则函数重新获取互斥量,然后返回ETIMEDOUT
int pthread_cond_signal(pthread_cond_t *cond);
至少能唤醒一个等待该条件的线程,
int pthread_cond_broadcast(pthread_cond_t *cond);
能唤醒等待该条件的所有线程。
代码展示:
注意 “while(workq == NULL)” 的作用非常重要:
1)是否可以用 if 取代 while :
若条件变量qready只是和变量workq对应,那么可以替换。若qready还和其他变量(比如 workq2)对应,显然不行,
因为线程被唤醒时便跳出if语句,继续执行。但是这个线程被唤醒的原因可能是另一个线程修改了workq2后调用pthread_cond_signal.
2)若条件变量qready只是和变量workq对应,那么“while(workq == NULL)”是否可以删除:
绝对不能,考虑若enqueue_msg先被一个线程执行完毕(但此时没有线程被唤醒),接着另外一个线程执行缺少“while(workq == NULL)”语句的process_msg函数时就会产生死锁。
-------------------------------------------[Figure 3]----------------------------------------------
自旋锁
自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。
自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。
int pthread_spin_init(pthread_spinlock_t *lock,int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
若无法获取锁,立即返回EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock);
屏障
屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程阻塞,直到所有相关线程到达一点
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr,
unsigned int count);
最多有count - 1个线程阻塞
int pthread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_wait(pthread_barrier_t *barrier);
屏障属性
屏障属性只有进程共享属性
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,
int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,int pshared)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。