在已开发的成熟的web服务器以及数据库管理系统 例如Mysql Apache中,通过统计这些应用历次进行并发缺陷修复时可以看到,大部分并发缺陷均为非死锁缺陷,有大概四分之一的缺陷为死锁缺陷
非死锁缺陷:违反原子性(atomicity violation)缺陷和错误顺序(order violation)缺陷
违反原子性:
1
Thread
1::
2
if
(thd->proc_info)
{
3
...
4 fputs(thd->proc_info,
...);
5
...
6
}
7
8
Thread
2::
9 thd->proc_info = NULL;
这个典型例子说明了在进程1和进程2并发执行的过程中,调用空指针pro_info 导致程序崩溃。
根据Lu等人,更正式的违反原子性的定义是:“违反了多次内存访问中预期的可串行性(即代码段本意是原子的,但在执行中并没有强制实现原子性)”。
修复的方法:(加锁)
1
pthread_mutex_t proc_info_lock = PTHREAD_MUTEX_INITIALIZER;
2
3
Thread
1::
4 pthread_mutex_lock(&proc_info_lock);
5
if
(thd->proc_info)
{
6
...
7 fputs(thd->proc_info,
...);
8
...
9
}
10 pthread_mutex_unlock(&proc_info_lock);
11
12
Thread
2::
13 pthread_mutex_lock(&proc_info_lock);
14 thd->proc_info = NULL;
15 pthread_mutex_unlock(&proc_info_lock);
违反顺序缺陷:
Thread
1::
void init()
{
...
mThread = PR_CreateThread(mMain,
...);
...
}
Thread
2::
void mMain(...)
{
...
mState = mThread->State;
...
}
这个例子说明了当进程1和2并发执行时,由于在调用MThread之前可能还未初始化,导致程序出现问题。
违反顺序更正式的定义是:“两个内存访问的预期顺序被打破了(即A应该在B之前执行,但是实际运行中却不是这个顺序)”
我们通过强制顺序来修复这种缺陷。
pthread_mutex_t mtLock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mtCond = PTHREAD_COND_INITIALIZER;
int mtInit =
0;
Thread
1::
void init()
{
...
mThread = PR_CreateThread(mMain,
...);
// signal that the thread has been created...
pthread_mutex_lock(&mtLock);
mtInit =
1;
pthread_cond_signal(&mtCond);
pthread_mutex_unlock(&mtLock);
...
}
Thread
2::
void mMain(...)
{
...
// wait for the thread to be initialized...
pthread_mutex_lock(&mtLock);
while
(mtInit ==
0)
pthread_cond_wait(&mtCond,
&mtLock);
pthread_mutex_unlock(&mtLock);
mState = mThread->State;
...
}
非死锁缺陷:小结
大部分(97%)的非死锁问题是违反原子性和违反顺序这两种。因此,程序员仔细研究这些错误模式,应该能够更好地避免它们。此外,随着更自动化的代码检查工具的发展,它们也应该关注这两种错误,因为开发中发现的非死锁问题大部分都是这两种。
然而,并不是所有的缺陷都像我们举的例子一样,这么容易修复。有些问题需要对应用程序的更深的了解,以及大量代码及数据结构的调整。
死锁缺陷
除了上面提到的并发缺陷,死锁(deadlock)是一种在许多复杂并发系统中出现的经典问题。例如,当线程1持有锁L1,正在等待另外一个锁L2,而线程2持有锁L2,却在等待锁L1释放时,死锁就产生了。以下的代码片段就可能出现这种死锁:
Thread
1:
Thread
2:
lock(L1);
lock(L2);
lock(L2);
lock(L1);
数据竞争(data race)是指在非线程安全的情况下,多线程对同一个地址空间进行写操作。一般来说,我们都会通过线程同步方法来保证数据的安全,比如采用互斥量或者读写锁。但是由于某些笔误或者设计的缺陷,还是存在data race的可能性的。
若线程的类型状态发生改变,且没有与另外线程的其他操作同步,则会造成对象执行其类型状态中不允许的操作,从而产生并发类型状态缺陷。
使用模型检测来进行并发缺陷检测的流程:
模型检测是一种验证有限状态系统时序逻辑属性的形式化方法。首先将软件构造为状态机等抽象模型,然后使用模态/时序逻辑公式等形式化的表达式来描述安全属性,最终对模型进行遍历以验证软件是否满足安全属性,从而判断是否存在缺陷。Verisoft[33]是基于程序执行信息提取的模型检测工具,通过调度程序执行,利用程序执行信息(堆、栈和寄存器等)构造状态转换图(Kripke图),然后进行属性验证。此后,Verisoft,JPF[34]和SPIN[35]模型检测工具得到了广泛应用。模型检测可以检测到并发缺陷中:死锁,数据竞争,顺序性违背和原子性违背四种缺陷。