并发编程中Data Race与Atomicity Violation的区别是什么?
原子性违规(Atomicity Violation)与数据竞争(Data Race)的区别
一、原子性违规(Atomicity Violation)
原子性违规的核心是代码逻辑错误地假设一组多步内存操作是原子的(不可打断),但实际上这些操作可以被其他线程抢占,最终导致逻辑结果不符合预期。它属于**竞态条件(Race Condition)**的典型场景,但不一定涉及未同步的内存访问。
示例:依赖非原子逻辑的计数器
假设我们用C标准库的原子变量实现一个上限为10的计数器,但错误地将“判断+自增”拆成两个独立的原子操作:
#include <stdatomic.h> #include <pthread.h> #include <stdio.h> atomic_int count = ATOMIC_VAR_INIT(0); void* increment(void* arg) { // 错误假设:"读取count + 判断 + 自增"是原子操作 if (atomic_load(&count) < 10) { // 两个线程可能同时通过判断,导致count超过10 atomic_fetch_add(&count, 1); } return NULL; } int main() { pthread_t t1, t2; pthread_create(&t1, NULL, increment, NULL); pthread_create(&t2, NULL, increment, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); // 可能输出11,而非预期的10 printf("count: %d\n", atomic_load(&count)); return 0; }
这里每个内存操作都是原子的,不存在未同步的访问,但由于代码假设“判断+自增”是一个不可分割的原子逻辑,最终导致计数器超出上限——这就是原子性违规。
二、数据竞争(Data Race)
数据竞争是C标准明确定义的未行为(Undefined Behavior):当两个线程同时访问同一内存位置,且至少有一个操作是写操作,同时没有使用同步机制(互斥锁、原子操作、内存屏障等)来协调这些访问时,就会触发数据竞争。
示例:未同步的全局变量自增
#include <pthread.h> #include <stdio.h> int global_count = 0; void* increment(void* arg) { // global_count +=1 是读-改-写三步非原子操作 global_count++; return NULL; } int main() { pthread_t threads[100]; for (int i = 0; i < 100; i++) { pthread_create(&threads[i], NULL, increment, NULL); } for (int i = 0; i < 100; i++) { pthread_join(threads[i], NULL); } // 可能输出小于100的数,甚至出现不可预测的值 printf("global_count: %d\n", global_count); return 0; }
这里多个线程同时对global_count执行读-改-写操作,没有任何同步保护,属于典型的数据竞争。程序的行为完全不可预测,最终结果可能远小于预期的100,甚至出现奇怪的数值(因CPU缓存同步问题)。
核心区别总结
| 维度 | 原子性违规 | 数据竞争 |
|---|---|---|
| 定义核心 | 错误假设多步逻辑为原子操作 | 未同步的并发读写访问 |
| 标准合规性 | 代码可能符合C标准(如用了原子操作) | 代码违反C标准,属于未定义行为 |
| 触发原因 | 逻辑设计错误,依赖非原子的复合操作 | 缺少同步机制,内存访问无协调 |
| 关联关系 | 可能由数据竞争导致,但不限于数据竞争 | 可能引发原子性违规,但本身是独立问题 |
内容的提问来源于stack exchange,提问作者persephone
相关产品推荐
相关产品推荐

