Chinaunix首页 | 论坛 | 博客
  • 博客访问: 550666
  • 博文数量: 469
  • 博客积分: 50
  • 博客等级: 民兵
  • 技术积分: 1495
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-15 21:04
文章分类

全部博文(469)

文章存档

2015年(81)

2014年(125)

2013年(261)

2012年(2)

分类: C/C++

2013-08-27 11:50:01

原文地址:memcached refcount 个人见解 作者:djjsindy

       在多线程环境中,前面memcached item锁粒度中可以看出在thread.c中那些item_getitem_link等,item的一些基本操作都会根据锁的类型(分段锁或者是全局锁),严格的保证在多线程环境中的原子性,但是memcached中通过libevent得到的客户端请求,然后处理命令,函数是process_command,可以看到这个函数是不加锁的,也就是说通过原子操作调用了item_get操作后得到了item,对item后会对他进行一系列操作都是不加锁的,这样如果两个线程在先后都得到了同一个item,一个是删除item,一个是读取item,删除的item的操作在时间点上先到达,那么直接释放item的空间给slab会产生问题。

       个人认为通过itemrefcount变量控制itemrefcount主要是保证在多线程环境中item的内存空间,不被其他操作同一item的线程释放空间,我认为这个变量的值含义是:表示有多个线程的多少个地方引用着这块item空间,可想而知,在函数中取得了item,表示了有一个线程的函数引用了这块item内存区域。在函数退出后,lruhash表中会同时引用这块空间,例如有3个线程同时引用这个item1个从lru中取得这个item,另外两个从hash结构中取出item,那么这个refcount应该是3.在函数调用后应该是释放占用的这个refcount引用,通过调用item_remove函数,使得refcount1.通过前面描述我们可以知道,正常情况下如果一个item被插入到了hashlru中后,函数退出后,refcount=1,表示hash结构和lru队列依然对这块item内存区域保持引用,使得其他线程不能释放这块item内存。

对item的refcount操作是原子操作,代码:thread.c


  1.  85 unsigned short refcount_incr(unsigned short *refcount) {
  2.  86 #ifdef HAVE_GCC_ATOMICS         //GCC内建原子操作
  3.  87    return __sync_add_and_fetch(refcount, 1);
  4.  88 #elif defined(__sun)
  5.  89    return atomic_inc_ushort_nv(refcount);
  6.  90 #else
  7.  91    unsigned short res;
  8.  92    mutex_lock(&atomics_mutex); //通过信号量实现原子性
  9.  93    (*refcount)++;              //refcount加1
  10.  94    res = *refcount;
  11.  95    mutex_unlock(&atomics_mutex);
  12.  96    return res;
  13.  97 #endif
  14.  98 }

      这样在多线程环境下,refcount_incrrefcount_decr都是原子性操作。后面看下memcached是如何利用refcount限制行为的。下面的代码会把itemrefcount 变为1,不管是从slab中新分配的item或者是从lru中拿出来的item

