【转】读APUE分析散列表的使用

最近学习APUE读到避免线程死锁的部分,看到部分源码涉及到避免死锁部分,源码使用了散列表来实现对结构(struct)的存储与查找。

本文不讨论代码中的互斥量部分。

  1 #include <stdlib.h>  
  2 #include <pthread.h>  
  3   
  4 #define NHASH 29  
  5 #define HASH(id) (((unsigned long)id)%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     int             f_id;  
 15     struct foo     *f_next; /* protected by hashlock */  
 16     /* ... more stuff here ... */  
 17 };  
 18   
 19 struct foo *  
 20 foo_alloc(int id) /* 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         fp->f_id = id;  
 28         if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {  
 29             free(fp);  
 30             return(NULL);  
 31         }  
 32         idx = HASH(id);  
 33         pthread_mutex_lock(&hashlock);  
 34         fp->f_next = fh[idx];  
 35         fh[idx] = fp;  
 36         pthread_mutex_lock(&fp->f_lock);  
 37         pthread_mutex_unlock(&hashlock);  
 38         /* ... continue initialization ... */  
 39         pthread_mutex_unlock(&fp->f_lock);  
 40     }  
 41     return(fp);  
 42 }  
 43   
 44 void  
 45 foo_hold(struct foo *fp) /* add a reference to the object */  
 46 {  
 47     pthread_mutex_lock(&fp->f_lock);  
 48     fp->f_count++;  
 49     pthread_mutex_unlock(&fp->f_lock);  
 50 }  
 51   
 52 struct foo *  
 53 foo_find(int id) /* find an existing object */  
 54 {  
 55     struct foo  *fp;  
 56   
 57     pthread_mutex_lock(&hashlock);  
 58     for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) {  
 59         if (fp->f_id == id) {  
 60             foo_hold(fp);  
 61             break;  
 62         }  
 63     }  
 64     pthread_mutex_unlock(&hashlock);  
 65     return(fp);  
 66 }  
 67   
 68 void  
 69 foo_rele(struct foo *fp) /* release a reference to the object */  
 70 {  
 71     struct foo  *tfp;  
 72     int         idx;  
 73   
 74     pthread_mutex_lock(&fp->f_lock);  
 75     if (fp->f_count == 1) { /* last reference */  
 76         pthread_mutex_unlock(&fp->f_lock);  
 77         pthread_mutex_lock(&hashlock);  
 78         pthread_mutex_lock(&fp->f_lock);  
 79         /* need to recheck the condition */  
 80         if (fp->f_count != 1) {  
 81             fp->f_count--;  
 82             pthread_mutex_unlock(&fp->f_lock);  
 83             pthread_mutex_unlock(&hashlock);  
 84             return;  
 85         }  
 86         /* remove from list */  
 87         idx = HASH(fp->f_id);  
 88         tfp = fh[idx];  
 89         if (tfp == fp) {  
 90             fh[idx] = fp->f_next;  
 91         } else {  
 92             while (tfp->f_next != fp)  
 93                 tfp = tfp->f_next;  
 94             tfp->f_next = fp->f_next;  
 95         }  
 96         pthread_mutex_unlock(&hashlock);  
 97         pthread_mutex_unlock(&fp->f_lock);  
 98         pthread_mutex_destroy(&fp->f_lock);  
 99         free(fp);  
100     } else {  
101         fp->f_count--;  
102         pthread_mutex_unlock(&fp->f_lock);  
103     }  
104 }  

取余法散列表:书中使用的是取余法来构建散列表,通过使用第5行定义的宏函数来计算(唯一计算)出每个ID(struct内部属性,保证struct唯一性)对应的散列表中的直接索引值。

而对于一个本例中已经构建出来的散列表,它的本质是这样的:

 

上图的0-15的实现实质其实也是指针(有自己指向的对象的Value值),这也是我之前没有弄明白的地方。

之前一直以为散列表是一个链表(就是里面存了,n个指针,每个指针相互首尾串联)。

然而不然,散列表的每个元素都是一个链表的头指针(即假设0-15都不为空,则一个散列表有16个相互独立的链表),添加新结构进入散列表的方法则是将结构本身代替索引位置的头指针,并指向他。

代码分析(背景灰色部分):

4-5:使用求余的方法构建散列函数,使得每一个ID都能通过散列函数计算出的值落中在散列表定义数组的索引区间内。

7:定义一个符合上述条件区间长度的数组,数组内的每个元素都是structfoo*类型,初始值为NULL。

foo_alloc(intid)部分:该函数的作用是在散列表中添加一个未初始化的id为id的新结构。

32:计算散列表中的索引(计算应该将新结构放在第几个链表里)。

34:将该结构的next节点指向目前该位置上链表的头结点。

35:将链表的头结点设置成自己的指针。

foo_find(intid)部分:该函数的作用是通过结构的id,在散列表中找到结构的指针。

58:通过散列函数计算出数组的索引值,并确保在该索引位置上的链表中从头到尾使用循环查找。

59-63:如果链表中的某个结点满足id值和传入的id值相等的条件,就确信已经找到了该结构,返回这个指针。

foo_rele(structfoo*fp)部分:释放这个引用(指针),如果这是最后一个引用释放这个对象的内存空间。

75:如果这是当前的引用计数是1(该指针是最后一个指向对象的指针)。

87-88:通过散列函数计算索引,并获取链表第一个结点的地址。

89-91:如果当前结点是首结点,将链表的首指针(该位置的指针)设置为该元素的next指针。

92-95:向后寻找结点,直到该节点(结点a)的next是目标元素(结点b)的指针,把结点a的next设置为结点b的next。

99:该结点已经不在散列表内,释放对象的内存。

 

注:非本博原创,原作者 http://baijiahao.baidu.com/s?id=1566359565923607&wfr=spider&for=pc    无线科技007

代码摘自 http://blog.csdn.net/abcef31415926/article/details/53898325

    原文作者:xyu1
    原文地址: https://www.cnblogs.com/xyu1/p/8030615.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