带围栏的SPSC线程安全
我只希望我的代码尽可能简单和线程安全 使用C11原子 关于ISO/IEC 9899/201X草案第7.17.4部分围栏 X和Y,都作用于某个原子对象M,因此A是 在X之前排序,X修改M,Y在B之前排序,Y读取 由X写入的值或由中的任何副作用写入的值 假想的发布序列X如果是一个发布的话,它将是头 手术 此代码是线程安全的(将“w_i”作为“对象M”)?带围栏的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值,并将队列视为未满(满)并
“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可见
因此,并非所有共享变量都需要是原子变量,您只需确保您的状态通过原子变量正确传播即可。从这个意义上讲,当您使用顺序或获得发布一致性时,围栏不会给您带来任何好处,它们只会使情况变得复杂
一些更一般的评论:
- 因为您似乎使用了顺序一致性模型 默认情况下,原子操作的函数写入(例如 原子负载是多余的。仅仅计算原子变量是非常困难的 完全一样
- 我的印象是你也在尝试优化 在你发展的早期。我认为你应该做一个实现 首先,你可以证明这是正确的。然后,当且仅当 如果您注意到性能问题,您应该开始考虑 优化。这种原子数据结构不太可能 是应用程序的真正瓶颈。你必须有一个非常好的假期 大量的线程,所有的同时锤打你的穷人 一个小的原子变量,可以看到一个可测量的瓶颈
asm volatile ("lfence" ::: "memory");