C++11特性:原子变量

C++11提供了一个原子类型std::atomic,通过这个原子类型管理的内部变量就可以称之为原子变量,我们可以给原子类型指定bool、char、int、long、指针等类型作为模板参数(不支持浮点类型和复合类型)。

原子指的是一系列不可被CPU上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核CPU下,当某个CPU核心开始运行原子操作时,会先暂停其它CPU内核对内存的操作,以保证原子操作不会被其它CPU内核所干扰。

由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。

可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了CAS循环,当大量的冲突发生时,该等待还是得等待!但是总归比锁要好。

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

CAS全称是Compare and swap, 它通过一条指令读取指定的内存地址,然后判断其中的值是否等于给定的前置值,如果相等,则将其修改为新的值。

1. atomic 类成员

1.1 类定义:

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

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

1.2 构造函数:

// ①
atomic() noexcept = default;
// ②
constexpr atomic( T desired ) noexcept;
// ③
atomic( const atomic& ) = delete;

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

1.3 公共成员函数: 

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

T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;

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

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

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;

1. desired:存储到原子变量中的值。
2. order:强制的内存顺序 。

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

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;

1.4 类型别名: 

atomic_bool(C++11)    std::atomic
atomic_char(C++11)    std::atomic
atomic_schar(C++11)    std::atomic
atomic_uchar(C++11)    std::atomic
atomic_short(C++11)    std::atomic
atomic_ushort(C++11)    std::atomic
atomic_int(C++11)    std::atomic
atomic_uint(C++11)    std::atomic
atomic_long(C++11)    std::atomic
atomic_ulong(C++11)    std::atomic
atomic_llong(C++11)    std::atomic
atomic_ullong(C++11)    std::atomic
atomic_char8_t(C++20)    std::atomic
atomic_char16_t(C++11)    std::atomic
atomic_char32_t(C++11)    std::atomic
atomic_wchar_t(C++11)    std::atomic
atomic_int8_t(C++11)(可选)    std::atomic
atomic_uint8_t(C++11)(可选)    std::atomic
atomic_int16_t(C++11)(可选)    std::atomic
atomic_uint16_t(C++11)(可选)    std::atomic
atomic_int32_t(C++11)(可选)    std::atomic
atomic_uint32_t(C++11)(可选)    std::atomic
atomic_int64_t(C++11)(可选)    std::atomic
atomic_uint64_t(C++11)(可选)    std::atomic
atomic_int_least8_t(C++11)    std::atomic
atomic_uint_least8_t(C++11)    std::atomic
atomic_int_least16_t(C++11)    std::atomic
atomic_uint_least16_t(C++11)    std::atomic
atomic_int_least32_t(C++11)    std::atomic
atomic_uint_least32_t(C++11)    std::atomic
atomic_int_least64_t(C++11)    std::atomic
atomic_uint_least64_t(C++11)    std::atomic
atomic_int_fast8_t(C++11)    std::atomic
atomic_uint_fast8_t(C++11)    std::atomic
atomic_int_fast16_t(C++11)    std::atomic
atomic_uint_fast16_t(C++11)    std::atomic
atomic_int_fast32_t(C++11)    std::atomic
atomic_uint_fast32_t(C++11)    std::atomic
atomic_int_fast64_t(C++11)    std::atomic
atomic_uint_fast64_t(C++11)    std::atomic
atomic_intptr_t(C++11)(可选)    std::atomic
atomic_uintptr_t(C++11)(可选)    std::atomic
atomic_size_t(C++11)    std::atomic
atomic_ptrdiff_t(C++11)    std::atomic
atomic_intmax_t(C++11)    std::atomic
atomic_uintmax_t(C++11)    std::atomic

2. 原子变量的使用

 2.1 互斥锁版本:

#include 
#include 
#include 
#include 
#include 
using namespace std;

struct Counter
{
	void increment()
	{
		for (int i = 0; i < 10; ++i)
		{
			lock_guard 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 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;
}

示例程序的执行结果为(当然执行的结果不唯一):

decrement number: -1, theadID: 41376
decrement number: -2, theadID: 41376
decrement number: -3, theadID: 41376
decrement number: -4, theadID: 41376
decrement number: -5, theadID: 41376
increment number: -4, theadID: 25900
increment number: -3, theadID: 25900
increment number: -2, theadID: 25900
increment number: -1, theadID: 25900
increment number: 0, theadID: 25900
increment number: 1, theadID: 25900
increment number: 2, theadID: 25900
increment number: 3, theadID: 25900
increment number: 4, theadID: 25900
increment number: 5, theadID: 25900
decrement number: 4, theadID: 41376
decrement number: 3, theadID: 41376
decrement number: 2, theadID: 41376
decrement number: 1, theadID: 41376
decrement number: 0, theadID: 41376

2.2 原子变量版本: 

 

#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;
}

通过代码的对比可以看出,使用了原子变量之后,就不需要再定义互斥量了,在使用上更加简便,并且这两种方式都能保证在多线程操作过程中数据的正确性,不会出现数据的混乱。

原子类型atomic 可以封装原始数据最终得到一个原子变量对象,操作原子对象能够得到和操作原始数据一样的效果,当然也可以通过store()load()来读写原子对象内部的原始数据。

 3. 效率的对比

 3.1 原子变量版本:

#include
#include
#include
#include
#include
#include
#include

std::atomic shared_data;
void func()
{
	for (int i = 0; i < 100000; ++i)
	{
		shared_data++;
	}
}

int main()
{
	auto last = std::chrono::duration_cast(
		std::chrono::system_clock::now().time_since_epoch()).count();

	std::thread t1(func);
	std::thread t2(func);
	t1.join();
	t2.join();

	shared_data.store(666);
	auto cnt = shared_data.load();
	std::cout << "shared_data:" << cnt << std::endl;

	auto cur = std::chrono::duration_cast(
		std::chrono::system_clock::now().time_since_epoch()).count();

	std::cout << cur - last << std::endl;

	return 0;
}

执行的结果为:

shared_data:666
4447

其中消耗的微妙数为3000到5000。 

 3.2 互斥锁版本:

#include
#include
#include
#include
#include
#include
#include

int shared_data;
std::mutex mtx;
void func()
{
	for (int i = 0; i < 100000; ++i)
	{
		std::lock_guard locker(mtx);
		shared_data++;
	}
}

int main()
{
	auto last = std::chrono::duration_cast(
		std::chrono::system_clock::now().time_since_epoch()).count();

	std::thread t1(func);
	std::thread t2(func);
	t1.join();
	t2.join();

	std::cout << "shared_data:" << shared_data << std::endl;

	auto cur = std::chrono::duration_cast(
		std::chrono::system_clock::now().time_since_epoch()).count();

	std::cout << cur - last << std::endl;

	return 0;
}

执行结果为:

shared_data:200000
14698

 其中消耗的微妙数为12000到20000。 

对比两种方式的消耗时间发现,使用原子量的效率更高

 本文参考:原子变量 | 爱编程的大丙 (subingwen.cn)

你可能感兴趣的:(开发语言,c++,笔记)