C++ 11增加了原子类型atomic
类,在一定条件下可以实现无锁编程。
1. 简介
atomic
是一个模板类,定义如下:
template< class T > struct atomic;
atomic
可以实现无锁编程,在效率上要比mutex
高很多,直接看个直观的例子:
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>
#include <vector>
int sum1{0};
std::atomic<int> sum2{0};
std::mutex mtx;
const int max_ = 100000000;
void add1()
{
while (1) {
mtx.lock();
if (sum1 >= max_)
{
mtx.unlock();
return;
}
sum1++;
mtx.unlock();
}
}
void add2()
{
while (1)
{
if (sum2 >= max_) return;
sum2++;
}
}
int main()
{
{
std::vector<std::thread> ths;
std::cout << "begin, sum1=" << sum1 << std::endl;
auto start = std::chrono::system_clock::now();
for (int i = 0; i <= 10; i++)
{
std::thread th{add1};
ths.push_back(std::move(th));
}
for (int i = 0; i <= 10; i++)
{
ths[i].join();
}
auto finish = std::chrono::system_clock::now();
auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
std::cout << "completed, sum1=" << sum1 << std::endl;
std::cout << "mutex cost: " << (double)cost.count() << "ms" << std::endl;
}
std::cout << std::endl;
{
std::vector<std::thread> ths;
std::cout << "begin, sum2=" << sum2 << std::endl;
auto start = std::chrono::system_clock::now();
for (int i = 0; i <= 10; i++)
{
std::thread th{add2};
ths.push_back(std::move(th));
}
for (int i = 0; i <= 10; i++)
{
ths[i].join();
}
auto finish = std::chrono::system_clock::now();
auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
std::cout << "completed, sum2=" << sum2 << std::endl;
std::cout << "atomic cost: " << (double)cost.count() << "ms" << std::endl;
}
}
这个例子里边,用多线程对一个变量自增,自增到一个值后结束,比较加锁和原子类型的运行时间,结果如下:
经多次测试,这个示例中atomic
的效率是mutex
的3~4倍。
2. 接口用法
下面记录的是atomic类的常用接口,全部接口在这里看
2.1 构造函数
atomic() noexcept = default;
constexpr atomic(T desired) noexcept; //常用
atomic(const atomic&) = delete; //禁用拷贝构造函数
示例:
std::atomic<int> sum1{0};
std::atomic<int> sum2(1);
std::atomic<int> sum3 = 3; //error, 禁止拷贝
2.2 修改、访问
atomic
对象的修改主要使用store
函数,访问主要使用load
函数。
void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
std::memory_order是内存顺序,太复杂了,摆烂了,默认值又不是不能用!这篇勉强能看懂...
#include <atomic>
#include <iostream>
int main()
{
std::atomic<int> a{1};
std::cout << a.load() << std::endl;
a.store(4);
std::cout << a.load() << std::endl;
}
输出:
1
4
2.3 fetch_* 系列函数
fetch_ 开头的函数一共有5个,功能是简单的加减与之类的运算。与fetch_ 系列函数对应的运算符同样是5个,他们的功能是一样的。
fetch_* | 对应运算符 | 功能 |
fetch_add | += | 原子地将参数加到存储于原子对象的值,并返回先前保有的值 |
fetch_sub | -= | 原子地从存储于原子对象的值减去参数,并获得先前保有的值 |
fetch_and | &= | 原子地进行参数和原子对象的值的逐位与,并获得先前保有的值 |
fetch_or | |= | 原子地进行参数和原子对象的值的逐位或,并获得先前保有的值 |
fetch_xor | ^= | 原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值 |
#include <atomic>
#include <iostream>
int main()
{
std::atomic<int> a{1};
std::cout << a.load() << std::endl;
a.fetch_add(5);
std::cout << a.load() << std::endl;
}
输出:
1
6
2.4 其他接口
2.4.1 is_lock_free
bool is_lock_free() const noexcept;
检查此类型所有对象上的原子操作是否免锁。
如果免锁,返回true
,如果不是,会编译不过,这里是挺奇怪的。经过测试,模板类型的大小为1、2、4、8字节时,atomic
是免锁的,其他情况都会编译不过,详情。
2.4.2 exchange
T exchange( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
原子地以 desired
替换底层值。操作为读-修改-写操作。