《C++ Concurrency in Action》笔记25 std::atomic<>类模板、全局函数

现在我们已经了解了所有基本的原子类型,它们都是std::atomic<>主类模板实例化出来的,而不是模板特化。下面我们看看std::atomic<>类模板。

5.2.6 std::atomic<>主模板

std::atomic<>类模板允许用户定义自己的原子类型。可以使用任意自定义类型作为模板参数,但是这个类型必须遵守几个规则。这个类型必须提供平凡(trivial)的拷贝赋值运算符函数,意味着不能拥有虚函数以及虚基类,而且必须使用编译器自动合成的拷贝赋值运算符。而且,这个类的所有基类以及非静态数据成员都需要提供这样的拷贝赋值运算符。这在本质上意味着允许编译器在赋值时使用memcpy()或者等价的操作,因为不存在用户定义的代码。

最后,这个类型还需要具有按位可比性(bitwise equality comparable)。这个要求是与赋值操作并驾齐驱的,它不只要求可以使用memcpy() 进行拷贝,还要能够使用memcmp()进行比较。这些要求最终的目的是为了能够正确的执行“比较/交换”(compare/exchange)操作。

这些限制都来自第3章所述的一个原则:不要把指向被保护数据的指针或者引用从具有锁定保护的代码范围中传出去,作为用户指定函数的参数。通常情况下,编译器不能为用户定义的std::atomic类型生成无锁代码,因此它不得不为这些操作使用内部锁。如果允许用户自定义拷贝构造函数或者拷贝赋值运算函数,那等同于允许将指向被保护数据的引用或指针传给用户提供的函数,这将违背原则。而且,标准库在库中的所有操作都是使用一个单独的锁的,如果允许用户在holding一个锁时执行其他操作将会导致死锁,或者因为一个比较操作耗费太多时间而导致其他线程阻塞太久。总之,这些限制可以为编译器提供很大的机会针对用户指定的类型直接使用内存指令,甚至生成一个特定的无锁类型,因为它完全可以将用户指定的类型当做原始字节数据处理。

注意,尽管你可以使用std::atomic或者std::atomic,因为内置的浮点型满足了memcpy()和memcmp()的要求,但是使用compare_exchange_strong()函数时,其结果可能会让人意想不到。如果数据存储使用了不同的表示方式,即使比较的结果相同compare_exchange_strong()也可能操作失败。记住,没有浮点型的原子算法操作。

对于std::atomic类型来说,UDT代表用户定义的类型。如果UDT的大小为小于等于一个int或一个void*,大多数平台会对其使用原子指令。有些平台会对具有2倍int大小的用户类型使用原子指令。这些平台支持一种所谓的“双字比较交换”(double-word-com-pare-and-swap)(DWCAS)指令,相当于 compare_exchange_xxx()。你将在第7章看到,这种支持对于编写无锁程序是有很大帮助的。

这些限制意味着你不能定义一个类似std::atomic>的类型,但是你可以使用这样的类:包含有计数器、bool标志位、指针、简单数据类型数组。这不是个特别的问题,数据结构越复杂,你想定义除了赋值和比较外的操作的可能性就越大。此时,你最好使用std::mutex来保护数据。

一旦你定义了一个std::atomic类型,那么他的所有操作接口就限制在与std::atomic 一样的操作接口集合中了:load() ,  store() ,exchange(), compare_exchange_weak(), compare_exchange_strong(),以及从与一个非原子类型UDT实例之间的相互转换。

5.2.7 与原子类型的相关全局函数

除了之前说的成员函数外,还有一些具有等价功能的全局函数。大多数全局函数的名字与对应的成员函数一样,只是前面加了一个前缀:atomic_,例如:atomic_load()。这些函数对所有的原子类型都做了重载。为了有机会指定内存指令,每种函数都有两个版本:不带后缀的版本不指定内存指令,例如:

std::atomic_store(&atomic_var,new_value);

带有 _explicit后缀的版本可以在后面指定一个或多个内存指令,例如:

store_explicit(&atomic_var,new_value,std::memory_order_release);

成员函数直接调用,全局函数则是将一个原子类型的指针作为第一个参数传入。

这些全局函数是为了兼容C语言而定义的,所以使用指针而不是引用。与std::atomic_flag相关的函数是个特例,名字中包含有“flag”字样,例如:std::atomic_flag_test_and_set()、std::atomic_flag_clear() , std::atomic_flag_test_and_set_explicit()、 std::atomic_
flag_clear_explicit()。

C++标准库也为std::shared_ptr<>实例提供了相应的全局函数。这打破了只有原子类型才有原子操作这一常规,因为std::shared_ptr<>显然不是一个原子类型。尽管这样,C++委员会依然认为提供这个额外的还是非常有必要的。有效的原子操作有:load,store,exchange,compare/exchange,它们以标准原子类型的相关操作的重载函数的形式提供,接受一个std::shared_ptr<>*作为第一个参数:

std::shared_ptr p;
void process_global_data()
{
	std::shared_ptr local = std::atomic_load(&p);
	process_data(local);
}
void update_global_data()
{
	std::shared_ptr local(new my_data);
	std::atomic_store(&p, local);
}

同样,带有 _explicit后缀的函数可以接受一个内存指令参数。 std::atomic_is_lock_free()函数可以用来检测其是否为无锁实现。









你可能感兴趣的:(C++11,STL,多线程,C++11,C++,Concurrency,in,Action,atomic)