#include "memcached.h"
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
 
static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;
 
 
typedef  unsigned long  int  ub4;   /* unsigned 4-byte quantities */
typedef  unsigned       char ub1;   /* unsigned 1-byte quantities */
 
unsigned int hashpower = HASHPOWER_DEFAULT;
 
#define hashsize(n) ((ub4)1<<(n)) //2^n 次方  默认n是16(上面hashpower)
#define hashmask(n) (hashsize(n)-1) //0x1111 1111 1111 1111
 
static item** primary_hashtable = 0; //主hash表,注意理解,这个表只是存指针,没有存item
 
 
static item** old_hashtable = 0; //旧的hash表,在扩展hash表的时候用到
 
static unsigned int hash_items = 0; //hash表总数
 
static bool expanding = false;    //是不是正在扩展hash表中(通过线程assoc_maintenance_thread)
static bool started_expanding = false;
 
/*
 在扩展时,是以桶为粒度进行的,这是告诉我们扩张到哪个桶了。3
 从0 到 hashsize(hashpower - 1) - 1 
 */
static unsigned int expand_bucket = 0;
 
void assoc_init(const int hashtable_init) {
    if (hashtable_init) {
        hashpower = hashtable_init;
    }
    primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
    if (! primary_hashtable) {
        fprintf(stderr, "Failed to init hashtable.\n");
        exit(EXIT_FAILURE);
    }
    STATS_LOCK();
    stats.hash_power_level = hashpower;
    stats.hash_bytes = hashsize(hashpower) * sizeof(void *);
    STATS_UNLOCK();
}
 
/**
通过key查找item
*/
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
    item *it;
    unsigned int oldbucket;
 
 
    //hv & hashmask(hashpower)得到的是桶在hash表中的下标
    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        it = old_hashtable[oldbucket];
    } else {
        it = primary_hashtable[hv & hashmask(hashpower)]; //找出item所在的桶链表的首item
    }
 
    item *ret = NULL;
    int depth = 0;
    //遍历相同桶的链表,直到指定的key名为止。
    while (it) {
        //这里为什么要先判断长度?&&的执行过程是先判断左边,如果不为true,那右边的条件也不用判断了,
        //所以个人认为是为了判断memcmp(key, ITEM_key(it), nkey)的调用
        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
            ret = it;
            break;
        }
        it = it->h_next;
        ++depth;
    }
    MEMCACHED_ASSOC_FIND(key, nkey, depth);
    return ret;
}
 
/**
这里的查找过程和上面的assoc_find 基本一致,不同的地方在于:
这里返回的是指向 “指向当前item的指针”,并且引用当前item的上一个item的h_next,
所以这里返回的就是当前item在桶链表中的前一个item的h_next,这也是为什么命名叫
_hashitem_before的原因~
*/
 
static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
    item **pos;
    unsigned int oldbucket;
 
    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        pos = &old_hashtable[oldbucket];
    } else {
        pos = &primary_hashtable[hv & hashmask(hashpower)];
    }
 
    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
        pos = &(*pos)->h_next;
    }
    return pos;
}
 
/**
扩展哈希表
*/
static void assoc_expand(void) {
    old_hashtable = primary_hashtable;
 
    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
    if (primary_hashtable) {
        if (settings.verbose > 1)
            fprintf(stderr, "Hash table expansion starting\n");
        hashpower++;
        expanding = true;
        expand_bucket = 0;
        STATS_LOCK();
        stats.hash_power_level = hashpower;
        stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
        stats.hash_is_expanding = 1;
        STATS_UNLOCK();
    } else {
        primary_hashtable = old_hashtable;
        /* Bad news, but we can keep running. */
    }
}
 
/**
主要是唤醒哈希表维护线程,执行哈希表扩展工作。
*/
static void assoc_start_expand(void) {
    if (started_expanding)
        return;
    started_expanding = true;
    /**
     发送一个信号给正在处于阻塞等待状态的哈希表维护线程。见assoc_maintenance_thread
    */
    pthread_cond_signal(&maintenance_cond);
}
 
/* Note: this isn't an assoc_update.  The key must not already exist to call this */
/**
把item插入到hash表
*/
int assoc_insert(item *it, const uint32_t hv) {
    unsigned int oldbucket;
 
//    assert(assoc_find(ITEM_key(it), it->nkey) == 0);  /* shouldn't have duplicately named things defined */
 
    //hv & hashmask(hashpower)得到的是桶在hash表中的下标
 
    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        it->h_next = old_hashtable[oldbucket];
        old_hashtable[oldbucket] = it;
    } else {
        //哈希表难免会冲突,这里用链表保存相同桶下标的item
        //这里是把新的item放到桶的链表头
        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
        primary_hashtable[hv & hashmask(hashpower)] = it;
    }
 
    hash_items++;
    if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
        //当哈希表中的item数大于哈希表桶数的1.5倍时,开始扩展哈希表
        assoc_start_expand();
    }
 
    MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);
    return 1;
}
 
