POSIX 线程详解(3-互斥量:"固定加锁层次"/“试加锁-回退”)

有时一个互斥量是不够的:

比如:

当多个线程同时访问一个队列结构时,你需要2个互斥量,一个用来保护队列头,一个用来保护队列元素内的数据。

当为多线程建立一个树结构时,你可能需要为每个节点设置一个互斥量。

同时使用多个互斥量会导致复杂度的增加

最坏的情况就是死锁的发生,即两个线程分别锁住一个互斥量而等待对方的互斥量。

多互斥量可能导致死锁:

如果可以在独立的数据上使用两个分离的互斥量,那么就应该这么做。这样,通过减少线程必须等待其他线程完成数据操作的时间。

如果数据独立,则某个特定函数就不太可能经常需要同时加锁两个互斥量。

如果数据不是完全独立的时候,情况就复杂了。

如果你的程序中有一个不变量,影响着由两个互斥量保护的数据。

即使该不变量很少被改变或引用,你迟早需要编写同时锁住两个互斥量的代码,来确保不变量的完整性。

一个经典的死锁现象

如果一个线程锁住互斥量A后加锁互斥量B。同时另一个线程锁住互斥量B后加锁互斥量A。

这样的代码就是一个经典的死锁现象。

两个线程可能同时完成第一步。

即使是在但处理器系统中,一个线程完成了第一步后可能被时间片机制抢占,以使另一个线程完成第一步。

至此两个线程都无法完成第二步,因为他们彼此等待的互斥量已经被对方锁住。

针对上述类型的死锁,可以考虑一下两种通用的解决方法:

1、固定加锁层次

比如,所有需要同时加锁互斥量A和互斥量B的代码,必须先加锁互斥量A,再加锁互斥量B。

2、试加锁和回退

在锁住某个集合中的第一个互斥量后,使用pthread_mutex_trylock来加锁集合中的其他互斥量。

如果失败则将集合中所有已加锁的互斥量释放,并重新加锁。

固定加锁层次详解:

有许多方式定义固定加锁层次,但对于特定的互斥量,总有某个明显的加锁顺序。

例如:

如果有两个互斥量,一个保护队列头,一个保护队列元素内的数据,

则很显然的一种固定加锁层次就是先将队列头互斥量加锁,然后再加锁另一个互斥量。

如果互斥量间不存在明显的逻辑层次,则可以建立任意的固定加锁层次。

例如:

你可以创建这样一个加锁互斥量集合的函数。

将集合中的互斥量照ID地址顺序排列,并以此顺序加锁互斥量。

或者给每个互斥量指派名字,然后按照字母顺序加锁。

或者给每个互斥量指派序列号,然后按照数字顺序加锁。

从某种程度上讲,只要总是保持相同的顺序,顺序本身就并不真正重要。

试加锁和回退详解:

回退的方式没有固定加锁层次有效,它会浪费时间来试锁和回退。

另一方面,你也不必定义和遵循严格的固定加锁层次,这使得回退的方法更为灵活。

可以组合两种算法来最小化回退的代价。

即在定义良好的代码区遵循固定加锁层次,在更灵活的地方使用试加锁-回退。

试加锁和回退的代码示例:

