微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Vala 如何使用多线程处理引用计数?

如何解决Vala 如何使用多线程处理引用计数?

据我所知,在多线程环境中,引用计数应该通过锁定来执行,以确保所有线程看到相同的内存快照。但是锁定会降低性能。 Vala 是如何解决这个问题的?

解决方法

引用计数主要在 GObject 中处理(对于 GLib.Object 派生类型),它反过来使用 GLib 中的 Atomic Operations。原子是一个棘手的话题。如果您想深入了解细节,最好从 Herb Sutter 几年前的 Atomic Weapons 演讲开始。我建议您观看这些视频,即使您永远不会使用它们(并且 99.9% 的程序员不应该使用它们),因为它会让您更好地了解计算机的实际工作原理。

“原子”这个名字可能有点误导;这不是真正关于 atomicicity,尽管这是其中的一部分。从某种意义上说,这些操作是原子的,即更改要么全部进行,要么根本不进行,这很重要,但更有趣的部分是原子充当障碍,阻止编译器重新跨越障碍的排序操作。赫伯·萨特 (Herb Sutter) 的演讲详细介绍了这一点,我不会在这里重复。

例如,考虑一个简单的不受保护的引用计数器:

typedef struct {
  int reference_count = 0;
} Foo;

Foo* foo_create(void) {
  Foo* foo = malloc(sizeof(Foo));
  foo->reference_count = 1;
}

void ref(Foo* foo) {
  ++(foo->reference_count);
}

void unref(Foo* foo) {
  if (--(foo->reference_count) == 0) {
    free(foo);
  }
}

我假设您可以看到不加保护的问题,因为我正在写一篇 SO 帖子而不是一本书。

我们感兴趣的特定原子操作是 compare-and-swap (CAS),它基本上提供了安全执行此操作的能力:

bool cas(int* value,int* expected,int desired) {
  if (*value == *expected) {
    *value = desired;
    return true;
  } else {
    return false;
  }
}

使用它,我们将上面的引用计数实现更改为:

typedef struct {
  int reference_count = 0;
} Foo;

Foo* foo_create(void) {
  Foo* foo = malloc(sizeof(Foo));
  /* No atomics needed,we haven't made the value public yet */
  foo->reference_count = 1;
}

void ref(Foo* foo) {
  int old_refcount;
  int new_refcount;
  do {
    current_refcount = foo->reference_count;
    new_refcount = current_refcount + 1;
  } while (!cas (&(foo->reference_count),&old_refcount,new_refcount))
}

void unref(Foo* foo) {
  int old_refcount;
  int new_refcount;
  do {
    current_refcount = foo->reference_count;
    new_refcount = current_refcount - 1;
  } while (!cas (&(foo->reference_count),new_refcount));

  if (new_refcount == 0) {
    free(foo);
  } else if (new_recount < 0) {
    // Double-free bug,code should not be reached!
  }
}

但是锁定会降低性能。

原子也是如此。很多。但也比更高级别的锁要少得多。一方面,如果您使用互斥锁,您所做的基本上是:

  • 获取锁。
  • 执行操作。
  • 解除锁定。

对于原子,我们基本上是在乞求宽恕而不是征求许可:

  • 尝试执行操作。

然后我们只看操作是否成功(即,如果 cas() 返回 true)。

操作也小了很多,速度也快了很多;使用互斥体,您可能会获取锁然后读取当前值,递增/递减它,然后释放锁。使用原子,CAS 操作被包裹在单个 CPU 指令中。

CPU 仍然必须通过确保下次任何其他内核(有点过于简化,因为即使在一个内核内也有多个缓存)要求读取数据时处理缓存一致性问题,并提供新数据。换句话说,原子引用计数对性能不利,但比互斥锁要好得多。坦率地说,如果您想要引用计数而不是跟踪垃圾收集,原子几乎是您最不坏的选择。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?