0

最近研究glibc中线程本地存储是如何实现的,发现如下代码,实现了APIpthread_key_create()

int
__pthread_key_create (key, destr)
      pthread_key_t *key;
      void (*destr) (void *);
{
    /* Find a slot in __pthread_kyes which is unused.  */
    for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
    {
        uintptr_t seq = __pthread_keys[cnt].seq;

        if (KEY_UNUSED (seq) && KEY_USABLE (seq)
            /* We found an unused slot.  Try to allocate it.  */
            && ! atomic_compare_and_exchange_bool_acq (&__pthread_keys[cnt].seq,
                                                       seq + 1, seq))
        {
            /* Remember the destructor.  */
            __pthread_keys[cnt].destr = destr;

            /* Return the key to the caller.  */
            *key = cnt;

            /* The call succeeded.  */
            return 0;
       }
    }

    return EAGAIN;
}

__pthread_keys是所有线程访问的全局数组。我不明白为什么其成员的读取seq不同步,如下所示:

uintptr_t seq = __pthread_keys[cnt].seq;

虽然它在以后修改时是同步的。

仅供参考,__pthread_keys是一个类型为 的数组struct pthread_key_struct,其定义如下:

/* Thread-local data handling.  */
struct pthread_key_struct
{
    /* Sequence numbers.  Even numbers indicated vacant entries.  Note
       that zero is even.  We use uintptr_t to not require padding on
       32- and 64-bit machines.  On 64-bit machines it helps to avoid
       wrapping, too.  */
    uintptr_t seq;

    /* Destructor for the data.  */
    void (*destr) (void *);
};

提前致谢。

4

1 回答 1

0

在这种情况下,循环可以避免昂贵的锁获取。稍后完成的原子比较和交换操作( atomic_compare_and_exchange_bool_acq) 将确保只有一个线程可以成功增加序列值并将密钥返回给调用者。在第一步中读取相同值的其他线程将继续循环,因为 CAS 只能对单个线程成功。

这是有效的,因为序列值在偶数(空)和奇数(占用)之间交替。将值增加到奇数会阻止其他线程获取插槽。

与 CAS 指令相比,仅读取该值通常需要更少的周期,因此在执行 CAS 之前查看该值是有意义的。

有许多无等待和无锁算法利用 CAS 指令来实现低开销同步。

于 2013-04-21T08:57:43.527 回答