面对多个互斥量的加锁策略:"试加锁-回退"算法/固定加锁层次
有时一个互斥量是不够的:
比如:
当多个线程同时访问一个队列结构时,你需要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值时,通常会看到更多的回退操作。
线程按照加锁的相反顺序释放所有锁
这是用来避免线程中的不必要的回退操作。
如果你使用“试加锁和回退”算法,你应该总是以相反的顺序解锁互斥量
面对多个互斥量的加锁策略:"试加锁-回退"算法/固定加锁层次的更多相关文章
- linux线程同步(1)-互斥量
一.概述 互斥量是线程同步的一种机制,用来保护多线程的共享资源.同一时刻,只允许一个线程对临界区进行 ...
- posix thread互斥量
互斥量 互斥量(Mutex)是“mutual exclusion”的缩写.互斥量是实现线程同步,和保护同时写共享数据的主要方法.使用互斥量的典型顺序如下:1. 创建和初始一个互斥量 2. 多个线程尝试 ...
- Linux多线程——使用互斥量同步线程
前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...
- Linux多线程--使用互斥量同步线程【转】
本文转载自:http://blog.csdn.net/ljianhui/article/details/10875883 前文再续,书接上一回,在上一篇文章:Linux多线程——使用信号量同步线程中, ...
- pthread中互斥量,锁和条件变量
互斥量 #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_in ...
- Cocos2d-X多线程(2) 线程的互斥量std::mutex和线程锁
多个线程同时访问共享资源时,经常会出现冲突等.为了避免这种情况的发生,可以使用互斥量,当一个线程锁住了互斥量后,其他线程必须等待这个互斥量解锁后才能访问它. thread提供了四种不同的互斥量: 1. ...
- C++并发与多线程学习笔记--互斥量、用法、死锁概念
互斥量(mutex)的基本概念 互斥量的用法 lock(), unlock() std::lock_guard类模板 死锁 死锁演示 死锁的一般解决方案 std::lock()函数模板 std::lo ...
- 多线程相关------互斥量Mutex
互斥量(Mutex) 互斥量是一个可以处于两态之一的变量:解锁和加锁.只有拥有互斥对象的线程才具有访问资源的权限.并且互斥量可以用于不同进程中的线程的互斥访问. 相关函数: CreateMutex用于 ...
- Linux/Unix 线程同步技术之互斥量(1)
众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...
随机推荐
- Delphi中拖动无边框窗口的5种方法
1.MouseMove事件中加入: // ReleaseCapture;// Perform(WM_SYSCOMMAND, $F017 , 0); 2.MouseDown事件中加入: // POSTM ...
- hdu 4691 Front compression (后缀数组)
hdu 4691 Front compression 题意:很简单的,就是给一个字符串,然后给出n个区间,输出两个ans,一个是所有区间的长度和,另一个是区间i跟区间i-1的最长公共前缀的长度的数值的 ...
- Android开发之查看应用包名package和入口activity名称的方法
使用android自动化测试工具monkeyrunner启动应用时,需要填写被测程序的包名和启动的Activity,以下有两种查看应用包名package和入口activity名称的方法: 方法一:使用 ...
- TWinControl.WMNCPaint对非客户的绘制
混个脸熟: procedure TWinControl.WMNCPaint(var Message: TMessage); const InnerStyles: , BDR_SUNKENINNER, ...
- javascript中apply和eval结合的强大用法
eval是一个函数,可以接受一个参数,这个参数可以作为js语句被解释性的执行,利用这个特性,eval和apply结合起来,可以大大简化代码 如下例子 <a class="cl ...
- frame.bounds和center
CGPoint point=CGPoint(x,y); //表示位置 CGSize size=CGSzieMake(width,height); //表示大小 CGRect rect=CGRect ...
- quarze的工作原理
quartz的工作原理 http://lavasoft.blog.51cto.com/62575/181907/ 几种定时任务的比較 http://blog.sina.com.cn/s/blog_69 ...
- Python+Django+SAE系列教程15-----输出非HTML内容(图片/PDF)
一个Django视图函数 必须 接受一个HttpRequest 实例作为它的第一个參数 返回一个HttpResponse 实例 从一个视图返回一个非HTML 内容的关键是在构造一个 HttpRespo ...
- ORACLE 五种表的优缺点总结
ORACLE 五种表的优缺点总结: 1.普通表(heap table):适合大部分设计场景,有长处也有缺点. 长处: a,语法简单方便 b,适合大部分场景 缺点: a,更新日志开销较大 b,Delet ...
- android平台中,EventBus研究学习
当一个Android应用功能越来越多的时候.app中各个部分之间通信.往往採用Observer的方式来进行,即注冊----通知----注销的方式运行 各类控件常常须要依据某个状态来更 ...