在原子变量一中做了原子变量的科普介绍,仅仅将普通变量升级为原子变量,便解决了多线程环境下的数据竞争问题。在应对如上的简单案例时,仅仅使用原子变量重载的操作++即可,为了应对更加复杂的使用场景,C++标准库提供了丰富的原子变量操作,使之无需加锁便可在多线程环境中操作共享数据。本文将对这些原子变量操作做更详细的说明。
在C++中,常用的原子变量操作包括:
虽然如上的每个操作均可以指定不同的内存序,如memory_order_relaxed(无序)、memory_order_acquire(获取)、memory_order_release(释放)等。但本文只聚焦所有的操作,内存序将在下一章节介绍。
让我们开始本文的原子变量操作之旅。
1. 存储操作
store操作将一个新值存储到原子变量中。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic atomicInt{0};
void threadFunc()
{
atomicInt.store(10); // 设置新的值为10
}
int main()
{
std::thread t(threadFunc);
t.join();
std::cout << "Value after store: " << atomicInt.load() << std::endl;
return 0;
}
2.加载操作
load操作从原子变量中读取当前值。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic atomicInt{10};
void threadFunc()
{
int value = atomicInt.load(); // 读取值,确保同步性
std::cout << "Loaded value: " << value << std::endl;
}
int main()
{
std::thread t(threadFunc);
t.join();
return 0;
}
3.交换操作
exchange操作用新值替换原子变量的当前值,并返回旧值。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic atomicInt{ 10 };
void threadFunc()
{
int oldValue = atomicInt.exchange(20); // 将值替换为20
std::cout << "Old value was: " << oldValue << std::endl;
}
int main()
{
std::thread t(threadFunc);
t.join();
std::cout << "Value after exchange: " << atomicInt.load() << std::endl;
return 0;
}
4. 加法和减法操作
fetch_add和fetch_sub分别用于对原子变量执行加法和减法操作。它们返回操作之前的旧值。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic atomicCounter{0};
void increment()
{
atomicCounter.fetch_add(1); // 增加1
}
void decrement()
{
atomicCounter.fetch_sub(1); // 减少1
}
int main()
{
std::thread t1(increment);
std::thread t2(decrement);
t1.join();
t2.join();
std::cout << "Counter after operations: " << atomicCounter.load() << std::endl;
return 0;
}
5. 比较并交换
compare_exchange_weak和compare_exchange_strong用于比较并交换当前值。这些操作尝试将原子变量从给定的旧值更改为新值,并返回布尔值表示操作是否成功。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic atomicInt{ 10 };
void tryExchange()
{
int expected = 8;
bool success = atomicInt.compare_exchange_strong(expected, 20);
if (success) {
std::cout << "Exchange succeeded, new value: " << atomicInt.load() << std::endl;
}
else {
std::cout << "Exchange failed, expected: " << expected << std::endl;
}
}
int main()
{
std::thread t(tryExchange);
t.join();
return 0;
}
//返回值:失败,输入的预期值是8,但应该是10
//output:
//Exchange failed, expected: 10
compare_exchange_strong在原子变量当前值等于expected时,将其更新为新值20。若操作失败(即原子变量值不等于expected),则更新expected的值为当前的实际值。
6. 逻辑操作
对于位操作,fetch_and、fetch_or和fetch_xor允许对原子变量执行按位与、或和异或操作。
#include <iostream>
#include <atomic>
std::atomic flags{0b1010}; // 二进制1010
int main()
{
flags.fetch_or(0b0101); // 设置标志位
std::cout << "Flags after OR: " << std::bitset<4>(flags.load()) << std::endl;
flags.fetch_and(0b1110); // 清除标志位
std::cout << "Flags after AND: " << std::bitset<4>(flags.load()) << std::endl;
flags.fetch_xor(0b0011); // 反转标志位
std::cout << "Flags after XOR: " << std::bitset<4>(flags.load()) << std::endl;
return 0;
}
总结
本文介绍了C++中原子变量的各种操作,包括store、load、exchange、fetch_add、fetch_sub、compare_exchange和位操作。通过这些操作,可以在多线程环境中实现安全、无锁的数据操作。
本文的所示例代码均没有设置函数的最后一个参数——内存序,采用的是内存序的默认值——memory_order_seq_cst。内存序才是本系列专栏的最重要的知识点,只是为了循序渐见,方才有了上一篇和本篇文章。敬请期待下一篇文章。