/**
从哈希表中删除某个item
*/
void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
    /**
    调用_hashitem_before取到指向 指向当前item的上一个item的h_next指针
    */
    item **before = _hashitem_before(key, nkey, hv);
 
    //下面利用before指针,把当前item的h_next指向0,把上一个item的h_next指向原来before的h_next达到删除作用
    if (*before) {
        item *nxt;
        hash_items--;
        /* The DTrace probe cannot be triggered as the last instruction
         * due to possible tail-optimization by the compiler
         */
        MEMCACHED_ASSOC_DELETE(key, nkey, hash_items);
        nxt = (*before)->h_next;
        (*before)->h_next = 0;   /* probably pointless, but whatever. */
        *before = nxt;
        return;
    }
    /* Note:  we never actually get here.  the callers don't delete things
       they can't find. */
    assert(*before != 0);
}
 
 
static volatile int do_run_maintenance_thread = 1;
 
#define DEFAULT_HASH_BULK_MOVE 1
int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
 
/**
哈希表维护线程工作时执行的函数
*/
static void *assoc_maintenance_thread(void *arg) {
 
    while (do_run_maintenance_thread) {
        int ii = 0;
 
        /* Lock the cache, and bulk move multiple buckets to the new
         * hash table. */
        item_lock_global();
        mutex_lock(&cache_lock);
 
        for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
            item *it, *next;
            int bucket;
 
            for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
                next = it->h_next;
 
                bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
                it->h_next = primary_hashtable[bucket];
                primary_hashtable[bucket] = it;
            }
 
            old_hashtable[expand_bucket] = NULL;
 
            expand_bucket++;
            if (expand_bucket == hashsize(hashpower - 1)) {
                expanding = false;
                free(old_hashtable);
                STATS_LOCK();
                stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
                stats.hash_is_expanding = 0;
                STATS_UNLOCK();
                if (settings.verbose > 1)
                    fprintf(stderr, "Hash table expansion done\n");
            }
        }
 
        mutex_unlock(&cache_lock);
        item_unlock_global();
 
        if (!expanding) {
            /* finished expanding. tell all threads to use fine-grained locks */
            switch_item_lock_type(ITEM_LOCK_GRANULAR);
            slabs_rebalancer_resume();
            /* We are done expanding.. just wait for next invocation */
            mutex_lock(&cache_lock);
            started_expanding = false;
            pthread_cond_wait(&maintenance_cond, &cache_lock); //等待条件变量,当条件到达时唤醒线程往下执行
            /* Before doing anything, tell threads to use a global lock */
            mutex_unlock(&cache_lock);
            slabs_rebalancer_pause();
            switch_item_lock_type(ITEM_LOCK_GLOBAL);
            mutex_lock(&cache_lock);
            assoc_expand();
            mutex_unlock(&cache_lock);
        }
    }
    return NULL;
}
 
static pthread_t maintenance_tid;
 
/**
启动哈希表维护线程
*/
int start_assoc_maintenance_thread() {
    int ret;
    char *env = getenv("MEMCACHED_HASH_BULK_MOVE");
    if (env != NULL) {
        hash_bulk_move = atoi(env);
        if (hash_bulk_move == 0) {
            hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
        }
    }
    if ((ret = pthread_create(&maintenance_tid, NULL,
                              assoc_maintenance_thread, NULL)) != 0) { //assoc_maintenance_thread为线程执行入口
        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
        return -1;
    }
    return 0;
}
 
/**
停止哈希表维护线程,在memcached服务退出时执行,见memcached.c中main函数,event_base_loop之后
*/
void stop_assoc_maintenance_thread() {
    mutex_lock(&cache_lock);
 
    /**
    发送信号assoc_maintenance_thread进入while循环相应的上下文,
    而设置do_run_maintenance_thread = 0让线程在下次while(do_run_maintenance_thread)语句
    中退出循环,线程退出。
    */
    do_run_maintenance_thread = 0;
    pthread_cond_signal(&maintenance_cond); 
    mutex_unlock(&cache_lock);
 
    pthread_join(maintenance_tid, NULL); //等待线程退出
}