代码:items.c
  1. 187 if (!tried_alloc && (tries == 0 || search == NULL)) //没试过分配并且lru中没有取到item
  2. 188    it = slabs_alloc(ntotal, id); //从slab中重新分配
  3. 189
  4. 190 if (it == NULL) {
  5. 191    itemstats[id].outofmemory++;
  6. 192    mutex_unlock(&cache_lock);
  7. 193    return NULL;
  8. 194 }
  9. 195
  10. 196 assert(it->slabs_clsid == 0);
  11. 197 assert(it != heads[id]);
  12. 198
  13. 199 /* Item initialization can happen outside of the lock; the item's already
  14. 200 * been removed from the slab LRU.
  15. 201 */
  16. 202 it->refcount = 1; /* the caller will have a reference */ 
  17.     //统一refcount都为1,表示当前引用到了这块item内存

     在插入lru队列和插入hash结构后,item的内存在这两种数据结构中都有引用,应该加引用加1

  1. 305 ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
  2. 306 assoc_insert(it, hv);
  3. 307 item_link_q(it);
  4. 308 refcount_incr(&it->refcount); //refcount引用加1
  5. 309 mutex_unlock(&cache_lock);

     item_remove函数可以认为是在函数的最后,还原函数引用的item内存空间(因为在之前函数引用item时已经加了1,使用完之后需要减1

  1. 345 void do_item_remove(item *it) {
  2. 346    MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);
  3. 347    assert((it->it_flags & ITEM_SLABBED) == 0);
  4. 348
  5. 349    if (refcount_decr(&it->refcount) == 0) { //如果只有一个引用item,就释放这块内存空间给slab
  6. 350       item_free(it);
  7. 351    }
  8. 352 }

     在每次从hash结构中取得了item,取得了它的引用,这是需要对这个item的引用次数加1

  1. 521 item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {
  2. 522 //mutex_lock(&cache_lock);
  3. 523    item *it = assoc_find(key, nkey, hv);
  4. 524    if (it != NULL) {
  5. 525       refcount_incr(&it->refcount); //refcount加1
    slab_move中,对于slab_move需要了解下的可以看前面的blog,有对refcount的操作

  1. 527 refcount = refcount_incr(&it->refcount); //当前函数引用了item,所以refcount+1
  2. 528 if (refcount == 1) { /* item is unlinked, unused */ 
  3.       //等于1,证明加1之前是0,那么item就是未分配。
  4. 529 if (it->it_flags & ITEM_SLABBED) { //证明了这个已经在slab中了 
  5. 530 /* remove from slab freelist */
  6. 531 if (s_cls->slots == it) {
  7. 532    s_cls->slots = it->next;
  8. 533 }
  9. 534 if (it->next) it->next->prev = it->prev;
  10. 535 if (it->prev) it->prev->next = it->next;
  11. 536 s_cls->sl_curr--;
  12. 537 status = MOVE_DONE;
  13. 538 } else {
  14. 539    status = MOVE_BUSY; //这个item 未初始化完成
  15. 540 }
  16. 541 } else if (refcount == 2) { /* item is linked but not busy */ 
  17.       //说明这个item 在lru和hash结构中
  18. 542 if ((it->it_flags & ITEM_LINKED) != 0) { //如果这个item还在这个lru,hash结构中
  19. 543    do_item_unlink_nolock(it, hash(ITEM_key(it), it->nkey, 0));
  20. 544    status = MOVE_DONE;
  21. 545 } else { 
  22.      //这个说明item的标志已经改变了,但是还未从lru和hash中删除item
  23. 546 /* refcount == 1 + !ITEM_LINKED means the item is being
  24. 547 * uploaded to, or was just unlinked but hasn't been freed
  25. 548 * yet. Let it bleed off on its own and try again later */
  26. 549     status = MOVE_BUSY;

     上面的这种情况545行这个分支如果在下面代码319-324行就会出现这种情况

  1. 314 void do_item_unlink(item *it, const uint32_t hv) {
  2. 315    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
  3. 316    mutex_lock(&cache_lock);
  4. 317    if ((it->it_flags & ITEM_LINKED) != 0) {
  5. 318       it->it_flags &= ~ITEM_LINKED; //item的标志已经改变了,但是引用还没减1
  6. 319       STATS_LOCK();
  7. 320       stats.curr_bytes -= ITEM_ntotal(it);
  8. 321       stats.curr_items -= 1;
  9. 322       STATS_UNLOCK();
  10. 323       assoc_delete(ITEM_key(it), it->nkey, hv);
  11. 324       item_unlink_q(it);
  12. 325       do_item_remove(it);          //直到这里refcount才会减1
  13. 326    }
  14. 327    mutex_unlock(&cache_lock);
  15. 328 }

      总结:通过上面的分析,个人认为refcount是为了防止在多线程环境中,某个线程删除了其他线程引用的那段item内存,认为这是一种折中的办法,不用在item的操作上面加锁,又在必要的时候避免了其他其他的线程改变了当前线程用的item那段内存,这样大大提高了item操作的并发性。

              

阅读(622) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~