Linux 常见并发问题

最近在看《操作系统导论》,觉得里面的并发相关的内容写的很好,故此总结下。

违反原子性缺陷
第一种类型的问题叫作违反原子性。这是一个 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 本身作为状态变量,但为了简洁,我们没有这样做。当线程之间的顺序很重要时,条件变量(或信号量)能够解决问题。

总结:这里说的违反顺序缺陷,相信有一定开发经验的同学一定遇到过,这类问题遇到的话会有些头疼,偶现、无规律等等。总的来说,对于违反顺序缺陷的问题,我们可以设计的让他们有序,在充分了解当前业务的基础上,合理的设计业务的启动和运行时序,保证可控,比如用条件变量、信号量等。对于进程和进程间的,可以通过设置启动参数或者延时处理来控制时序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值