赞
踩
最近在看《操作系统导论》,觉得里面的并发相关的内容写的很好,故此总结下。
违反原子性缺陷
第一种类型的问题叫作违反原子性。这是一个 MySQL 中出现的例子。读者可以先自行
找出其中问题所在。
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;
这个例子中, 两个线程都要访问 thd 结构中的成员 proc_info。 第一个线程检查 proc_info非空,然后打印出值;第二个线程设置其为空。显然,当第一个线程检查之后,在 fputs()调用之前被中断,第二个线程把指针置为空;当第一个线程恢复执行时,由于引用空指针,导致程序奔溃。根据 Lu 等人,更正式的违反原子性的定义是:“违反了多次内存访问中预期的可串行性(即代码段本意是原子的,但在执行中并没有强制实现原子性)”。在我们的例子中,proc_info 的非空检查和 fputs()调用打印 proc_info 是假设原子的,当假设不成立时,代码就出问题了。这种问题的修复通常(但不总是)很简单。你能想到如何修复吗?在这个方案中,我们只要给共享变量的访问加锁,确保每个线程访问 proc_info 字段时,都持有锁(proc_info_lock)。当然,访问这个结构的所有其他代码,也应该先获取锁。
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);
总结:违反原子性 也就是通常所说的破坏了事物的原子性导致操作是非原子的,更简单通俗点说,由于临界区的读写不一致导致不可预知的结果。通常的做法是对临界区加锁处理或者用“比较和交换”的原子思想实现原子操作来避免这种情况;
违反顺序缺陷
Lu 等人提出的另一种常见的非死锁问题叫作违反顺序(order violation)。下面是一个简单的例子。同样,看看你是否能找出为什么下面的代码有缺陷。
1 Thread 1::
2 void init() {
3 ...
4 mThread = PR_CreateThread(mMain, ...);
5 ...
6 }
7
8 Thread 2::
9 void mMain(...) {
10 ...
11 mState = mThread->State;
12 ...
13 }
你可能已经发现, 线程 2 的代码中似乎假定变量 mThread 已经被初始化了(不为空)。然而,如果线程 1 并没有首先执行,线程 2 就可能因为引用空指针奔溃(假设 mThread初始值为空;否则,可能会产生更加奇怪的问题,因为线程 2 中会读到任意的内存位置并引用)。违反顺序更正式的定义是:“两个内存访问的预期顺序被打破了(即 A 应该在 B 之前执行,但是实际运行中却不是这个顺序)” [L+08]。我们通过强制顺序来修复这种缺陷。 正如之前详细讨论的,条件变量( condition variables)就是一种简单可靠的方式,在现代代码集中加入这种同步。在上面的例子中,我们可以把代码修改成这样:
1 pthread_mutex_t mtLock = PTHREAD_MUTEX_INITIALIZER; 2 pthread_cond_t mtCond = PTHREAD_COND_INITIALIZER; 3 int mtInit = 0; 4 5 Thread 1:: 6 void init() { 7 ... 8 mThread = PR_CreateThread(mMain, ...); 9 10 // signal that the thread has been created... 11 pthread_mutex_lock(&mtLock); 12 mtInit = 1; 13 pthread_cond_signal(&mtCond); 14 pthread_mutex_unlock(&mtLock); 15 ... 16 } 17 18 Thread 2:: 19 void mMain(...) { 20 ... 21 // wait for the thread to be initialized... 22 pthread_mutex_lock(&mtLock); 23 while (mtInit == 0) 24 pthread_cond_wait(&mtCond, &mtLock); 25 pthread_mutex_unlock(&mtLock); 26 27 mState = mThread->State; 28 ... 29 }
在这段修复的代码中,我们增加了一个锁(mtLock)、一个条件变量(mtCond)以及状态的变量(mtInit)。初始化代码运行时,会将 mtInit 设置为 1,并发出信号表明它已做了这件事。如果线程 2 先运行,就会一直等待信号和对应的状态变化;如果后运行,线程 2 会检查是否初始化(即 mtInit 被设置为 1),然后正常运行。请注意,我们可以用 mThread 本身作为状态变量,但为了简洁,我们没有这样做。当线程之间的顺序很重要时,条件变量(或信号量)能够解决问题。
总结:这里说的违反顺序缺陷,相信有一定开发经验的同学一定遇到过,这类问题遇到的话会有些头疼,偶现、无规律等等。总的来说,对于违反顺序缺陷的问题,我们可以设计的让他们有序,在充分了解当前业务的基础上,合理的设计业务的启动和运行时序,保证可控,比如用条件变量、信号量等。对于进程和进程间的,可以通过设置启动参数或者延时处理来控制时序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。