POSIX 线程具体解释(3-相互排斥量:"固定加锁层次"/“试加锁-回退”)
有时一个相互排斥量是不够的:
比方:
当多个线程同一时候訪问一个队列结构时,你须要2个相互排斥量,一个用来保护队列头,一个用来保护队列元素内的数据。
当为多线程建立一个树结构时。你可能须要为每一个节点设置一个相互排斥量。
同一时候使用多个相互排斥量会导致复杂度的添加
最坏的情况就是死锁的发生。即两个线程分别锁住一个相互排斥量而等待对方的相互排斥量。
多相互排斥量可能导致死锁:
假设能够在独立的数据上使用两个分离的相互排斥量,那么就应该这么做。
这样,通过降低线程必须等待其它线程完毕数据操作的时间。
假设数据独立,则某个特定函数就不太可能常常须要同一时候加锁两个相互排斥量。
假设数据不是全然独立的时候。情况就复杂了。
假设你的程序中有一个不变量,影响着由两个相互排斥量保护的数据。
即使该不变量非常少被改变或引用。你迟早须要编写同一时候锁住两个相互排斥量的代码,来确保不变量的完整性。
一个经典的死锁现象
假设一个线程锁住相互排斥量A后加锁相互排斥量B。同一时候还有一个线程锁住相互排斥量B后加锁相互排斥量A。
这种代码就是一个经典的死锁现象。
两个线程可能同一时候完毕第一步。
即使是在但处理器系统中,一个线程完毕了第一步后可能被时间片机制抢占。以使还有一个线程完毕第一步。
至此两个线程都无法完毕第二步,由于他们彼此等待的相互排斥量已经被对方锁住。
针对上述类型的死锁,能够考虑一下两种通用的解决方法:
1、固定加锁层次
比方,全部须要同一时候加锁相互排斥量A和相互排斥量B的代码,必须先加锁相互排斥量A。再加锁相互排斥量B。
2、试加锁和回退
在锁住某个集合中的第一个相互排斥量后,使用pthread_mutex_trylock来加锁集合中的其它相互排斥量。
假设失败则将集合中全部已加锁的相互排斥量释放,并又一次加锁。
固定加锁层次具体解释:
有很多方式定义固定加锁层次,但对于特定的相互排斥量。总有某个明显的加锁顺序。
比如:
假设有两个相互排斥量,一个保护队列头。一个保护队列元素内的数据。
则非常显然的一种固定加锁层次就是先将队列头相互排斥量加锁。然后再加锁还有一个相互排斥量。
假设相互排斥量间不存在明显的逻辑层次,则能够建立随意的固定加锁层次。
比如:
你能够创建这样一个加锁相互排斥量集合的函数。
将集合中的相互排斥量照ID地址顺序排列。并以此顺序加锁相互排斥量。
或者给每一个相互排斥量指派名字,然后依照字母顺序加锁。
或者给每一个相互排斥量指派序列号,然后依照数字顺序加锁。
从某种程度上讲,仅仅要总是保持同样的顺序。顺序本身就并不真正重要。
试加锁和回退具体解释:
回退的方式没有固定加锁层次有效,它会浪费时间来试锁和回退。
还有一方面。你也不必然义和遵循严格的固定加锁层次,这使得回退的方法更为灵活。
能够组合两种算法来最小化回退的代价。
即在定义良好的代码区遵循固定加锁层次,在更灵活的地方使用试加锁-回退。
试加锁和回退的代码演示样例:
- #include <pthread.h>
- #include <sched.h>
- #include <unistd.h>
- #include <errno.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- //====================================================
- #define ITERATIONS 10
- //====================================================
- //相互排斥量数组
- pthread_mutex_t mutex[3]=
- {
- PTHREAD_MUTEX_INITIALIZER,
- PTHREAD_MUTEX_INITIALIZER,
- PTHREAD_MUTEX_INITIALIZER
- };
- //====================================================
- //是否开启试加锁-回退模式
- int backoff=1;
- /*
- 该标识符决定的操作:
- 当它>0,线程会在锁住每一个相互排斥量后调用sched_yield。以确保其它线程有机会执行
- 当它<0,线程则在锁住每一个相互排斥量后睡眠1秒,以确保其它线程真正有机会执行
- */
- int yield_flag = 0;
- //====================================================
- void *lock_forward (void *arg)
- {
- int i, iterate, backoffs;
- int status;
- //循环ITERATIONS次
- for (iterate = 0; iterate < ITERATIONS; iterate++)
- {
- //记录回退的次数
- backoffs = 0;
- //按0、1、2的顺序对3个相互排斥量加锁
- for (i = 0; i < 3; i++)
- {
- //按正常的方法加锁第一个相互排斥量
- if (i == 0)
- {
- status = pthread_mutex_lock (&mutex[i]);
- if (status != 0)
- {
- printf("First lock error\n");
- //终止异常程序
- abort();
- }
- }
- /*
- 对于第2、3个相互排斥量
- 假设开启了试加锁模式,就运行试加锁
- 否则依照正常模式加锁
- */
- else
- {
- if (backoff)
- status = pthread_mutex_trylock (&mutex[i]);
- else
- status = pthread_mutex_lock (&mutex[i]);
- //假设是试加锁失败。则回退
- if (status == EBUSY)
- {
- //回退次数++
- backoffs++;
- printf( "[forward locker backing off at %d]\n",i);
- //将之前加锁的相互排斥量释放掉
- for (; i >= 0; i--)
- {
- status = pthread_mutex_unlock (&mutex[i]);
- if (status != 0)
- {
- printf("Backoff error\n");
- //终止异常程序
- abort();
- }
- }
- }
- else
- {
- if (status != 0)
- {
- printf("Lock mutex error\n");
- //终止异常程序
- abort();
- }
- printf("forward locker got %d\n",i);
- }
- }
- /*
- 依据yield_flag决定是睡1秒还是调用sched_yield ()
- */
- if (yield_flag)
- {
- if (yield_flag > 0)
- sched_yield ();
- else
- sleep (1);
- }
- }
- //显示加锁情况
- printf ("lock forward got all locks, %d backoffs\n", backoffs);
- //所有解锁
- pthread_mutex_unlock (&mutex[2]);
- pthread_mutex_unlock (&mutex[1]);
- pthread_mutex_unlock (&mutex[0]);
- sched_yield ();
- }
- return NULL;
- }
- //====================================================
- void *lock_backward (void *arg)
- {
- int i, iterate, backoffs;
- int status;
- //循环ITERATIONS次
- for (iterate = 0; iterate < ITERATIONS; iterate++)
- {
- //记录回退的次数
- backoffs = 0;
- //按2、1、0的顺序对3个相互排斥量加锁
- for (i = 2; i >= 0; i--)
- {
- //按正常的方法加锁第一个相互排斥量
- if (i == 2)
- {
- status = pthread_mutex_lock (&mutex[i]);
- if (status != 0)
- {
- printf("First lock error\n");
- //终止异常程序
- abort();
- }
- }
- /*
- 对于第2、3个相互排斥量
- 假设开启了试加锁模式,就运行试加锁
- 否则依照正常模式加锁
- */
- else
- {
- if (backoff)
- status = pthread_mutex_trylock (&mutex[i]);
- else
- status = pthread_mutex_lock (&mutex[i]);
- //假设是试加锁失败,则回退
- if (status == EBUSY)
- {
- //回退次数++
- backoffs++;
- printf( "[backward locker backing off at %d]\n",i);
- //将之前加锁的相互排斥量释放掉
- for (; i < 3; i++)
- {
- status = pthread_mutex_unlock (&mutex[i]);
- if (status != 0)
- {
- printf("Backoff error\n");
- //终止异常程序
- abort();
- }
- }
- }
- else
- {
- if (status != 0)
- {
- printf("Lock mutex error\n");
- //终止异常程序
- abort();
- }
- printf( "backward locker got %d\n",i);
- }
- }
- /*
- 依据yield_flag决定是睡1秒还是调用sched_yield ()
- */
- if (yield_flag)
- {
- if (yield_flag > 0)
- sched_yield ();
- else
- sleep (1);
- }
- }
- //显示加锁情况
- printf ("lock backward got all locks, %d backoffs\n", backoffs);
- //所有解锁
- pthread_mutex_unlock (&mutex[0]);
- pthread_mutex_unlock (&mutex[1]);
- pthread_mutex_unlock (&mutex[2]);
- sched_yield ();
- }
- return NULL;
- }
- //====================================================
- int main (int argc, char *argv[])
- {
- pthread_t forward, backward;
- int status;
- //手动设置是否开启回退模式
- if (argc > 1)
- backoff = atoi (argv[1]);
- //手动设置是否沉睡或调用sched_yield()
- if (argc > 2)
- yield_flag = atoi (argv[2]);
- //开启lock_forward线程,按0、1、2的顺序加锁相互排斥量
- status = pthread_create (&forward, NULL, lock_forward, NULL);
- if (status != 0)
- {
- printf("Create forward error\n");
- //终止异常程序
- abort();
- }
- //开启lock_forward线程。按2、1、0的顺序加锁相互排斥量
- status = pthread_create (&backward, NULL, lock_backward, NULL);
- if (status != 0)
- {
- printf("Create backward error\n");
- //终止异常程序
- abort();
- }
- pthread_exit (NULL);
- }
代码结果:
[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-相互排斥量:"固定加锁层次"/“试加锁-回退”)的更多相关文章
- Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁
相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...
- 【C/C++多线程编程之六】pthread相互排斥量
多线程编程之线程同步相互排斥量 Pthread是 POSIX threads 的简称,是POSIX的线程标准. Pthread线程同步指多个线程协调地,有序地同步使用共享 ...
- Linux多线程同步之相互排斥量和条件变量
1. 什么是相互排斥量 相互排斥量从本质上说是一把锁,在訪问共享资源前对相互排斥量进行加锁,在訪问完毕后释放相互排斥量上的锁. 对相互排斥量进行加锁以后,不论什么其它试图再次对相互排斥量加锁的线程将会 ...
- linux系统编程:线程同步-相互排斥量(mutex)
线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...
- Linux线程相互排斥量--进程共享属性
多线程中.在相互排斥量和 读写锁的 属性中.都有一个叫 进程共享属性 . 对于相互排斥量,查询和设置这个属性的方法为: pthread_mutexattr_getpshared pthread_mut ...
- 数据共享之相互排斥量mutex
相互排斥量介绍 相互排斥量能够保护某些代码仅仅能有一个线程运行这些代码.假设有个线程使用相互排斥量运行某些代码,其它线程訪问是会被堵塞.直到这个线程运行完这些代码,其它线程才干够运行. 一个线程在訪问 ...
- μCOS-II系统之事件(event)的使用规则及Semaphore的相互排斥量使用方法
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wavemcu/article/details/27798493 ****************** ...
- WinCE C#程序,控制启动时仅仅能启动一个程序,使用相互排斥量来实现,该实现方法測试通过
</pre><pre code_snippet_id="430174" snippet_file_name="blog_20140718_5_46349 ...
- Linux互斥和同步应用程序(一):posix线程和线程之间的相互排斥
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流.请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回 ...
随机推荐
- python-day41--约束条件
一 .介绍 约束条件与数据类型的宽度一样,都是可选参数 作用:用于保证数据的完整性和一致性主要分为: PRIMARY KEY (PK) 标识该字段为该表的主键,可以唯一的标识记录 FOREIGN KE ...
- 万恶的deferred_segment_creation(延迟块分配)
11gR2开始,空表是不分配segment的.由于没有分配 segment, EXP默认不能导出空表,user_objects有该对象但是 user_segment没有该对象
- 用了皮肤控件之后,报错:容量超出了最大容量 参数名:capacity
用了皮肤控件之后,报错:容量超出了最大容量 参数名:capacity MessageBox.show()错误!!容量超出了最大容量.参数名: capacity 解决方案: 设置 skin.SkinDi ...
- 第5章——使用 Razor(MVC框架视图引擎)
Razor 是MVC框架视图引擎的名称. 本章提供 Razor 语法的快速教程,以使你能够识别 Razor 表达式. 本章不打算提供 Razor 的完整参考,而将其视为一个语法速成教程.在本书的后续内 ...
- XML删除节点
XmlDocument doc = new XmlDocument(); doc.Load("Order.xml"); XmlNode xn = doc.SelectSingleN ...
- Vim:replace with foobar (y/n/a/q/l/^E/^Y)?
y:to substitute this match n:to skip this match a:to substitute this and all remaining matches q:to ...
- spring的FactoryBean
(以下内容翻译自spring/docs/3.2.18.RELEASE) 为具有工厂属性的对象实现FactoryBean接口. FactoryBean接口是spring IoC 容器实例化逻辑的一点补充 ...
- spring boot 学习(六)spring boot 各版本中使用 log4j2 记录日志
spring boot 各版本中使用 log4j2 记录日志 前言 Spring Boot中默认日志工具是 logback,只不过我不太喜欢 logback.为了更好支持 spring boot 框架 ...
- Flask初级(五)flash在模板中使用继承,模板的模板
Project name :Flask_Plan templates:templates static:static 继续上一篇文章. 我们不希望每个页面都写一遍引入js,css,导航条……………… ...
- 递归算法,如何把list中父子类对象递归成树
以前写代码for循环写的多,递归除了在大学学习以外,真没怎么用过! 最近项目中使用到了关于族谱排列的问题,就是怎么把数据库里的多个子父类people对象,在界面中用树的结构展示出来 假设数据库中peo ...