如何寻找多线程程序的bug
1. 准备工作
确保程序以单线程方式运行时没有错误。这是找bug的基础。
2. 区分错误类型
2.1 根本无法运行:
可能原因1:对mutex使用lock后忘记解锁
例如:没有对每一种可能的情况进行unlock。
mutex m1;
thread1{
m1.lock();
if(......){
return;
}
m1.unlock();
return;
}
可能原因2:发生死锁
例如:使用普通锁对递归程序加锁。
mutex m1;
void func1(){
m1.lock();
......
......
fun1();
......
......
m1.unlock();
}
2.2 运行过程中异常终止:
可能原因1:数据竞争导致的数组越界错误。
例如:
atomic<int> index = 0;
vector<int> vec;
int func(int n){
......
......
}
thread1{
index++;
vec.push_back(func(index));
vec.push_back(func(index+1));
vec.push_back(func(index+2));
......
......
......
cout << vec[index*3-3] << endl;
cout << vec[index*3-2] << endl;
cout << vec[index*3-1] << endl;
}
thread2{
index++;
vec.push_back(func(index));
vec.push_back(func(index+1));
vec.push_back(func(index+2));
......
......
......
cout << vec[index*3-3] << endl;
cout << vec[index*3-2] << endl;
cout << vec[index*3-1] << endl;
}
2.3 正常运行但结果错误:
可能原因:数据竞争,没有对共享的全局变量做有效保护。
举个例子:
atomic<int> index = 0;
thread1{
index++;
int a = index;
cout << a << endl;
}
thread2{
index++;
int a = index;
cout << a << endl;
}
2.4 正常运行,但结果偶尔错误:
即使保证输入数据完全不变,结果也会出现时对时错的情况。这种问题基本上不会出现在单线程程序中。
可能原因:仍然是数据竞争,但与2.3的区别在于,大部分时候程序运算的结果都是正确的。我们知道,数据竞争这类多线程错误,往往发生在一个线程读完共享变量但没来得及写时,另一个线程闯入并修改共享变量导致的。所以这样的小概率错误就提醒我们,线程中对某个共享变量的读和写没有得到保护,但由于读操作和写操作的位置非常近,使得其他线程恰好在读和写的间隔中修改了共享变量的概率很低。
举个例子:
atomic<int> index = 0;
thread1{
index++;
int a = index;
/*
占99%以上运行时间的其他代码
*/
}
thread2{
index++;
int a = index;
/*
占99%以上运行时间的其他代码
*/
}
其实可以看到,这仍然是一个很普通的多线程错误。但是两个线程下其他代码占用99%以上的时间,这就导致恰好在index++;
和int a = index;
之间切换线程的概率很低。
3. 缩小错误范围
如果第2步仍然没有帮你找到程序的错误所在,那么就不得不使用比较麻烦的方法,一步步缩小错误范围。
3.1 只用一个线程的程序
将最大线程数设置为1,测试是否会发生错误。如果会发生错误,说明bug出现在多线程程序额外引入的各种原子变量或者多线程容器上,而不是数据竞争。
3.2 抽取中间结果
在每两个步骤之间抽取出中间结果,与正确程序的中间结果进行对比。如果一样说明没有问题。
注意:在某些程序中,使用多线程时中间结果不一样反而是正常的。
4. 总结
结合2与3中方法,应该能大致定位出bug所在。