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

比如:

当多个线程同时访问一个队列结构时,你需要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值时,通常会看到更多的回退操作。

线程按照加锁的相反顺序释放所有锁

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

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

面对多个互斥量的加锁策略:"试加锁-回退"算法/固定加锁层次的更多相关文章

  1. linux线程同步(1)-互斥量

    一.概述                                                   互斥量是线程同步的一种机制,用来保护多线程的共享资源.同一时刻,只允许一个线程对临界区进行 ...

  2. posix thread互斥量

    互斥量 互斥量(Mutex)是“mutual exclusion”的缩写.互斥量是实现线程同步,和保护同时写共享数据的主要方法.使用互斥量的典型顺序如下:1. 创建和初始一个互斥量 2. 多个线程尝试 ...

  3. Linux多线程——使用互斥量同步线程

    前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...

  4. Linux多线程--使用互斥量同步线程【转】

    本文转载自:http://blog.csdn.net/ljianhui/article/details/10875883 前文再续,书接上一回,在上一篇文章:Linux多线程——使用信号量同步线程中, ...

  5. pthread中互斥量,锁和条件变量

    互斥量 #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_in ...

  6. Cocos2d-X多线程(2) 线程的互斥量std::mutex和线程锁

    多个线程同时访问共享资源时,经常会出现冲突等.为了避免这种情况的发生,可以使用互斥量,当一个线程锁住了互斥量后,其他线程必须等待这个互斥量解锁后才能访问它. thread提供了四种不同的互斥量: 1. ...

  7. C++并发与多线程学习笔记--互斥量、用法、死锁概念

    互斥量(mutex)的基本概念 互斥量的用法 lock(), unlock() std::lock_guard类模板 死锁 死锁演示 死锁的一般解决方案 std::lock()函数模板 std::lo ...

  8. 多线程相关------互斥量Mutex

    互斥量(Mutex) 互斥量是一个可以处于两态之一的变量:解锁和加锁.只有拥有互斥对象的线程才具有访问资源的权限.并且互斥量可以用于不同进程中的线程的互斥访问. 相关函数: CreateMutex用于 ...

  9. Linux/Unix 线程同步技术之互斥量(1)

    众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...

随机推荐

  1. Qt遍历图片文件

    原地址:http://blog.sina.com.cn/s/blog_5c70dfc80100tgff.html //实现遍历某个文件下的图片文件 //如果想遍历其余类型文件,方法也一样,只需简单修改 ...

  2. css3 动画运动路径

    1.cubic-bezier贝塞尔曲线CSS3动画工具 http://www.xuanfengge.com/cubic-bezier-bezier-css3-animation-tools.html ...

  3. 九度OnlineJudge之1014:排名

    题目描述:     今天的上机考试虽然有实时的Ranklist,但上面的排名只是根据完成的题数排序,没有考虑每题的分值,所以并不是最后的排名.给定录取分数线,请你写程序找出最后通过分数线的考生,并将他 ...

  4. Qt中用正則表達式来推断Text的语种,主要通过推断unicode的编码范围

    QString MainWindow::ParseLanguage(QString Text) {     if(Text.length()<=0)     {         return & ...

  5. 原始的js代码和jquery对比

    Even a task as simple as this can be complicated without jQuery at our disposal. In plain JavaScript ...

  6. Linux 二层协议架构组织

    本文主要讲解了Linux 二层协议架构组织,使用的内核的版本是2.6.32.27 为了方便理解,本文采用整体流程图加伪代码的方式从内核高层面上梳理了Linux 二层协议架构组织,希望可以对大家有所帮助 ...

  7. EasyUI - 操作 Tree 控件

    效果: HTML代码: 使用了模板页 <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHo ...

  8. Linux - 文件系统结构

    文件系统结构:   Linux文件系统为一个倒转的系统单根树状结构. 根为   / 严格区分大小写. 路径使用   /    分割,Windows使用  \     . 当前工作目录: 每一个Shel ...

  9. QNX---- interrupts 例程

    #include <sys/neutrino.h> int interruptID; const struct sigevent * intHandler (void *arg, int ...

  10. 控件编写:增强 TMEMO (一)(增加对WM_HSCROLL消息的处理)

    相信没有什么人对 MEMO 陌生了吧.尽管其组件的功能不错.但是,对它进行一些功能的改进,可以更好的使用. 有的时候,我们想要知道,当前的坐标是什么?甚至,想要在 滚动条滚动时触发一些事件. 但,TM ...