引子:线程死锁曾是多少程序员的噩梦,每每为此食不甘味,夜不成寐,一句话:苦不堪言。本文从几个场景入手,试图解开产生死锁的原因之谜。
教科书:说的很具体,理解很抽象
关于死锁产生的原因《操作系统》中有比较好的说明:
(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 }
以上代码注意加锁的顺序,如果顺序错了,则会有可能出现死锁。