带围栏的SPSC线程安全

带围栏的SPSC线程安全,c,multithreading,synchronization,c11,atomicity,C,Multithreading,Synchronization,C11,Atomicity,我只希望我的代码尽可能简单和线程安全 使用C11原子 关于ISO/IEC 9899/201X草案第7.17.4部分围栏 X和Y,都作用于某个原子对象M,因此A是 在X之前排序,X修改M,Y在B之前排序,Y读取 由X写入的值或由中的任何副作用写入的值 假想的发布序列X如果是一个发布的话,它将是头 手术 此代码是线程安全的(将“w_i”作为“对象M”)? “w_i”和“r_i”是否都需要声明为原子的 如果只有Wii i是原子原子,主线程是否可以在缓存中保留一个旧的Rayi值,并将队列视为未满(满)并

我只希望我的代码尽可能简单和线程安全

使用C11原子

关于ISO/IEC 9899/201X草案第7.17.4部分围栏

X和Y,都作用于某个原子对象M,因此A是 在X之前排序,X修改M,Y在B之前排序,Y读取 由X写入的值或由中的任何副作用写入的值 假想的发布序列X如果是一个发布的话,它将是头 手术

此代码是线程安全的(将“w_i”作为“对象M”)?
“w_i”和“r_i”是否都需要声明为原子的 如果只有Wii i是原子原子,主线程是否可以在缓存中保留一个旧的Rayi值,并将队列视为未满(满)并写入数据? 如果我读一个没有原子负载的原子,会发生什么

我做了一些测试,但我所有的尝试似乎都给出了正确的结果。 然而,我知道我的测试在多线程方面并不正确:我运行了几次程序并查看了结果

即使w_i和r_i都不被声明为_原子,我的程序也能工作,但对于C11标准来说,仅仅围栏是不够的,对吗

typedef int rbuff_data_t;

struct rbuf {
    rbuff_data_t * buf;
    unsigned int bufmask;

    _Atomic unsigned int w_i;
    _Atomic unsigned int r_i;
};
typedef struct rbuf rbuf_t;

static inline int
thrd_tryenq(struct rbuf * queue, rbuff_data_t val) {
    size_t next_w_i;

    next_w_i = (queue->w_i + 1) & queue->bufmask;

    /* if ring full */
    if (atomic_load(&queue->r_i) == next_w_i) {
        return 1;
    }

    queue->buf[queue->w_i] = val;
    atomic_thread_fence(memory_order_release);
    atomic_store(&queue->w_i, next_w_i);

    return 0;
}

static inline int
thrd_trydeq(struct rbuf * queue, rbuff_data_t * val) {
    size_t next_r_i;

    /*if ring empty*/
    if (queue->r_i == atomic_load(&queue->w_i)) {
        return 1;
    }
    next_r_i = (queue->r_i + 1) & queue->bufmask;
    atomic_thread_fence(memory_order_acquire);
    *val = queue->buf[queue->r_i];
    atomic_store(&queue->r_i, next_r_i);
    return 0;
}
我将这些函数称为:
主线程将一些数据排队:

while (thrd_tryenq(thrd_get_queue(&tinfo[tnum]), i)) {
    usleep(10);
    continue;
}
其他线程退出数据队列:

static void *
thrd_work(void *arg) {
    struct thrd_info *tinfo = arg;
    int elt;

    atomic_init(&tinfo->alive, true);

    /* busy waiting when queue empty */
    while (atomic_load(&tinfo->alive)) {
        if (thrd_trydeq(&tinfo->queue, &elt)) {
            sched_yield();
            continue;
        }
        printf("Thread %zu deq %d\n",
                tinfo->thrd_num, elt);
    }

    pthread_exit(NULL);
}
带asm围栏

关于使用lfence和sfence的特定平台x86, 如果我删除所有C11代码并用

asm volatile ("sfence" ::: "memory");

(我对这些宏的理解是:防止重新组织/优化内存访问的编译器围栏+硬件围栏)

例如,我的变量是否需要声明为volatile


我已经看到了上面这个环形缓冲区代码,只有这些asm围栏,但没有原子类型,我真的很惊讶,我想知道这个代码是否正确。

我只是回答关于C11原子,平台细节太复杂了,应该逐步淘汰

C11中线程之间的同步只能通过一些系统调用(例如对于
mtx\u t
)和原子来保证。甚至都不要尝试在没有你的情况下去做

也就是说,同步化是通过原子来实现的,也就是说,副作用的可见性是通过原子效应的可见性来传播的。例如,对于最简单的一致性模型sequential,每当线程T2看到修改线程T1对原子变量a产生影响时,线程T1中该修改之前的所有副作用都对T2可见

因此,并非所有共享变量都需要是原子变量,您只需确保您的状态通过原子变量正确传播即可。从这个意义上讲,当您使用顺序或获得发布一致性时,围栏不会给您带来任何好处,它们只会使情况变得复杂

一些更一般的评论:

  • 因为您似乎使用了顺序一致性模型 默认情况下,原子操作的函数写入(例如 原子负载是多余的。仅仅计算原子变量是非常困难的 完全一样
  • 我的印象是你也在尝试优化 在你发展的早期。我认为你应该做一个实现 首先,你可以证明这是正确的。然后,当且仅当 如果您注意到性能问题,您应该开始考虑 优化。这种原子数据结构不太可能 是应用程序的真正瓶颈。你必须有一个非常好的假期 大量的线程,所有的同时锤打你的穷人 一个小的原子变量,可以看到一个可测量的瓶颈

谢谢!如果我避开了篱笆,我必须使用原子存储显式的内存顺序?或者我会写:queue->w_I=next_w_I(这是默认使用的放松顺序?),在这种情况下,我应该使用fences吗?不,默认顺序是顺序一致性,可能是最强的。
asm volatile ("lfence" ::: "memory");