互斥锁属性详解

互斥锁属性说明

使用互斥锁可以是线程按顺序执行,互斥锁通过确保一次只有一个线程/进程执行代码临界段来同步多个线程/进程。互斥锁还可以保护单线程代码。要更改缺省的互斥锁属性,可以对属性对象进程声明和初始化。

1
2
//互斥锁动态初始化函数原型
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr);

在使用动态互斥锁初始化函数pthread_mutex_init()初始化互斥锁的时候,attr参数指定了新创建互斥锁的属性。如果attr参数为NULL,则默认属性为快速互斥锁。如果要自定义属性则需要使用pthread_mutexattr_init()函数来创建属性。

互斥锁属性操作方法

互斥锁属性对象初始化

1
2
3
4
5
6
//原型: int pthread_mutexattr_init(pthread_mutexattr_t *mattr);
#include <pthread.h>
pthread_mutexattr_t mattr;
int ret;
ret = pthread_mutexattr_init(&mattr);
//成功返回0,其他任何返回值都表示出现了错误

初始化的互斥锁属性默认是PTHREAD_PROCESS_PRIVATE,也就是说只能在当前进程内使用该互斥锁,如果要在共享内存中,多进程使用互斥锁,需要调用pthread_mutexattr_setpshared把属性设置为PTHREAD_PROCESS_SHARED.

互斥锁属性对象销毁

1
2
3
4
5
6
//原型: int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
#include <pthread.h>
pthread_mutexattr_t mattr;
int ret;
ret = pthread_mutexattr_destroy(&mattr);
//成功返回0,其他任何返回值都表示出现了错误

pthread_mutexattr_initpthread_mutexattr_destroy 必须成对出现,不然会产生内存泄露。

设置互斥锁的使用范围

互斥锁变量可以是进程内专用的变量,也可以是系统范围(多进程)的变量,要在多进程内共享互斥锁,可以在共享内存中创建互斥锁,并将pshared属性设置为PTHREAD_PROCESS_SHARED,如果互斥锁的pshared属性设置为PTHREAD_PROCESS_PRIVATE,则只有同一个进程内创建的线程才能处理该互斥锁.

1
2
3
//原型: int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);
 pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
 //成功返回0,其他任何返回值都表示出现了错误

获取互斥锁的使用范围

1
2
3
4
//原型: int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);
 int pshared;
 pthread_mutexattr_getpshared(&mattr,&pshared);
 //成功返回0,其他任何返回值都表示出现了错误

获取/设置互斥锁属性的类型

1
2
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
int pthread_mutexattr_gettype(pthread_mutexattr_t *attr,int *type);  
  • PTHREAD_MUTEX_DEFAULT(缺省的互斥锁类型属性):这种类型的互斥锁不会自动检测死锁。如果一个线程试图对一个互斥锁重复锁定,将会引起不可预料的结果。如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。POSIX标准规定,对于某一具体的实现,可以把这种类型的互斥锁定义为其他类型的互斥锁。

  • PTHREAD_MUTEX_NORMAL:这种类型的互斥锁不会自动检测死锁。如果一个线程试图对一个互斥锁重复锁定,将会引起这个线程的死锁。如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。

  • PTHREAD_MUTEX_ERRORCHECK:这种类型的互斥锁会自动检测死锁。 如果一个线程试图对一个互斥锁重复锁定,将会返回一个错误代码。 如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。

  • PTHREAD_MUTEX_RECURSIVE:如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁。一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的(作用域属性为PTHREAD_PROCESS_PRIVATE)。

锁类型 互斥量类型 没有解锁时再次加锁 不占用时解锁 已解锁时再次解锁
自适应锁 PTHREAD_MUTEX_DEFAULT 死锁 未定义 未定义
普通锁 PTHREAD_MUTEX_NORMAL 返回错误 返回错误 返回错误
纠错锁 PTHREAD_MUTEX_ERRORCHECK 允许 返回错误 返回错误
嵌套锁 PTHREAD_MUTEX_RECURSIVE 未定义 未定义 未定义
锁类型 初始化方式 加解锁特征 调度特征 windows支持 linux支持 OS_APPLE,OS_ANDROID支持
普通锁 PTHREAD_MUTEX_INITIALIZER 同一线程可重复加锁,解锁一次释放锁 先等待锁的进程先获得锁 不支持 支持 支持
嵌套锁 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 同一线程可以多次加锁,解锁相同次数才能释放锁 先等待锁的线程先获取锁 支持 支持 支持
纠错锁 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 同一线程不能重复加锁,加上的锁只能由本线程解锁 先等待锁的进程先获得锁 不支持 支持 支持
自适应锁 PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP 同一线程可重加锁,解锁一次生效 所有等待锁的线程自由竞争

PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP 还没搞清楚。

获取/设置互斥锁属性的协议

1
2
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);  
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);  

