关于C内存序模型理解正确性及数据竞争的技术问询
C语言内存序模型练习:生产者-消费者代码分析
背景
正在学习C语言内存序模型,编写了一段生产者与消费者共享带“units”的“bucket”的代码,意图在生产者(P)与消费者(C)间建立如下happens before(hb)关系序列:
- P: 写入bucket(填充)
- P: release存储bucket_full
- C: acquire加载bucket_full
- C: 读写bucket(递减、打印)
- C: release存储bucket_full
- P: acquire加载bucket_full
- P: 写入bucket(循环重复)
注:我知道有更简洁的实现生产者-消费者的方式,此代码仅为练习
问题
- 我是否正确建立了上述happens before关系?
- 代码是否实际存在数据竞争,只是未显现?
代码实现
#include <stdio.h> #include <stdatomic.h> #include <pthread.h> int num_buckets = 30, bucket = 0, units_per_bucket = 12; _Atomic int bucket_full = 0; void *producer(void *arg){ for(int i=0; i<num_buckets+1; ++i){ //acquire load to wait for "empty bucket" signal while(atomic_load_explicit(&bucket_full, memory_order_acquire)) sched_yield(); if(i == num_buckets){ // no more buckets to fill atomic_store_explicit(&bucket_full, -1, memory_order_release); break; } bucket = units_per_bucket; // fill bucket printf("\n[%2i]: %d", i+1, bucket); // release store to signal "bucket is full" atomic_store_explicit(&bucket_full, 1, memory_order_release); } return arg; } void *consumer(void *arg){ while(1){ //acquire load to wait for "bucket is full" signal while(!atomic_load_explicit(&bucket_full, memory_order_acquire)) sched_yield(); if(bucket_full == -1) //no more buckets, exit break; bucket --; printf(" %d", bucket); if(bucket < 1) // release store to signal "bucket is empty" atomic_store_explicit(&bucket_full, 0, memory_order_release); } return arg; } int main(){ pthread_t t0, t1; pthread_create(&t0, NULL, consumer, (void *)NULL); pthread_create(&t1, NULL, producer, (void *)NULL); pthread_join(t0, NULL); pthread_join(t1, NULL); printf("\n"); return 0; }
编译命令
gcc -std=c11 -pthread -O2 -Wall -Wextra -Werror -pedantic -pedantic-errors main.c -o main
问题解答
1. 是否正确建立目标happens before关系?
核心的happens before链条是正确建立的,完全符合预期:
- 生产者填充
bucket的操作在memory_order_release语义的bucket_full=1存储之前,因此前者happens before后者; - 消费者通过
memory_order_acquire语义加载到bucket_full=1时,根据release-acquire同步规则,生产者的bucket_full=1存储操作happens before消费者的这次加载操作,进而消费者后续对bucket的递减、读取操作都happens after生产者的填充操作; - 消费者将
bucket减至0后,执行memory_order_release语义的bucket_full=0存储,该操作happens before生产者通过memory_order_acquire加载到bucket_full=0的操作,进而生产者下一次填充bucket的操作happens after消费者的清空操作; - 循环过程中上述链条会重复,形成连贯的happens before序列。
但代码存在一个瑕疵:消费者中if(bucket_full == -1)是直接以非原子方式读取原子变量,不符合C标准对原子变量的访问要求(原子变量必须通过原子操作访问),可能导致编译器优化后无法及时读取到生产者设置的-1值,但这并不影响核心的happens before关系链条。
2. 是否存在数据竞争?
针对bucket的访问不存在数据竞争,原因如下:
数据竞争的定义是:两个不同线程对同一非原子内存位置的访问,至少一个是写入操作,且两者之间没有happens before关系。
- 生产者仅在通过acquire加载确认
bucket_full=0后才会写入bucket,此时消费者已经完成了对bucket的清空操作(消费者只会在bucket<1时才会release存储bucket_full=0),因此生产者的写入与消费者之前的读写存在明确的happens before关系; - 消费者仅在通过acquire加载确认
bucket_full=1后才会读写bucket,此时生产者已经完成填充,消费者的读写与生产者的写入存在明确的happens before关系; - 消费者多次递减
bucket的过程中,生产者会持续等待bucket_full=0,不会访问bucket,不存在并发访问。
但需注意:消费者直接非原子读取原子变量bucket_full的行为属于未定义行为,虽然这不是数据竞争,但可能导致程序行为异常(比如无法正确退出循环),建议改为if(atomic_load_explicit(&bucket_full, memory_order_acquire) == -1)。
内容的提问来源于stack exchange,提问作者onlycparra
相关产品推荐
相关产品推荐

