给定以下约束,这段代码中的优化正确吗?
is_shared
设置为 true。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;
}
不幸的是,您的实现存在竞争条件。
当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
}