互斥锁协议属性的可能值及其含义:

  • PTHREAD_PRIO_NONE:线程的优先级和调度不会受到互斥锁拥有权的影响。

  • PTHREAD_PRIO_INHERIT:当高优先级的等待低优先级的线程锁定互斥量时,低优先级的线程以高优先级线程的优先级运行。这种方式将以继承的形式传递。当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级。(“优先级继承”意味着,当一个线程在由另一个低优先级线程拥有的互斥量上等待时,后者的优先级将被增加到等待线程的优先级.)

  • PTHREAD_PRIO_PROTECT:拥有该类型的互斥量的线程将以自己的优先级和它拥有的互斥量的线程将以自己的优先级和它拥有的互斥量的优先级较高者运行,其他等待该线程拥有的锁得线程对该线程的调度优先级没有影响。

    注意:PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用实时调度策略SCHED_FIFO 或SCHED_RR的优先级进程内可用。 一个线程可以同时拥有多个混合使用PTHREAD_PRIO_INHERIT 和PTHREAD_PRIO_PROTECT协议属性初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。pthread_mutexattr_getprotocol可用来获取互斥锁属性对象的协议属性。

获取/设置互斥锁属性的优先级上限

1
2
3

int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling, int *oldceiling);  
int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, int *prioceiling);  
  • prioceiling指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。oldceiling 用于返回以前的优先级上限值。

  • pthread_mutex_setprioceiling可更改互斥锁mutex的优先级上限prioceiling。

  • pthread_mutex_setprioceiling可锁定互斥锁(如果未锁定的话),或者一直处于阻塞状态,直到它成功锁定该互斥锁,更改该互斥锁的优先级上限并将该互斥锁释放为止。锁定互斥锁的过程无需遵循优先级保护协议。

如果 pthread_mutex_setprioceiling成功,则将在 old_ceiling 中返回以前的优先级上限值。如果pthread_mutex_setprioceiling失败,则互斥锁的优先级上限保持不变。pthread_mutex_getprioceiling会返回mutex 的优先级上限prioceiling。

注意:“优先级上限”协议意味着当一个线程拥有互斥量时,它将以指定的优先级运行。

获取/设置互斥锁的强健属性

1
2
int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, int *robustness);  
int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, int *robustness);  

robustness 定义在互斥锁的持有者“死亡”时的行为。pthread.h 中定义的robustness 的值为PTHREAD_MUTEX_ROBUST_NP 或 PTHREAD_MUTEX_STALLED_NP。缺省值为PTHREAD_MUTEX_STALLED_NP。

  • PTHREAD_MUTEX_STALLED_NP: 如果互斥锁的持有者死亡,则以后对pthread_mutex_lock() 的所有调用将以不确定的方式被阻塞。

  • PTHREAD_MUTEX_ROBUST_NP: 如果互斥锁的持有者“死亡”了,或者持有这样的互斥锁的进程unmap了互斥锁所在的共享内存或者持有这样的互斥锁的进程执行了exec调用,则会解除锁定该互斥锁。互斥锁的下一个持有者将获取该互斥锁,并返回错误EOWNWERDEAD。

如果互斥锁具有PTHREAD_MUTEX_ROBUST_NP的属性,则应用程序在获取该锁时必须检查pthread_mutex_lock 的返回代码看获取锁时是否返回了EOWNWERDEAD错误。如果是,则

  • 互斥锁的新的持有者应使该互斥锁所保护的状态保持一致。因为互斥锁的上一个持有者“死亡”时互斥锁所保护的状态可能出于不一致的状态。
  • 如果互斥锁的新的持有者能够使该状态保持一致,请针对该互斥锁调用pthread_mutex_consistent_np(),并解除锁定该互斥锁。
  • 如果互斥锁的新的持有者无法使该状态保持一致,请勿针对该互斥锁调用pthread_mutex_consistent_np(),而是解除锁定该互斥锁。所有等待的线程都将被唤醒,以后对pthread_mutex_lock() 的所有调用都将无法获取该互斥锁。返回错误为ENOTRECOVERABLE。

如果一个线程获取了互斥锁,但是获取时得到了EOWNERDEAD的错误,然后它终止并且没有释放互斥锁 ,则下一个持有者获取该锁时将返回代码EOWNERDEAD。

注意:

  1. 互斥量需要时间来加锁和解锁。锁住较少互斥量的程序通常运行得更快。所以,互斥量应该尽量少,够用即可,每个互斥量保护的区域应则尽量大。
  2. 互斥量的本质是串行执行。如果很多线程需要频繁地加锁同一个互斥量,则线程的大部分时间就会在等待,这对性能是有害的。如果互斥量保护的数据(或代码)包含彼此无关的片段,则可以特大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待时间就会减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则应尽量的少。
  3. POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。
  4. 线程在加锁后解锁前被取消,锁将永远保持锁定状态。因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。
  5. 锁机制不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。
0%