面对多个互斥量的加锁策略:"试加锁-回退"算法/固定加锁层次
有时一个互斥量是不够的:
比如:
当多个线程同时访问一个队列结构时,你需要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 ...
随机推荐
- 一个问题:关于类型转换Type Cast(汇编讲解 as 语法)
问题如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ...
- 基于Greenplum Hadoop分布式平台的大数据解决方案及商业应用案例剖析
随着云计算.大数据迅速发展,亟需用hadoop解决大数据量高并发访问的瓶颈.谷歌.淘宝.百度.京东等底层都应用hadoop.越来越多的企 业急需引入hadoop技术人才.由于掌握Hadoop技术的开发 ...
- Serialize a Binary Tree or a General Tree
For a binary tree, preorder traversal may be enough. For example, _ / \ / / \ 50 ...
- 微软推荐的130道ASP.NET常见面试题及答案
1. 简述 private. protected. public. internal 修饰符的访问权限. 答 . private : 私有成员, 在类的内部才可以访问. protected : 保护成 ...
- 显示出eclipse文件层次
看到图片中右边那个倒三角型符号没, 点一下,弹出个菜单,选package presentation->hierarachial 文件目录结构 flat 是包结构
- 如何在Linux上检查SSH的版本(转)
SSH协议规范存在一些小版本的差异,但是有两个主要的大版本:SSH1 (版本号 1.XX) 和 SSH2 (版本号 2.00). 事实上,SSH1和SSH2是两个完全不同互不兼容的协议.SSH2明显地 ...
- 用VC制作应用程序启动画面
摘 要:本文提供了四种启动画面制作方法. 使用启动画面一是可以减少等待程序加载过程中的枯燥感(尤其是一些大型程序):二是 可以用来显示软件名称和版权等提示信息.怎样使用VC++制作应用程序的启动画面呢 ...
- 在界面线程不能使用Sleep和WaitForSingleObject之类的函数, 使用 MsgWaitForMultipleObjects
http://blog.csdn.net/wishfly/article/details/3726985 你在主线程用了WaitForSingleObject,导致了消息循环的阻塞,界面假死. 然后在 ...
- uploadify,实际开发案例【选择完文件点击上传才上传】
<script type="text/javascript"> )+Math.floor(Math.random()*)+']-'; //设置随机文件前缀. $k(fu ...
- eclipse Maven plugin 配置
1. eclipse -> help -> marketpalce -> search 在输入框中输入Maven,回车. 在搜索结果列表中往下拉几个安装 Maven Integrat ...