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,文章仅供学习交流.请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回 ...
随机推荐
- bzoj2595: [Wc2008]游览计划 斯坦纳树
斯坦纳树是在一个图中选取某些特定点使其联通(可以选取额外的点),要求花费最小,最小生成树是斯坦纳树的一种特殊情况 我们用dp[i][j]来表示以i为根,和j状态是否和i联通,那么有 转移方程: dp[ ...
- hdu3374 kmp+最小表示法
Give you a string with length N, you can generate N strings by left shifts. For example let consider ...
- Oracle 使用GSON库解析复杂json串
在前文中讲到了如何使用JSON标准库解析json串,参考: Oracle解析复杂json的方法(转) 现补充一篇使用GSON库在Oracle中解析复杂json的方法. GSON串的使用教程参考官方文档 ...
- 41. First Missing Positive *HARD*
Given an unsorted integer array, find the first missing positive integer. For example,Given [1,2,0] ...
- Mac下的nodeJs版本切换和升级
在我们开发多个项目的时候,因为框架支持的node版本不同,所以要切换多个node版本 首先我们要使用npm安装一个模块 n 的全局 1.npm install -g n 2.使用 n 加版本号就 ...
- 利用express.js连接mongodb数据库
var MongoClient = require('mongodb').MongoClient; var DB_CONN_STR = "mongodb://localhost:27017/ ...
- ACdream 1067:Triangles
Problem Description 已知一个圆的圆周被N个点分成了N段等长圆弧,求任意取三个点,组成锐角三角形的个数. Input 多组数据,每组数据一个N(N <= 1000000) Ou ...
- Numpy 函数总结 (不断更新)
本篇主要收集一些平时见到的 Numpy 函数. numpy.random.seed & numpy.random.RandomState np.random.seed() 和 np.rando ...
- php require include 区别
php提供了两种包含外部文件的方法:include()和require().include()语句是一个常规的php函数:而require() 是一种特殊的语言结构,它的使用受到一些限制.对这两者来说 ...
- grafana+influxdb+telegraf监控服务器cpu,内存和硬盘
随便抄了一篇,目前我们的项目也在用,这个是linux和windows通吃的一种监控方案,非常有效,详细和优美,需要监控什么具体的业务内容,自己向influxdb中插入就行了. 监控服务器状态是运维必不 ...