int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
pthread_mutexattr_setpshared 可用来设置互斥锁 变量的 作用域。
pthread_mutexattr_getpshared可用来返回由 pthread_mutexattr_setpshared() 定义的互斥锁 变量 的范围
语法
int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);
int pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr, int *pshared);
pthread_mutexattr_t mattr;
int ret;
ret = pthread_mutexattr_init(&mattr);/* * resetting to its default value: private */
ret = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_PRIVATE);
ret = pthread_mutexattr_getpshared(&mattr, &pshared); 此函数可为属性对象 mattr 获取 pshared 的当前值。
互斥锁 变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享互斥锁,可以在 共享内存中创建互斥锁,并将 pshared属性设置为 PTHREAD_PROCESS_SHARED。 此行为与最初的 Solaris 线程实现中 mutex_init()中的 USYNC_PROCESS 标志等效。
如果互斥锁的 pshared属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。
7、设置协议
pthread_mutexattr_setprotocol 可用来设置互斥锁属性对象的协议属性。
pthread_mutexattr_getprotocol 可用来获取互斥锁属性对象的协议属性。
语法
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *mattr, int protocol);
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);
protocol 可定义应用于互斥锁属性对象的协议。
pthread.h 中定义的 protocol 可以是以下值之一:PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT。
PTHREAD_PRIO_NONE
线程的优先级和调度不会受到互斥锁拥有权的影响。
PTHREAD_PRIO_INHERIT
此协议值(如 thrd1) 会影响线程的优先级和调度 。如果更高优先级的线程因 thrd1 所拥有的一个或多个互斥锁而被阻塞,而这些互斥锁是用 PTHREAD_PRIO_INHERIT 初始化的,则 thrd1 将以高于它的优先级或者所有正在等待这些互斥锁(这些互斥锁是 thrd1 指所拥有的互斥锁)的线程的最高优先级运行。
如果 thrd1 因另一个线程 (thrd3) 拥有的互斥锁而被阻塞,则相同的优先级继承效应会以递归方式传播给 thrd3。
使用 PTHREAD_PRIO_INHERIT 可以 避免优先级倒置 。如果没有优先级继承,底优先级的线程可能会在很长一段时间内都得不到调度,而这会导致等待低优先级线程锁持有的锁的高优先级线程也等待很长时间(因为低优先级线程无法运行,因而就无法释放锁,所以高优先级线程只能继续阻塞在锁上)使用优先级继承可以短时间的提高低优先级线程的优先级,从而使它可以尽快得到调度,然后释放锁。低优先级线程在释放锁后就会恢复自己的优先级。
当前线程已经拥有互斥锁,如果定义了 _POSIX_THREAD_PRIO_INHERIT 符号,则会使用协议属性值 PTHREAD_PRIO_INHERIT 对互斥锁进行初始化。属主失败时的行为取决于pthread_mutexattr_setrobust_np()的 robustness 参数的值。
解除锁定互斥锁。
互斥锁的下一个属主将获取该互斥锁,并返回错误 EOWNERDEAD。
互斥锁的下一个属主会尝试使该互斥锁所保护的状态一致。如果上一个属主失败,则状态可能会不一致。如果属主成功使状态保持一致,则可针对该互斥锁调用 pthread_mutex_init()并解除锁定该互斥锁。
注 –如果针对以前初始化的但尚未销毁的互斥锁调用pthread_mutex_init(),则该互斥锁不会重新初始化。
如果属主无法使状态保持一致, 请勿调用 pthread_mutex_init(),而是解除锁定该互斥锁。在这种情况下,所有等待的线程都将被唤醒。以后对 pthread_mutex_lock()的所有调用将无法获取互斥锁,并将返回错误代码 ENOTRECOVERABLE。现在,通过调用 pthread_mutex_destroy()来取消初始化该互斥锁,即可使其状态保持一致。调用 pthread_mutex_init()可重新初始化互斥锁。
如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主将获取该锁及错误代码 EOWNERDEAD。
PTHREAD_PRIO_PROTECT
此协议值 会影响其他线程(如 thrd2)的优先级和调度 。thrd2 以其较高的优先级或者以 thrd2 拥有的所有互斥锁的最高优先级上限运行。基于被 thrd2 拥有的任一互斥锁阻塞的较高优先级线程对于 thrd2 的调度没有任何影响。
如果某个线程调用sched_setparam()来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。
一个 线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。
PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用实时调度策略SCHED_FIFO 或 SCHED_RR的优先级进程内可用。
pthread_mutexattr_setprotocol 返回值
如果成功完成, pthread_mutexattr_setprotocol()会返回 0。其他任何返回值都表示出现了错误。
如果出现以下任一情况, pthread_mutexattr_setprotocol()将失败并返回对应的值。
8、设置上限
pthread_mutexattr_setprioceiling 可用来设置互斥锁属性对象的优先级上限属性。
pthread_mutexattr_getprioceiling 可用来获取互斥锁属性对象的优先级上限属性。
pthread_mutexattr_setprioceiling 语法
int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *mattr, int prioceiling, int *oldceiling);
int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *mattr, int *prioceiling);
prioceiling 指定已初始化互斥锁的优先级上限。 优先级上限定义执行互斥锁保护的临界段时的最低优先级 。prioceiling 位于 SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。 可锁定互斥锁(如果未锁定的话),或者一直处于 阻塞状态
,直到 pthread_mutex_setprioceiling() 成功锁定该互斥锁,更改该互斥锁的优先级上限并将该互斥锁释放为止。锁定互斥锁的过程无需遵循优先级保护协议。
如果 pthread_mutex_setprioceiling()成功,则将在 old_ceiling 中返回以前的优先级上限值。如果 pthread_mutex_setprioceiling()失败,则互斥锁的优先级上限保持不变。
oldceiling 包含以前的优先级上限值。
注 –仅当定义了 _POSIX_THREAD_PRIO_PROTECT 符号时,attr 互斥锁属性对象才会包括优先级上限属性。
pthread_mutexattr_setprioceiling 返回值
如果成功完成, pthread_mutexattr_setprioceiling()会返回 0。其他任何返回值都表示出现了错误。
9、强健属性
robustness 定义在互斥锁的持有者“死亡”时的行为
np后缀的,表示not portable,就是不可移值的意思,这些函数是一些系统自己实现的,而不是POSIX标准。
pthread_mutexattr_setrobust_np 可用来设置互斥锁属性对象的强健属性。
pthread_mutexattr_setrobust_np 语法
int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *mattr, int *robustness);
注 –仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_setrobust_np()才适用。
robustness 定义在互斥锁的属主失败时的行为。pthread.h 中定义的 robustness 的值为 PTHREAD_MUTEX_ROBUST_NP 或 PTHREAD_MUTEX_STALLED_NP。缺省值为 PTHREAD_MUTEX_STALLED_NP。
PTHREAD_MUTEX_ROBUST_NP
如果互斥锁的持有者死亡,则以后对 pthread_mutex_lock() 的所有调用将以不确定的方式被阻塞。
PTHREAD_MUTEX_STALLED_NP
如果互斥锁的持有者“死亡”了,或者持有这样的互斥锁的进程unmap了互斥锁所在的共享内存或者持有这样的互斥锁的进程执行了exec调用,则会解除锁定该互斥锁。互斥锁的下一个持有者将获取该互斥锁,并返回错误 EOWNWERDEAD。
注 – 如果互斥锁具有PTHREAD_MUTEX_ROBUST_NP的属性,则应用程序在获取该锁时必须检查 pthread_mutex_lock 的返回代码看获取锁时是否返回了EOWNWERDEAD错误。
互斥锁的新属主应使该互斥锁所保护的状态保持一致。如果上一个属主失败,则互斥锁状态可能会不一致。
如果新属主能够使状态保持一致,请针对该互斥锁调用pthread_mutex_consistent_np(),并解除锁定该互斥锁。
如果新属主无法使状态保持一致,请勿针对该互斥锁调用pthread_mutex_consistent_np(),而是解除锁定该互斥锁。
所有等待的线程都将被唤醒,以后对pthread_mutex_lock()的所有调用都将无法获取该互斥锁。 返回代码为 ENOTRECOVERABLE,就意味这这个锁不能被使用了。通过调用pthread_mutex_destroy()取消对互斥锁的初始化,并调用pthread_mutex_int()重新初始化该互斥锁,可使该互斥锁保持一致。
如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主获取该锁时将返回代码 EOWNERDEAD。
pthread_mutexattr_setrobust_np 返回值
如果成功完成, pthread_mutexattr_setrobust_np()会返回 0。其他任何返回值都表示出现了错误。
如果出现以下任一情况, pthread_mutexattr_setrobust_np()将失败并返回对应的值。
10、互斥锁的相关实现与效率问题
互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁。
二、读写锁
读写锁实际是一种特殊的 自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于 自旋锁而言,能提高 并发性,因为在 多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
在读写锁保持期间也是抢占失效的。
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
1、特性
一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁. 正是因为这个特性,
当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.
当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的 线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到知道所有的线程释放锁.
通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞.
2、适用性
读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁.
3、初始化和销毁
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分配的资源.
4、读和写
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个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作, 同样, 非阻塞的函数为:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
非阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.
三、条件变量
1、特性
不同于互斥锁,互斥锁主要用于上锁,而条件变量用于等待。它特别适合需要进行同步的问题,比如线程A,B存在依赖关系,B要在某个条件发生之后才能继续执行,而这个条件只有A才能满足,这个时候就可以使用条件变量来完成这个事情:
- 创建和该条件相关联的条件变量,并初始化它
- 对于线程A来说,它需要做的是设置这个条件,通知等待在相关联条件变量上的线程
- 对于线程B来说,它需要做的是检查这个条件,如果不满足自己的要求,就阻塞在相关联的条件变量上
由于条件变量并没有包含任何需要检测的条件的信息,因而对这个条件需要用其它方式来保护,所以条件变量需要和互斥锁一起使用,每个条件变量总是有一个互斥锁和其关联。如果线程未持有与条件相关联的互斥锁,则调用pthread_cond_signal或pthread_cond_broadcast会产生唤醒丢失错误,满足以下所有条件时,即会出现唤醒丢失问题:
- 一个线程调用 pthread_cond_signal或 pthread_cond_broadcast
- 另一个线程已经测试了该条件,但是尚未调用 pthread_cond_wait
- 没有正在等待的线程,因而pthread_cond_signal或 pthread_cond_broadcast的唤醒将无法起作用,该唤醒会被丢失
对比下信号,信号可以做到通知其它线程某件事发生了,接收信号的线程只需要注册一个信号处理函数,然后信号发生后该处理函数就会被系统调用,一旦该函数被调用了就意味着注册时关联的信号所代表的事情发生了。但要注意:
- POSXI要求多线程应用中信号处理程序必须在应用的多个线程之间共享(即在一个进程的多个线程之间共享),因而对于同一个进程中的多个线程来说它们必须共享信号处理程序,信号处理程序无法确定信号是被发给谁的
- 使用信号时只需要注册信号处理程序即可,不需要创建某种同步对象,而使用条件变量需要创建同步对象,如果要在进程间进行同步和互斥还对条件变量的作用域和属性有要求
- 有权限的任何用户的任何程序都可以发送信号给一个线程,而使用条件变量时,相关的线程必须可以访问同步对
举例
设有两个共享的变量 x 和 y,通过互斥量 mut 保护,当 x > y 时,条件变量 cond 被触发。先执行func1,之后再执行func2,
int x = 0,y = 5;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *func1(void *arg)
{
pthread_mutex_lock(&mut);
while (x <= y) {
pthread_cond_wait(&cond, &mut);
}
/* 对 x、y 进行操作 */
pthread_mutex_unlock(&mut);
}
void *func2(void *arg)
{
pthread_mutex_lock(&mut);
x = 5;
y = 0;
if (x > y)
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
}
2、初始化
如果条件变量变量是静态的则可以直接用PTHREAD_COND_INITIALIZER来初始化它,
比如:static pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER
如果条件变量是动态分配的,则必须在使用它之前用pthread_cond_init来初始化它。pthread_cond_init用来初始化cv所指向的条件变量,如果cattr为NULL则会用缺省的属性初始化条件变量;否则使用cattr指定的属性初始化条件变量。使用PTHREAD_COND_INITIALIZER 宏与动态分配具有null 属性的 pthread_cond_init()等效,不同之处在于PTHREAD_COND_INITIALIZER 宏不进行错误检查。
多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。
3、条件变量的阻塞
pthread_cond_wait以原子方式释放mutex所指向的互斥锁,并导致调用线程阻塞在cv所指向的条件变量上。阻塞的线程可以通过如下方式被唤醒:
- 由pthread_cond_signal唤醒
- 由pthread_cond_broadcast唤醒
- 由信号唤醒
pthread_cond_wait返回时,由mutex指定的互斥锁被锁定并且被调用线程锁持有,即使返回错误时也是如此。
pthread_cond_wait在被唤醒之前将一致保持阻塞状态。它会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁 。
通常情况下对条件表达式的检查是在互斥锁的保护下进行的。如果条件表达式为假,线程就会基于条件变量阻塞。然后,当其它线程更改条件值时,就会唤醒它(通过pthread_cond_signal或pthread_cond_broadcast)。 这种变化会导致至少一个正在等待该条件的线程解除阻塞并尝试再次获取互斥锁。
必须重新测试导致等待的条件,然后才能从 pthread_cond_wait处继续执行 。唤醒的线程重新获取互斥锁并从pthread_cond_wait返回之前,条件可能会发生变化。等待线程锁等待的条件可能并未真正发生。通常使用条件变量的方式如下:
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
pthread_cond_wait是一个取消点。如果有一个未决的取消请求并且该线程启用了取消功能,则该线程会被终止并在继续持有锁的状态下开始执行的清理处理函数。如果清理处理函数中未释放锁,则就会出现线程终止但是未释放锁的情形。
4、解除阻塞
pthread_cond_signal解除阻塞在该条件变量上的一个线程的阻塞状态。
应在互斥锁的保护下修改相关条件,该互斥锁应该是与该条件变量相关联的那个互斥锁(即调用wati时指定的那个互斥锁)。否则,可能在条件变量的测试和pthread_cond_wait阻塞之间修改该变量,这会导致无限期等待。
如果有多个线程在等待一个条件变量,则线程被唤醒的顺序由所采用的调度策略决定。
- 如果使用的是默认的调度策略,即SCHED_OTHER,则无法保证被唤醒的顺序
- 如果使用的是SCHED_FIFO 或SCHED_RR,则线程按照优先级被唤醒
如果没有任何线程基于条件变量阻塞,则调用 pthread_cond_signal不起作用。
5、指定时间内解除阻塞
pthread_cond_timedwait的用法与 pthread_cond_wait的用法基本相同,区别在于在由abstime指定的时间之后不再被阻塞。
pthread_cond_reltimedwait_np与pthread_cond_timedwait基本相同,它们唯一的区别在于pthread_cond_reltimedwait_np使用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。
类似于pthread_cond_wait,pthread_cond_reltimedwait_np和pthread_cond_timedwait也是取消点。
6、解除所有阻塞