[cpp] view
plain
copy

  1. #include <pthread.h>
  2. #include <sched.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. //====================================================
  9. #define ITERATIONS 10
  10. //====================================================
  11. //互斥量数组
  12. pthread_mutex_t mutex[3]=
  13. {
  14. PTHREAD_MUTEX_INITIALIZER,
  15. PTHREAD_MUTEX_INITIALIZER,
  16. PTHREAD_MUTEX_INITIALIZER
  17. };
  18. //====================================================
  19. //是否开启试加锁-回退模式
  20. int backoff=1;
  21. /*
  22. 该标识符决定的操作:
  23. 当它>0,线程会在锁住每个互斥量后调用sched_yield,以确保其他线程有机会运行
  24. 当它<0,线程则在锁住每个互斥量后睡眠1秒,以确保其他线程真正有机会运行
  25. */
  26. int yield_flag = 0;
  27. //====================================================
  28. void *lock_forward (void *arg)
  29. {
  30. int i, iterate, backoffs;
  31. int status;
  32. //循环ITERATIONS次
  33. for (iterate = 0; iterate < ITERATIONS; iterate++)
  34. {
  35. //记录回退的次数
  36. backoffs = 0;
  37. //按0、1、2的顺序对3个互斥量加锁
  38. for (i = 0; i < 3; i++)
  39. {
  40. //按正常的方法加锁第一个互斥量
  41. if (i == 0)
  42. {
  43. status = pthread_mutex_lock (&mutex[i]);
  44. if (status != 0)
  45. {
  46. printf("First lock error\n");
  47. //终止异常程序
  48. abort();
  49. }
  50. }
  51. /*
  52. 对于第2、3个互斥量
  53. 如果开启了试加锁模式,就执行试加锁
  54. 否则按照正常模式加锁
  55. */
  56. else
  57. {
  58. if (backoff)
  59. status = pthread_mutex_trylock (&mutex[i]);
  60. else
  61. status = pthread_mutex_lock (&mutex[i]);
  62. //如果是试加锁失败,则回退
  63. if (status == EBUSY)
  64. {
  65. //回退次数++
  66. backoffs++;
  67. printf( "[forward locker backing off at %d]\n",i);
  68. //将之前加锁的互斥量释放掉
  69. for (; i >= 0; i--)
  70. {
  71. status = pthread_mutex_unlock (&mutex[i]);
  72. if (status != 0)
  73. {
  74. printf("Backoff error\n");
  75. //终止异常程序
  76. abort();
  77. }
  78. }
  79. }
  80. else
  81. {
  82. if (status != 0)
  83. {
  84. printf("Lock mutex error\n");
  85. //终止异常程序
  86. abort();
  87. }
  88. printf("forward locker got %d\n",i);
  89. }
  90. }
  91. /*
  92. 根据yield_flag决定是睡1秒还是调用sched_yield ()
  93. */
  94. if (yield_flag)
  95. {
  96. if (yield_flag > 0)
  97. sched_yield ();
  98. else
  99. sleep (1);
  100. }
  101. }
  102. //显示加锁情况
  103. printf ("lock forward got all locks, %d backoffs\n", backoffs);
  104. //全部解锁
  105. pthread_mutex_unlock (&mutex[2]);
  106. pthread_mutex_unlock (&mutex[1]);
  107. pthread_mutex_unlock (&mutex[0]);
  108. sched_yield ();
  109. }
  110. return NULL;
  111. }
  112. //====================================================
  113. void *lock_backward (void *arg)
  114. {
  115. int i, iterate, backoffs;
  116. int status;
  117. //循环ITERATIONS次
  118. for (iterate = 0; iterate < ITERATIONS; iterate++)
  119. {
  120. //记录回退的次数
  121. backoffs = 0;
  122. //按2、1、0的顺序对3个互斥量加锁
  123. for (i = 2; i >= 0; i--)
  124. {
  125. //按正常的方法加锁第一个互斥量
  126. if (i == 2)
  127. {
  128. status = pthread_mutex_lock (&mutex[i]);
  129. if (status != 0)
  130. {
  131. printf("First lock error\n");
  132. //终止异常程序
  133. abort();
  134. }
  135. }
  136. /*
  137. 对于第2、3个互斥量
  138. 如果开启了试加锁模式,就执行试加锁
  139. 否则按照正常模式加锁
  140. */
  141. else
  142. {
  143. if (backoff)
  144. status = pthread_mutex_trylock (&mutex[i]);
  145. else
  146. status = pthread_mutex_lock (&mutex[i]);
  147. //如果是试加锁失败,则回退
  148. if (status == EBUSY)
  149. {
  150. //回退次数++
  151. backoffs++;
  152. printf( "[backward locker backing off at %d]\n",i);
  153. //将之前加锁的互斥量释放掉
  154. for (; i < 3; i++)
  155. {
  156. status = pthread_mutex_unlock (&mutex[i]);
  157. if (status != 0)
  158. {
  159. printf("Backoff error\n");
  160. //终止异常程序
  161. abort();
  162. }
  163. }
  164. }
  165. else
  166. {
  167. if (status != 0)
  168. {
  169. printf("Lock mutex error\n");
  170. //终止异常程序
  171. abort();
  172. }
  173. printf( "backward locker got %d\n",i);
  174. }
  175. }
  176. /*
  177. 根据yield_flag决定是睡1秒还是调用sched_yield ()
  178. */
  179. if (yield_flag)
  180. {
  181. if (yield_flag > 0)
  182. sched_yield ();
  183. else
  184. sleep (1);
  185. }
  186. }
  187. //显示加锁情况
  188. printf ("lock backward got all locks, %d backoffs\n", backoffs);
  189. //全部解锁
  190. pthread_mutex_unlock (&mutex[0]);
  191. pthread_mutex_unlock (&mutex[1]);
  192. pthread_mutex_unlock (&mutex[2]);
  193. sched_yield ();
  194. }
  195. return NULL;
  196. }
  197. //====================================================
  198. int main (int argc, char *argv[])
  199. {
  200. pthread_t forward, backward;
  201. int status;
  202. //手动设置是否开启回退模式
  203. if (argc > 1)
  204. backoff = atoi (argv[1]);
  205. //手动设置是否沉睡或调用sched_yield()
  206. if (argc > 2)
  207. yield_flag = atoi (argv[2]);
  208. //开启lock_forward线程,按0、1、2的顺序加锁互斥量
  209. status = pthread_create (&forward, NULL, lock_forward, NULL);
  210. if (status != 0)
  211. {
  212. printf("Create forward error\n");
  213. //终止异常程序
  214. abort();
  215. }
  216. //开启lock_forward线程,按2、1、0的顺序加锁互斥量
  217. status = pthread_create (&backward, NULL, lock_backward, NULL);
  218. if (status != 0)
  219. {
  220. printf("Create backward error\n");
  221. //终止异常程序
  222. abort();
  223. }
  224. pthread_exit (NULL);
  225. }

