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


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

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

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

      (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 }

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

  • 相关阅读:
    2019/9/10
    2019/9/9
    软件课程设计(21)
    软件课程设计(20)
    软件课程设计(19)
    软件课程设计(18)
    软件课程设计(17)
    软件课程设计(16)
    数风流人物,还看今朝
    峰回路转二十四天
  • 原文地址:https://www.cnblogs.com/hadoopdev/p/3247406.html
Copyright © 2020-2023  润新知