C++11提供了一个原子类型std::atomic
原子指的是一系列不可被CPU上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核CPU下,当某个CPU核心开始运行原子操作时,会先暂停其它CPU内核对内存的操作,以保证原子操作不会被其它CPU内核所干扰。
由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。
可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了CAS循环,当大量的冲突发生时,该等待还是得等待!但是总归比锁要好。
C++11内置了整形的原子变量,这样就可以更方便的使用原子变量了。在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量了,用起来更简洁。因为对原子变量进行的操作只能是一个原子操作(atomic operation),**原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换。**多线程同时访问共享资源造成数据混乱的原因就是因为CPU的上下文切换导致的,使用原子变量解决了这个问题,因此互斥锁的使用也就不再需要了。
类定义
// 定义于头文件
template< class T >
struct atomic;
通过定义可得知:在使用这个模板类的时候,一定要指定模板类型。
// ①
atomic() noexcept = default;
// ②
constexpr atomic( T desired ) noexcept;
// ③
atomic( const atomic& ) = delete;
原子类型在类内部重载了=操作符,并且不允许在类的外部使用 =进行对象的拷贝
T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;
atomic& operator=( const atomic& ) = delete;
atomic& operator=( const atomic& ) volatile = delete;
也就是说:
void test01() {
atomic_int a = 0; // correct
atomic_int d(0); // correct
// atomic_int b = a; // error
// atomic_int c(a); // error
}
store
函数用于将一个值存储到原子变量中。它的基本形式如下:
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
:表示存储操作的内存序(memory order),默认为 memory_order_seq_cst
,即顺序一致性。(可不指定)load
函数用于从原子变量中加载当前的值。它的基本形式如下:
T load(memory_order order = memory_order_seq_cst) const noexcept;
order
:表示加载操作的内存序(memory order),默认为 memory_order_seq_cst
,即顺序一致性。
两个函数示例:
#include
using namespace std;
#include
#include
atomic_int atomicCount(0);
void test02() {
for (int i = 0; i < 100; ++i) {
atomicCount.store(atomicCount.load() + 1);
}
}
int main() {
thread t1(test02);
thread t2(test02);
t1.join();
t2.join();
cout << atomicCount.load() << endl; // 200
}
在这个例子中,两个线程并发地对 atomicCounter
进行递增操作。由于 store
和 load
都是原子操作,因此可以确保对 atomicCounter
的操作是线程安全的。最后输出的 atomicCounter
的值是预期的 200。
主要说的是赋值运算符重载:
以上各个 operator 都会有对应的 fetch_* 操作,详细见下表:
见代码:
void test01() {
atomic_int a = 0; // correct
atomic_int d(1); // correct
// atomic_int b = a; // error
// atomic_int c(a); // error
a.store(10);
++a;
a += d;
auto e = a & d;
cout << e << endl; // 0
}
也就是load函数和store函数的参数 memory_order, 以指定如何同步不同线程上的其他操作。
定义如下:
typedef enum memory_order {
memory_order_relaxed, // relaxed
memory_order_consume, // consume
memory_order_acquire, // acquire
memory_order_release, // release
memory_order_acq_rel, // acquire/release
memory_order_seq_cst // sequentially consistent
} memory_order;
在C++20版本中添加了新的功能函数,可以通过原子类型来阻塞线程,和条件变量中的等待/通知函数是一样的。
假设我们要制作一个多线程交替数数的计数器,我们使用互斥锁和原子变量的方式分别进行实现,对比一下二者的差异:
#include
#include
#include
#include
#include
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
<< ", theadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(100));
}
}
void decrement()
{
for (int i = 0; i < 10; ++i)
{
lock_guard<mutex> locker(m_mutex);
m_value--;
cout << "decrement number: " << m_value
<< ", theadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(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;
}
#include
#include
#include
#include
using namespace std;
struct Counter
{
void increment()
{
for (int i = 0; i < 10; ++i)
{
m_value++;
cout << "increment number: " << m_value
<< ", theadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(500));
}
}
void decrement()
{
for (int i = 0; i < 10; ++i)
{
m_value--;
cout << "decrement number: " << m_value
<< ", theadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(500));
}
}
// atomic == atomic_int
atomic_int m_value = 0;
};
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;
}
程序运行结果:
decrement number: -1, theadID: 14916
increment number: 0, theadID: 6372
decrement number: -1, theadID: 14916
increment number: 0, theadID: 6372
decrement number: 0, theadID: 14916
increment number: 0, theadID: 6372
increment number: 1, theadID: 6372
decrement number: 0, theadID: 14916
increment number: 1, theadID: 6372
decrement number: 0, theadID: 14916
increment number: 1, theadID: 6372
decrement number: 0, theadID: 14916
decrement number: 0, theadID: 14916
increment number: 0, theadID: 6372
decrement number: 0, theadID: 14916
increment number: 0, theadID: 6372
decrement number: 0, theadID: 14916
increment number: 0, theadID: 6372
decrement number: -1, theadID: 14916
increment number: 0, theadID: 6372
总结:
通过代码的对比可以看出,使用了原子变量之后,就不需要再定义互斥量了,在使用上更加简便,并且这两种方式都能保证在多线程操作过程中数据的正确性,不会出现数据的混乱。
原子类型atomic