代码结果:

[[email protected] ~]$ ./backoff

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

[backward locker backing off at 0]

backward locker got 1

backward locker got 0

lock backward got all locks, 1 backoffs

[forward locker backing off at 1]

forward locker got 1

forward locker got 2

lock forward got all locks, 1 backoffs

backward locker got 1

[backward locker backing off at 0]

backward locker got 1

backward locker got 0

lock backward got all locks, 1 backoffs

[forward locker backing off at 1]

forward locker got 1

forward locker got 2

lock forward got all locks, 1 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

[forward locker backing off at 2]

forward locker got 1

forward locker got 2

lock forward got all locks, 1 backoffs

[backward locker backing off at 1]

backward locker got 1

backward locker got 0

lock backward got all locks, 1 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

forward locker got 1

forward locker got 2

lock forward got all locks, 0 backoffs

backward locker got 1

backward locker got 0

lock backward got all locks, 0 backoffs

[[email protected] ~]$

代码分析:

上述代码演示了如何使用回退算法避免互斥量死锁

程序建立了2个线程,一个运行函数lock_forward,一个运行lock_backward。

每个线程重复循环ITERATIONS,每次循环两个线程都试图以此锁住三个互斥量

lock_forward线程先锁住互斥量0,再锁住互斥量1,再锁住互斥量2。

lock_backward线程线索住互斥量2,再锁住互斥量1,再锁住互斥量0。

如果没有特殊的防范机制,则上述程序很快进入死锁状态。你可以通过[[email protected] ~]$ ./backoff 0来查看死锁的效果。

如果开启了试加锁模式

则两个线程都将调用pthread_mutex_trylock来加锁每个互斥量。

当加锁互斥量返回失败信息EBUSY时,线程释放所有现有的互斥量并重新开始。

在某些系统中,可能不会看到任何互斥量的冲突

