c++多线程基础四-- 原子操作
1 call_once使用
函数模板,该函数的第一个参数为标记,第二个参数是一个函数名(如a())。
功能:能够保证函数a()只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。
call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。
once_flag g_flag;
class Singelton
{
public:
static void CreateInstance()//call_once保证其只被调用一次
{
instance = new Singelton;
}
//两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕
static Singelton * getInstance() {
call_once(g_flag, CreateInstance);
return instance;
}
private:
Singelton() {}
static Singelton *instance;
};
Singelton * Singelton::instance = NULL;
2 原子操作
大家可以把原子操作理解成一种:不需要用到互斥量加锁(无锁)技术的多线程并发编程方式。
原子操作:在多线程中不会被打断的程序执行片段。
从效率上来说,原子操作要比互斥量的方式效率要高。
互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。
原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态。
2.1 std::atomic_flag
std::atomic_flag 构造函数如下:
- atomic_flag() noexcept = default;
- atomic_flag (const atomic_flag&T) = delete;
std::atomic_flag 只有默认构造函数,拷贝构造函数已被禁用,因此不能从其他的 std::atomic_flag 对象构造一个新的 std::atomic_flag 对象。
如果在初始化时没有明确使用 ATOMIC_FLAG_INIT初始化,那么新创建的 std::atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag不能被拷贝,也不能 move 赋值。
ATOMIC_FLAG_INIT: 如果某个 std::atomic_flag 对象使用该宏初始化,那么可以保证该 std::atomic_flag 对象在创建时处于 clear 状态。
2.2 std::atomic
std::atomic 是模板类,一个模板类型为 T 的原子对象中封装了一个类型为 T 的值。
template
原子类型对象的主要特点就是从不同线程访问不会导致数据竞争(data race)。因此从不同线程访问某个原子对象是良性 (well-defined) 行为,而通常对于非原子类型而言,并发访问某个对象(如果不做任何同步操作)会导致未定义 (undifined) 行为发生。
使用实例
std::atomic<int> count(0);
注意:一般atomic原子操作,针对++,–,+=,-=,&=,|=,^=是支持的,其他操作不一定支持
代码示例:
#include <iostream> // std::cout
#include <atomic> // std::atomic
#include <thread> // std::thread, std::this_thread::yield
std::atomic <int> foo = 0;
void set_foo(int x)
{
foo = x; // 调用 std::atomic::operator=().
}
void print_foo()
{
while (foo == 0) { // wait while foo == 0
std::this_thread::yield();
}
std::cout << "foo: " << foo << '\n';
}
int main()
{
std::thread first(print_foo);
std::thread second(set_foo, 10);
first.join();
second.join();
return 0;
}
浙公网安备 33010602011771号