C++并发编程(八):原子类型与数据竞争

本文探讨了C++中的非原子类型及其可能导致的数据竞争问题。非原子操作在多线程环境中可能不完整,导致写撕裂和读撕裂。为避免这种情况,文章介绍了原子类型的使用,确保操作的原子性,防止数据竞争,保证程序的正确性。通过实例说明,原子类型在多线程中的重要性,强调不能将原子变量简单替换为非原子变量,以防止未定义行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

非原子类型  


 C/C++中所有的操作默认为非原子操作。非原子操作,并不保证操作的完整性。当一个操作由2个以上的指令完成时,操作可能只执行了一个指令,变量就被另外一个线程抢占。当多个线程同时读写同一个变量时,就会发生数据竞争。

uint64_t i = 0;
void foo(){
    i = 1;
}

上述简单的赋值语句,通过一个32位的系统编译,这个操作就需要分别负责高32位和低32位的两条指令来完成。对于非原子的这两条指令,任意线程都可以在它们期间去访问变量。

当一个线程抢占在这两个指令之间调用变量,变量只修改了低32位就被其他线程调用,此时会发生写撕裂

读取情况与赋值情况类似,当一个线程抢占两个读取指令之间去修改变量,此时变量只读取了低32位,此时会发生读撕裂

 多个线程同时调用同一个变量,当其中一个线程执行写操作时,必须使用原子类型。

原子类型


原子类型的操作属于原子操作,对于它们的操作只有2种状态,完成和不做。不会出现操作进行一半而被其他线程抢占的情况。

#include <atomic>
#include <thread>
#include <assert.h>
 
bool x=false;  // x现在是一个非原子变量
std::atomic<bool> y;
std::atomic<int> z;
 
void write_x_then_y()
{
  x=true;  // 1 在栅栏前存储x
  std::atomic_thread_fence(std::memory_order_release);
  y.store(true,std::memory_order_relaxed);  // 2 在栅栏后存储y
}
 
void read_y_then_x()
{
  while(!y.load(std::memory_order_relaxed));  // 3 在#2写入前,持续等待
  std::atomic_thread_fence(std::memory_order_acquire);
  if(x)  // 4 这里读取到的值,是#1中写入
    ++z;
}
int main()
{
  x=false;
  y=false;
  z=0;
  std::thread a(write_x_then_y);
  std::thread b(read_y_then_x);
  a.join();
  b.join();
  assert(z.load()!=0);  // 5 断言将不会触发
}

上述代码,实现原子类型来对非原子类型操作排序的的功能。是否可以将原子变量y替换为非原子变量呢?答案是否定的。

对于变量y,线程a和线程b会同时进行访问,线程b不断读取等待线程a将y值修改。当变量y为非原子类型时,该代码就会发生数据竞争,引发“未定义行为”。

当使用原子类型进行操作排序时,“自由”内存顺序和非原子类型的操作顺序就等价了,可以将“自由”内存顺序的变量,直接替换为非原子类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值