C++原子变量

C++11 提供了一个原子类型 std::atomic<T>,通过这个原子类型管理的内部变量就可以称为原子变量,可以给原子类型指定任意的类型作为模板参数,因此原子变量可以是任意的类型。

C++11 内置了整形的原子变量,这样就可以更方便地使用原子变量。在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量,使用起来更简洁。因为对原子变量进行的操作只能是一个原子操作 atomic operation,原子操作是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换。多线程同时访问共享资源造成数据混乱是因为 CPU 的上下文切换导致的,使用原子变量解决了这个问题,因此不需要使用互斥锁。

atomic类

类定义

1
2
3
// 定义于头文件 <atomic>
template <class T>
struct atomic;

通过定义知道,在使用这个模板类的时候,一定要指定模板类型。

构造函数

1
2
3
4
5
6
// 1
atomic() noexcept = default;
// 2
constexpr atomic(T desired) noexcept;
// 3
atomic(const atomic&) = delete;

构造函数1:默认无参构造函数。
构造函数2:使用 desired 初始化原子变量的值。
构造函数3:使用 =delete 删除拷贝构造函数,不允许进行对象之间的拷贝。

公共成员函数

原子类型在类内部重载了 = 操作符,并且不允许在类的外部使用 = 进行对象的拷贝。

1
2
3
4
5
T operator=(T desired) noexcept;
T operator=(T desired) volatile noexcept;

atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;

原子地以 desired 替换当前值,按照 order 的值影响内存。

1
2
3
4
5
6
7
8
9
void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
void store(T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
/*
参数:
desired:
存储到原子变量中的值
order:
强制的内存顺序
*/

原子地加载并返回原子变量的当前值,按照 order 的值影响内存。直接访问原子对象也可以得到原子变量的当前值。

1
2
T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
T load(std::memory_order order = std::memory_order_seq_cst) const volatile noexcept;

C++20 新增成员

在 C++20 版本中添加了新的功能函数,可以通过原子类型来阻塞线程,和条件变量中的等待/通知函数是一样的。

1
2
3
wait(C++20):阻塞线程直至被唤醒且原子值更改
notify_one(C++20):通知(唤醒)至少一个在原子对象上阻塞的线程
notify_all(C++20):通知(唤醒)所有在原子对象上阻塞的线程

原子变量的使用

一个多线程交替的计数器,使用互斥锁和原子变量的方式分别进行实现,比较二者的不同:

互斥锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
using namespace std;

struct Counter
{
void increment()
{
for (int i = 0; i < 10; ++i)
{
lock_guard<mutex> locker(m_mutex);
m_value++;
cout << "increment number: " << m_value
<< ", threadId: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::microseconds(100));
}
}

void decrement()
{
for (int i = 0; i < 10; ++i)
{
lock_guard<mutex> locker(m_mutex);
m_value--;
cout << "decrement number: " << m_value
<< ", threadId: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::microseconds(100));
}
}

int m_value = 0;
mutex m_mutex;
};

int main()
{
Counter c;
auto increment = bind(&Counter::increment, &c);
auto decrement = bind(&Counter::decrement, &c);
thread t1(increment);
thread t2(decrement);

t1.join();
t2.join();

return 0;
}
// g++ lock_guard2.cpp -o app

原子变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <thread>
#include <atomic>
#include <functional>
using namespace std;

struct Counter
{
Counter() : m_value(0) {}

void increment()
{
for (int i = 0; i < 10000; ++i)
{
m_value++;
// cout << "increment number: " << m_value
// << ", threadId: " << this_thread::get_id() << endl;
// this_thread::sleep_for(chrono::milliseconds(500));
}
}

void decrement()
{
for (int i = 0; i < 10000; ++i)
{
// m_value--;
// cout << "decrement number: " << m_value
// << ", threadId: " << this_thread::get_id() << endl;
// this_thread::sleep_for(chrono::milliseconds(500));
}
}

// atomic<int> = atomic_int
atomic_int m_value;
};

int main()
{
Counter c;
auto increment = bind(&Counter::increment, &c);
auto decrement = bind(&Counter::decrement, &c);
thread t1(increment);
thread t2(decrement);
cout << c.m_value << endl;

t1.join();
t2.join();

return 0;
}
// g++ atomic.cpp -o app

使用这些原子类型即可摆脱每次对共享变量进行操作都进行的加锁解锁动作,节省了系统开销,同时避免了线程因阻塞而频繁的切换。但是atomic无法做到线程之间的同步。

参考资料


C++原子变量
https://lcf163.github.io/2021/08/28/Cpp原子变量/
作者
乘风的小站
发布于
2021年8月28日
许可协议