这是 C 语言中原子引用计数的正确实现吗?

问题描述 投票:0回答:1

给定以下约束,这段代码中的优化正确吗?

  • 在将对象传输到另一个线程之前,其引用计数会递增,并且
    is_shared
    设置为 true。
  • 每当对象的引用计数达到 1 时,
    is_shared
    就会设置为 false。
  • Header_is_unique
    100% 线程安全。

优化背后的想法是尝试减少原子操作的数量。

这是愚蠢的差事吗?如果不使用更多原子值和/或互斥体,这种优化是否不可能以线程安全的方式进行,从而使优化本身的想法无效?

我只想针对 GCC 和 Clang 编译器。

#include <stdint.h>

typedef struct Header {
    uint8_t is_shared;
    uint64_t reference_count;
} Header;

void
Header_acquire(
    Header * const self
) {
    if (!self->is_shared) {
        ++self->reference_count;
    } else {
        __atomic_fetch_add(&self->reference_count, 1, __ATOMIC_RELAXED);
    }
}

uint8_t
Header_release(
    Header * const self
) {
    if (!self->is_shared) {
        return !--self->reference_count;
    }

    uint64_t const reference_count =
        __atomic_fetch_sub(&self->reference_count, 1, __ATOMIC_RELEASE);

    if (reference_count == 2) {
        __atomic_thread_fence(__ATOMIC_ACQUIRE);
        self->is_shared = 0;
    }

    return reference_count == 1;
}

uint8_t
Header_is_unique(
    Header const * const self
) {
    return !self->is_shared && self->reference_count == 1;
}
c multithreading atomic reference-counting
1个回答
0
投票

不幸的是,您的实现存在竞争条件。

Header_release

 递减时,
is_shared
将把
reference_count
设置为 0。

但是,对

is_shared
的更改不是原子的。因此,比赛。

此外,当

Header_acquire
充分增加时,is_shared
不会
reference_count
设置为 1。

因此,为了缓解竞争,对

is_shared
的访问必须始终原子

发生这种情况是因为

is_shared
动态发生了变化。

当我使用类似的结构时,我总是仅在

初始化
期间将struct的类型设置为共享或私有。

这并不是一个很大的困难,因为通过正确/仔细的设计,我们总是知道给定的结构实例是否是共享的。或者,可能变得共享(这意味着它共享)。


这是我重构代码的方法。它消除了对

is_shared
的非原子访问。但是,它可能还有其他错误,可能在
Header_unique
(我在理解其用途时遇到了一些困难)

#include <stdint.h>
#include <stdlib.h>
#include <stdatomic.h>

typedef struct Header {
    uint8_t is_shared;
    uint8_t is_alloc;
    uint64_t reference_count;
} Header;

#define HDR_VALUES(_share,_alloc) \
    { .is_shared = _share, .is_alloc = _alloc }

#define HDR_SHARED(_sym) \
    Header _sym = HDR_VALUES(1,0)

#define HDR_PRIVATE(_sym) \
    Header _sym = HDR_VALUES(0,0)

HDR_SHARED(my_shared_header);
HDR_PRIVATE(my_private_header);

Header *
Header_new(Header *self,uint8_t is_shared)
{

    if (self == NULL) {
        self = malloc(sizeof(*self));
        self->is_alloc = 1;
    }
    else
        self->is_alloc = 0;

    self->is_shared = is_shared;

    self->reference_count = 0;

    return self;
}

void
Header_acquire(Header * const self)
{
    if (!self->is_shared) {
        ++self->reference_count;
    }
    else {
        atomic_fetch_add_explicit(&self->reference_count, 1, __ATOMIC_RELAXED);
    }
}

uint8_t
Header_release(Header * const self)
{
    if (!self->is_shared) {
        return !--self->reference_count;
    }

    uint64_t const reference_count =
        atomic_fetch_sub_explicit(&self->reference_count, 1, __ATOMIC_RELEASE);

#if 0
    if (reference_count == 2) {
        atomic_thread_fence(__ATOMIC_ACQUIRE);
        self->is_shared = 0;
    }
#endif

    return reference_count == 1;
}

uint8_t
Header_is_unique(Header const *const self)
{
#if 0
    return !self->is_shared && self->reference_count == 1;
#else
    uint8_t ret;
    if (self->is_shared)
        ret = 0;
    else
        ret = atomic_load(&self->reference_count) == 1;
    return ret;
#endif
}
© www.soinside.com 2019 - 2024. All rights reserved.