因为一个线程总是能够在另一个线程有机会加锁互斥量之前锁住所有互斥量。

可以设置yield_flag变量来解决这个问题。

在多处理器系统中,当将yield_flag设置为非0值时,通常会看到更多的回退操作。

线程按照加锁的相反顺序释放所有锁

这是用来避免线程中的不必要的回退操作。

如果你使用“试加锁和回退”算法,你应该总是以相反的顺序解锁互斥量

POSIX 线程详解(3-互斥量:"固定加锁层次"/“试加锁-回退”),布布扣,bubuko.com

时间: 2024-08-17 12:24:06

POSIX 线程详解(3-互斥量:"固定加锁层次"/“试加锁-回退”)的相关文章

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

POSIX 线程详解(1-概述)

线程是有趣的 线程类似于进程.如同进程,线程由内核按时间分片进行管理.在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同.而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行. 那么为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间.不同的线程可以存取内存中的同一个变量.所以,程序中的所有线程都可以读或写声明过的全局变量.如果曾用 fork() 编写过重要代码,就会认识到这个工具的重要性.为什么呢?虽然 fork() 允

POSIX 线程详解(2-线程创建和销毁)

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

POSIX 线程具体解释(3-相互排斥量:&quot;固定加锁层次&quot;/“试加锁-回退”)

有时一个相互排斥量是不够的: 比方: 当多个线程同一时候訪问一个队列结构时,你须要2个相互排斥量,一个用来保护队列头,一个用来保护队列元素内的数据. 当为多线程建立一个树结构时.你可能须要为每一个节点设置一个相互排斥量. 同一时候使用多个相互排斥量会导致复杂度的添加 最坏的情况就是死锁的发生.即两个线程分别锁住一个相互排斥量而等待对方的相互排斥量. 多相互排斥量可能导致死锁: 假设能够在独立的数据上使用两个分离的相互排斥量,那么就应该这么做. 这样,通过降低线程必须等待其它线程完毕数据操作的时间

Linux中-POSIX 线程详解

一种支持内存共享的简捷工具   摘自https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/ 线程是有趣的 了解如何正确运用线程是每一个优秀程序员必备的素质.线程类似于进程.如同进程,线程由内核按时间分片进行管理.在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同.而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行. 那么为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

python线程详解

#线程状态 #线程同步(锁)#多线程的优势在于可以同时运行多个任务,至少感觉起来是这样,但是当线程需要共享数据时,可能存在数据不同步的问题. #threading模块#常用方法:'''threading.currentThread():返回当前的线程变量threading.enumerate():返回一个包含正在运行的线程的list,正在运行指:线程启动后,结束前,不包含启动前和终止后的线程threading.activeCount():返回正在运行的线程数量,与len(threading.en

线程同步方式之互斥量Mutex

互斥量和临界区非常相似,只有拥有了互斥对象的线程才可以访问共享资源,而互斥对象只有一个,因此可以保证同一时刻有且仅有一个线程可以访问共享资源,达到线程同步的目的. 互斥量相对于临界区更为高级,可以对互斥量进行命名,支持跨进程的线程同步.互斥量是调用的Win32的API对互斥锁的操作,因此在同一操作系统下不同进程可以按照互斥锁的名称共享锁. 正因为如此,互斥锁的操作会更好资源,性能上相对于临界区也有降低,在使用时还要多斟酌.对于进程内的线程同步使用临界区性能会更佳. 在.Net中使用Mutex类来

线程的同步之互斥量

互斥量: 当多个线程共享相同的内存时,需要每一个线程看到相同的视图.当一个线程修改变量时,而其他线程也可以读取或者修改这个变量,就需要对这些线程同步,确保他们不会访问到无效的变量 在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器的读和写这两个周期交叉时,这种潜在的不一致性就会出现.当然这与处理器相关,但是在可移植的程序中并不能对处理器做出任何假设 为了让线程访问数据不产生冲突,这要就需要对变量加锁,使得同一时刻只有一个线程可以访问变量.互斥量本质就是锁,访问共享资源前对互斥量加锁,访