Memcached源码分析之assoc.c的更多相关文章

  1. Memcached源码分析

    作者:Calix,转载请注明出处:http://calixwu.com 最近研究了一下memcached的源码,在这里系统总结了一下笔记和理解,写了几 篇源码分析和大家分享,整个系列分为“结构篇”和“ ...

  2. Memcached源码分析之请求处理(状态机)

    作者:Calix 一)上文 在上一篇线程模型的分析中,我们知道,worker线程和主线程都调用了同一个函数,conn_new进行事件监听,并返回conn结构体对象.最终有事件到达时,调用同一个函数ev ...

  3. Memcached源码分析之线程模型

    作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...

  4. Memcached源码分析之从SET命令开始说起

    作者:Calix 如果直接把memcached的源码从main函数开始说,恐怕会有点头大,所以这里以一句经典的“SET”命令简单地开个头,算是回忆一下memcached的作用,后面的结构篇中关于命令解 ...

  5. Memcached源码分析之内存管理

    先再说明一下,我本次分析的memcached版本是1.4.20,有些旧的版本关于内存管理的机制和数据结构与1.4.20有一定的差异(本文中会提到). 一)模型分析在开始解剖memcached关于内存管 ...

  6. memcached源码分析-----item过期失效处理以及LRU爬虫

    memcached源码分析-----item过期失效处理以及LRU爬虫,memcached-----item 转载请注明出处:http://blog.csdn.net/luotuo44/article ...

  7. Memcached源码分析——内存管理

    注:这篇内容极其混乱 推荐学习这篇博客.博客的地址:http://kenby.iteye.com/blog/1423989 基本元素item item是Memcached中记录存储的基本单元,用户向m ...

  8. Memcached源码分析——process_command函数解析

    以下为个人笔记 /** * process_command 在memcached中是用来处理用户发送的命令的, * 包括get set,add,delete,replace,stats,flush_a ...

  9. memcached源码分析一-slab

    Slab作为一种内存管理方案,其作用主要有以下2点: a) 避免频繁的内存分配释放造成的内存碎片 b) 减少内存分配操作产生的性能开销 Linux内核数据结构中也有slab的设计,Linux提供了一套 ...

随机推荐

  1. CodeForces#378--A, B , D--暴力出奇迹....

    A题 A. Grasshopper And the String time limit per test 1 second memory limit per test 256 megabytes in ...

  2. ACM课程学习总结

    ACM课程学习总结报告 通过一个学期的ACM课程的学习,我学习了到了许多算法方面的知识,感受到了算法知识的精彩与博大,以及算法在解决问题时的巨大作用.此篇ACM课程学习总结报告将从以下方面展开: 学习 ...

  3. Lucene 简单手记http://www.cnblogs.com/hoojo/archive/2012/09/05/2671678.html

    什么是全文检索与全文检索系统? 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查 ...

  4. 转:jmeter实践

    本文主要介绍性能测试中的常用工具jmeter的使用方式,以方便开发人员在自测过程中就能自己动手对系统进行自动压测和模拟用户操作访问请求.最后还用linux下的压测工具ab做了简单对比. 1.      ...

  5. CentOS Hadoop安装配置详细

    总体思路,准备主从服务器,配置主服务器可以无密码SSH登录从服务器,解压安装JDK,解压安装Hadoop,配置hdfs.mapreduce等主从关系. 1.环境,3台CentOS7,64位,Hadoo ...

  6. android AsyncTask介绍 转载

    http://www.cnblogs.com/devinzhang/archive/2012/02/13/2350070.html AsyncTask和Handler对比 1 ) AsyncTask实 ...

  7. iOS开发-正则表达式3种形式

    转至:http://www.cnblogs.com/GarveyCalvin/p/4250145.html iOS开发-正则表达式的使用方法 前 言:在表单验证中,我们经常会使用到正则,因为我们需要用 ...

  8. SD卡的SPI模式的初始化顺序(转)

    为了使SD卡初始化进入SPI模式,我们需要使用的命令有3个:CMD0,ACMD41,CMD55(使用ACMD类的指令前应先发CMD55,CMD55起到一个切换到ACMD类命令的作用). 为什么在使用C ...

  9. Request 地址栏传值

    request页面 protected void btnSearch_Click(object sender, EventArgs e) { Response.Redirect("Reque ...

  10. 转:lr_eval_string,lr_save_string 和 sprintf 的使用

    lr_eval_string,lr_save_string和 sprintf 函数使用介绍 一.lr_eval_string 使用介绍1.函数的主要作用:返回脚本中的一个参数当前的值,返回值类型:ch ...