下面我们来看一个简化了的死锁的例子:清单 19.例子 thread_deadlock.cc1 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 | static mutex g_mutex1, g_mutex2;
static void
inc1( int *p ){
for ( int i = 0; i < COUNT; i++){
g_mutex1.lock();
(*p)++;
g_mutex2.lock();
g_mutex2.unlock();
g_mutex1.unlock();
}
}
static void
inc2( int *p ){
for ( int i = 0; i < COUNT; i++){
g_mutex2.lock();
g_mutex1.lock();
(*p)++;
g_mutex1.unlock();
g_mutex2.unlock();
}
}
void threadMutex( void ){
int a = 0;
thread ta( inc1, &a);
thread tb( inc2, &a);
ta.join();
tb.join();
cout << "a=" << a << endl;
}
|
在这个例子中,g_mutex1 和 g_mutex2 都是互斥的资源,任意时刻都只有一个线程可以持有(加锁成功),而且只有持有线程调用 unlock 释放锁资源的时候其它线程才能去持有,满足条件 1 和 3,线程 ta 持有了 g_mutex1 之后,在释放 g_mutex1 之前试图去持有 g_mutex2,而线程 tb 持有了 g_mutex2 之后,在释放 g_mutex2 之前试图去持有 g_mutex1,满足条件 2 和 4,这种情况之下,当线程 ta 试图去持有 g_mutex2 的时候,如果 tb 正持有 g_mutex2 而试图去持有 g_mutex1 时就发生了死锁。在有些环境下,可能要多次运行这个例子才出现死锁,实际工作中这种偶现特性让查找问题变难。要破除这个死锁,我们只要按如下代码所示破除条件 3 和 4 即可: 清单 20.例子 thread_break_deadlock.cc1 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 | static mutex g_mutex1, g_mutex2;
static voi
inc1( int *p ){
for ( int i = 0; i < COUNT; i++){
g_mutex1.lock();
(*p)++;
g_mutex1.unlock();
g_mutex2.lock();
g_mutex2.unlock();
}
}
static void
inc2( int *p ){
for ( int i = 0; i < COUNT; i++){
g_mutex2.lock();
g_mutex2.unlock();
g_mutex1.lock();
(*p)++;
g_mutex1.unlock();
}
}
void threadMutex( void ){
int a = 0;
thread ta( inc1, &a);
thread tb( inc2, &a);
ta.join();
tb.join();
cout << "a=" << a << endl;
}
|
在一些复杂的并行编程场景,如何避免死锁是一个很重要的话题,在实践中,当我们看到有两个锁嵌套加锁的时候就要特别提高警惕,它极有可能满足了条件 2 或者 4。
结束语上述例子在 CentOS 6.5,g++ 4.8.1/g++4.9 以及 clang 3.5 下面编译通过,在编译的时候,请注意下述几点: - 设置 -std=c++11;
- 链接的时候设置 -pthread;
- 使用 g++编译链接时设置 -Wl,–no-as-needed 传给链接器,有些版本的 g++需要这个设置;
- 设置宏定义 -D_REENTRANT,有些库函数是依赖于这个宏定义来确定是否使用多线程版本的。
具体可以参考本文所附的代码中的 Makefile 文件。 在用 gdb 调试多线程程序的时候,可以输入命令 info threads 查看当前的线程列表,通过命令 thread n 切换到第 n 个线程的上下文,这里的 n 是 info threads 命令输出的线程索引数字,例如,如果要切换到第 2 个线程的上下文,则输入命令 thread 2。 聪明地使用多线程,拥抱多线程吧。 |