多线程专题之线程死锁原因之谜

引子:线程死锁曾是多少程序员的噩梦,每每为此食不甘味,夜不成寐,一句话:苦不堪言。本文从几个场景入手,试图解开产生死锁的原因之谜。

教科书:说的很具体,理解很抽象

关于死锁产生的原因《操作系统》中有比较好的说明:

(1)因为系统资源不足。

(2)进程运行推进的顺序不合适。

(3)资源分配不当等。

关于死锁出现的必要条件也有比较具体的说明:

(1)互斥条件:一个资源每次只能被一个进程使用。

(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,这也为我们实际应用中定位死锁问题,提供了路由。

情景一、不加锁,两线程访问,变量访问示例

关于死锁,有锁才能死,如果我们不加锁,自然不会发生死锁,但是如果不加锁,对资源的访问,将会发生什么情况呢。不妨看下面的例子:

当两个线程读写相同变量时,线程A读取变量然后给予变量赋予一个新的值,但是写操作需要两个存储器周期。当线程B在这两个存储器周期中间读取这个相同变量时,它就会得到不一致的值。这就是为什么要对多线程资源访问进行加锁,加锁以后的访问顺序就变成了顺序访问,从而可以避免资源的不一致访问。

情景二、不加锁,多线程访问,增量操作示例

当两个或多个线程试图在同一时间修改同一个变量时,如果不加锁也会出现数据资源不一致的情况。如下图所示:

我们可以看到,增量操作分为三个步骤进行:(1)从内存单元读入寄存器。(2)从寄存器中进行变量值的增加。(3)把新的值写回内存单元。如果两个线程试图同时对统一变量执行增量操作时,结果可能出现不一致。变量可能比原来增加了1,也可能增加了2,具体是1,还是2取决于第二个线程读取变量时获得的值是5还是6。这里面有一个前提就是变量增加的操作不是原子操作,这是因为现代计算机系统中,存储器访问需要多个总线周期,多处理器的总线周期通常在多个处理器上是交叉的,所以无法保证数据时顺序一致的。

情景三、互斥锁,多变量部分锁

以上示例已经讲明了我们为何需要线程锁,不加锁将会导致数据资源访问的不一致。可是加锁后,如果存在满足死锁的必要条件,又会产生死锁,我们该怎么办呢?不妨先来看一个示例:

复制代码
  1 #include <stdlib.h>
  2 #include <pthread.h>
  3 
  4 #define NHASH 29
  5 #define HASH(fp) (((unsigned long)fp)%NHASH)
  6 
  7 struct foo *fh[NHASH];
  8 
  9 pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
 10 
 11 struct foo {
 12     int             f_count;
 13     pthread_mutex_t f_lock;
 14     struct foo     *f_next; /* protected by hashlock */
 15     int             f_id;
 16     /* ... more stuff here ... */
 17 };
 18 
 19 struct foo *
 20 foo_alloc(void) /* allocate the object */
 21 {
 22     struct foo    *fp;
 23     int            idx;
 24 
 25     if ((fp = malloc(sizeof(struct foo))) != NULL) {
 26         fp->f_count = 1;
 27         if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
 28             free(fp);
 29             return(NULL);
 30         }
 31         idx = HASH(fp);
 32         pthread_mutex_lock(&hashlock);
 33         fp->f_next = fh[idx];
 34         fh[idx] = fp->f_next;
 35         pthread_mutex_lock(&fp->f_lock);
 36         pthread_mutex_unlock(&hashlock);
 37         /* ... continue initialization ... */
 38         pthread_mutex_unlock(&fp->f_lock);
 39     }
 40     return(fp);
 41 }
 42 //增加
 43 void
 44 foo_hold(struct foo *fp) /* add a reference to the object */
 45 {
 46     pthread_mutex_lock(&fp->f_lock);
 47     fp->f_count++;
 48     pthread_mutex_unlock(&fp->f_lock);
 49 }
 50 //查找已经对象
 51 struct foo *
 52 foo_find(int id) /* find an existing object */
 53 {
 54     struct foo    *fp;
 55     int            idx;
 56 
 57     idx = HASH(fp);
 58     pthread_mutex_lock(&hashlock);
 59     for (fp = fh[idx]; fp != NULL; fp = fp->f_next) {
 60         if (fp->f_id == id) {
 61             foo_hold(fp);
 62             break;
 63         }
 64     }
 65     pthread_mutex_unlock(&hashlock);
 66     return(fp);
 67 }
 68 //减小
 69 void
 70 foo_rele(struct foo *fp) /* release a reference to the object */
 71 {
 72     struct foo    *tfp;
 73     int            idx;
 74 
 75     pthread_mutex_lock(&fp->f_lock);
 76     if (fp->f_count == 1) { /* last reference */
 77         pthread_mutex_unlock(&fp->f_lock);  //如果不解锁会怎么样呢?
 78         pthread_mutex_lock(&hashlock);    //如果顺序发生变化呢?
 79         pthread_mutex_lock(&fp->f_lock);
 80         /* need to recheck the condition */
 81         if (fp->f_count != 1) {
 82             fp->f_count--;
 83             pthread_mutex_unlock(&fp->f_lock);
 84             pthread_mutex_unlock(&hashlock);
 85             return;
 86         }
 87         /* remove from list */
 88         idx = HASH(fp);
 89         tfp = fh[idx];
 90         if (tfp == fp) {
 91             fh[idx] = fp->f_next;
 92         } else {
 93             while (tfp->f_next != fp)
 94                 tfp = tfp->f_next;
 95             tfp->f_next = fp->f_next;
 96         }
 97         pthread_mutex_unlock(&hashlock);
 98         pthread_mutex_unlock(&fp->f_lock);
 99         pthread_mutex_destroy(&fp->f_lock);
100         free(fp);
101     } else {
102         fp->f_count--;
103         pthread_mutex_unlock(&fp->f_lock);
104     }
105 }
复制代码

以上代码注意加锁的顺序,如果顺序错了,则会有可能出现死锁。

posted @ 2013-08-10 00:11  坚固66  阅读(1961)  评论(0编辑  收藏  举报