C中的参考计数

时间:2014-10-25 08:26:48

标签: c gcc pthreads atomic

尝试使用引用计数在普通C中的线程之间传递结构。我有pthreads和gcc原子可用。我可以让它工作,但我正在寻找防弹。

首先,我使用了结构本身拥有的pthread互斥锁:

struct item {
  int ref;
  pthread_mutex_t mutex;
};

void ref(struct item *item) {
  pthread_mutex_lock(&item->mutex);
  item->ref++;
  pthread_mutex_unlock(&item->mutex);
}

void unref(struct item *item) {
  pthread_mutex_lock(&item->mutex);
  item->ref--;
  pthread_mutex_unlock(&item->mutex);
  if (item->ref <= 0)
    free(item);
}

struct item *alloc_item(void) {
  struct item *item = calloc(1, sizeof(*item));
  return item;
}

但是,意识到互斥锁不应归项目所有:

static pthread_mutex_t mutex;
struct item {
  int ref;
};

void ref(struct item *item) {
  pthread_mutex_lock(&mutex);
  item->ref++;
  pthread_mutex_unlock(&mutex);
}

void unref(struct item *item) {
  pthread_mutex_lock(&mutex);
  item->ref--;
  if (item->ref <= 0)
    free(item);
  pthread_mutex_unlock(&mutex);
}

struct item *alloc_item(void) {
  struct item *item = calloc(1, sizeof(*item));
  return item;
}

然后,进一步实现的指针按值传递,所以我现在有:

static pthread_mutex_t mutex;
struct item {
  int ref;
};

void ref(struct item **item) {
  pthread_mutex_lock(&mutex);
  if (item != NULL) {
    if (*item != NULL) {
      (*item)->ref++;
    }
  }
  pthread_mutex_unlock(&mutex);
}

void unref(struct item **item) {
  pthread_mutex_lock(&mutex);
  if (item != NULL) {
    if (*item != NULL) {
      (*item)->ref--;
      if ((*item)->ref == 0) {
        free((*item));
        *item = NULL;
      }
    }
  }
  pthread_mutex_unlock(&mutex);
}

struct item *alloc_item(void) {
  struct item *item = calloc(1, sizeof(*item));
  if (item != NULL)
    item->ref = 1;
  return item;
}

这里有任何逻辑错误吗?谢谢!

1 个答案:

答案 0 :(得分:1)

我不知道通用解决方案。

能够将其减少到引用计数的原子加/减,这将是很好的。事实上,大部分时间都是必需的...所以踩过互斥体或其他伤害

但真正的问题是同时管理引用计数和指向项目的指针。

当一个帖子来到ref()一个项目时,它是如何找到它的?如果它还不存在,那么可能它必须创建它。如果它已经存在,它必须避免一些其他线程在引用计数递增之前释放它。

所以...你的void ref(struct item** item)的工作原理是互斥锁保护struct item**指针...当你持有互斥锁时,没有其他线程可以改变指针 - 所以只有一个线程可以创建项目(并递增计数0-> 1),并且只有一个线程可以销毁该项目(在递减计数1-> 0之后)。

据说计算机科学中的许多问题可以通过引入新的间接层来解决,这就是这里发生的事情。问题是所有线程如何获得项目的地址 - 假设它可能(轻柔地和突然地)消失了?答:发明间接水平。

但是,现在我们假设指向该项目的指针本身不会消失。如果指向项目的指针可以保持全局进程(静态存储持续时间),则可以简单地实现这一点。如果指向该项的指针是已分配的存储持续时间对象(的一部分),那么我们必须确保以某种方式锁定该更高级别的对象 - 以便在使用该项时指向该项的指针的地址是“稳定的” 。也就是说,更高级别的对象不会在内存中移动,并且在我们使用它时不会被破坏!

因此,锁定互斥锁后的检查if (item == NULL)是可疑的。如果互斥锁也保护指向该项的指针,则该需要在建立指向该项的指针的地址之前锁定 - 在这种情况下,在锁定之后检查为时已晚。或者指向项目的指针的地址以其他方式受到保护(可能是通过另一个互斥锁) - 在这种情况下,检查可以在锁定之前完成(并且在那里移动它可以清楚地表明互斥锁保护什么,以及它保护)。

但是,如果该项目是较大数据结构的一部分,并且该结构已被锁定,则您可能(根本)不需要锁定来覆盖指向该项目的指针。这取决于...正如我所说,我不知道一般解决方案。

我有一些大型动态数据结构(哈希表,队列,树等),它们由许多线程共享。大多数情况下,线程会查找并保留一段时间的项目。当系统繁忙时,它非常繁忙,并且可以推迟项目的销毁,直到事情变得更安静。所以我在大型结构上使用读/写锁,对引用计数使用原子加/减,使用垃圾收集器来实际销毁项。这里的要点是,引用计数(显然简单和自包含)递增/递减的机制选择取决于如何管理项的创建和销毁,以及线程如何拥有指向一个项目(毕竟这是参考计数的重要性)。


如果您手头有128位原子操作,可以将64位地址和64位引用计数放在一起,并执行以下操作:

ref:   bar = fetch_add(*foo, 1) ;
       ptr = bar >> 64 ;
       if (ptr == NULL)
         {
            if (bar & 0xF...F)
              ...create item etc.
            else
              ...wait for item
         } ;

unref: bar = fetch_sub(*foo, 1) ;
       if ((bar & 0xF...F) == 0)
         {
            if (cmp_xchg(*foo, bar, (NULL << 64) | 0)))
              ...free(bar >> 64) ;
         } ;

其中foo是128位组合ptr / ref-count(其存在受某些外部手段保护) - 假设64位ptr和64位计数 - 和bar是该表单的局部变量,ptr是void *。

如果找到指针NULL触发项目创建,则从0-> 1移动计数的第一个线程知道它们是谁,以及在创建项目之前到达的任何线程,以及指针集合,也知道他们是谁,可以等待。设置指针需要cmp_xchg(),然后创建者会发现有多少线程在等待它。

此机制将引用计数移出项目,并将其与项目的地址捆绑在一起,这看起来很整齐 - 尽管您现在需要操作该项目的地址,以及参考地址当你操作它的引用计数时,到项目。

这取代了refunref函数中的互斥锁...但不解决引用本身受保护的问题。