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

比方:

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

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

同一时候使用多个相互排斥量会导致复杂度的添加

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

多相互排斥量可能导致死锁:

假设能够在独立的数据上使用两个分离的相互排斥量,那么就应该这么做。

这样,通过降低线程必须等待其它线程完毕数据操作的时间。

假设数据独立,则某个特定函数就不太可能常常须要同一时候加锁两个相互排斥量。

假设数据不是全然独立的时候。情况就复杂了。

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

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

一个经典的死锁现象

假设一个线程锁住相互排斥量A后加锁相互排斥量B。同一时候还有一个线程锁住相互排斥量B后加锁相互排斥量A。

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

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

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

至此两个线程都无法完毕第二步,由于他们彼此等待的相互排斥量已经被对方锁住。



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

1、固定加锁层次

比方,全部须要同一时候加锁相互排斥量A和相互排斥量B的代码,必须先加锁相互排斥量A。再加锁相互排斥量B。

2、试加锁和回退

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

假设失败则将集合中全部已加锁的相互排斥量释放,并又一次加锁。

固定加锁层次具体解释:

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

比如:

假设有两个相互排斥量,一个保护队列头。一个保护队列元素内的数据。

则非常显然的一种固定加锁层次就是先将队列头相互排斥量加锁。然后再加锁还有一个相互排斥量。

假设相互排斥量间不存在明显的逻辑层次,则能够建立随意的固定加锁层次。

比如:

你能够创建这样一个加锁相互排斥量集合的函数。

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

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

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

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

试加锁和回退具体解释:

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

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

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

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

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

  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. }

代码结果:

[allyes_op@allyes ~]$ ./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

[allyes_op@allyes ~]$

代码分析:

上述代码演示了怎样使用回退算法避免相互排斥量死锁

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

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

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

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

假设没有特殊的防范机制,则上述程序非常快进入死锁状态。你能够通过[allyes_op@allyes ~]$ ./backoff 0来查看死锁的效果。

假设开启了试加锁模式

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

当加锁相互排斥量返回失败信息EBUSY时,线程释放全部现有的相互排斥量并又一次開始。

在某些系统中,可能不会看到不论什么相互排斥量的冲突

由于一个线程总是可以在还有一个线程有机会加锁相互排斥量之前锁住全部相互排斥量。

能够设置yield_flag变量来解决问题。

在多处理器系统中,当将yield_flag设置为非0值时,一般会看到很多其它的回退操作。

线程依照加锁的相反顺序释放全部锁

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

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

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

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

    相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...

  2. 【C/C++多线程编程之六】pthread相互排斥量

    多线程编程之线程同步相互排斥量       Pthread是 POSIX threads 的简称,是POSIX的线程标准.          Pthread线程同步指多个线程协调地,有序地同步使用共享 ...

  3. Linux多线程同步之相互排斥量和条件变量

    1. 什么是相互排斥量 相互排斥量从本质上说是一把锁,在訪问共享资源前对相互排斥量进行加锁,在訪问完毕后释放相互排斥量上的锁. 对相互排斥量进行加锁以后,不论什么其它试图再次对相互排斥量加锁的线程将会 ...

  4. linux系统编程:线程同步-相互排斥量(mutex)

    线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...

  5. Linux线程相互排斥量--进程共享属性

    多线程中.在相互排斥量和 读写锁的 属性中.都有一个叫 进程共享属性 . 对于相互排斥量,查询和设置这个属性的方法为: pthread_mutexattr_getpshared pthread_mut ...

  6. 数据共享之相互排斥量mutex

    相互排斥量介绍 相互排斥量能够保护某些代码仅仅能有一个线程运行这些代码.假设有个线程使用相互排斥量运行某些代码,其它线程訪问是会被堵塞.直到这个线程运行完这些代码,其它线程才干够运行. 一个线程在訪问 ...

  7. μCOS-II系统之事件(event)的使用规则及Semaphore的相互排斥量使用方法

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wavemcu/article/details/27798493 ****************** ...

  8. WinCE C#程序,控制启动时仅仅能启动一个程序,使用相互排斥量来实现,该实现方法測试通过

    </pre><pre code_snippet_id="430174" snippet_file_name="blog_20140718_5_46349 ...

  9. Linux互斥和同步应用程序(一):posix线程和线程之间的相互排斥

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流.请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回 ...

随机推荐

  1. windows下进程与线程剖析

    进程与线程的解析 进程:一个正在运行的程序的实例,由两部分组成: 1.一个内核对象,操作系统用它来管理进程.内核对象也是系统保存进程统计信息的地方. 2.一个地址空间,其中包含所有可执行文件或DLL模 ...

  2. EchoService

    dubbo为consumer端的代理对象实现了EchoService接口. 使用示例: <dubbo:reference id="hello" interface=" ...

  3. JavaScript学习总结(八)——JavaScript数组

    JavaScript中的Array对象就是数组,首先是一个动态数组,无需预先制定大小,而且是一个像Java中数组.ArrayList.Hashtable等的超强综合体. 一.数组的声明 常规方式声明: ...

  4. Sql Server中集合的操作(并集、差集、交集)学习

    首先我们做一下测试数据 1.创建测试数据 --创建人员表1-- create table Person1 ( Uid ,) primary key, Name ) not null ) --创建人员表 ...

  5. 安装完DevExpress14.2.5,如何破解它呢?

    DevExpress是一个界面控件套件,提供了一系列的界面控件套件的DotNet界面控件.DevExpress开发的控件有很强的实力,不仅功能丰富,应用简单,而且界面华丽,更可方便订制,方便开发人员开 ...

  6. jsp javabean开发模式

    JSP&&JavaBean开发模式 在jsp+javabean架构中,jsp负责控制逻辑,表现逻辑,业务对象的调用 jsp+javaBean模式适合开发业务逻辑不复杂的web应用,这种 ...

  7. pl/sql快速输入select等语句

    平时对数据库操作的时候,输入DML语句,很浪费时间,我们想要这样的效果 ,输入sf,plsql就会自动输入select * from.我们需要在plsql中进行如下设置即可: 工具(tools)--& ...

  8. selenium(四)操作cookie,伪造cookie

    简介: Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份.进行 session 跟踪而储存在用户本地终端上的数据. 常见的用途就是保留用户登陆信息,登陆时的7天免登陆,记住 ...

  9. 《Python》反射、内置方法(__str__,__repr__)

    一.反射 通过字符串的形式操作对象相关的属性.(使用字符串数据类型的变量名来获取这个变量的值) Python中的一切事物都是对象(都可以使用反射) 反射类中的变量 反射对象中的变量 反射模板中的变量 ...

  10. Android Touch事件之一:Touch事件在父ViewGroup和子View之间的传递篇

    2015-11-26 17:00:22 前言:Android的Touch事件传递和View的实现紧密相连,因此理解Touch事件的传递,有助于我们更好的理解View的工作原理. 1. 几个重要的方法: ...