有时一个相互排斥量是不够的:
比方:
当多个线程同一时候訪问一个队列结构时,你须要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值时,一般会看到很多其它的回退操作。
线程依照加锁的相反顺序释放全部锁
这是用来避免线程中的不必要的回退操作。
假设你使用“试加锁和回退”算法,你应该总是以相反的顺序解锁相互排斥量