C++11 多线程
C++11 多线程相关的头文件 :
https://en.cppreference.com/w/cpp/thread
C++11 新标准中引入了四个头文件来支持多线程编程
<atomic>
,<mutex>
,<condition_variable>
,<future>
<atomic>
该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。<mutex>
该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。<condition_variable>
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。<future>
该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
第一个实例:
1 | // MacOS Mojave 10.14.5 |
atomic 头文件相关:
https://en.cppreference.com/w/cpp/atomic/atomic
C++11 新增头文件 <atomic>
atomic,atomic_flag,memory_order
atomic
简介:
原子类型对象的主要特点就是从不同线程访问不会导致数据竞争(data race)。因此从不同线程访问某个原子对象是良性 (well-defined) 行为,而通常对于非原子类型而言,并发访问某个对象(如果不做任何同步操作)会导致未定义 (undifined) 行为发生。
1 | Each instantiation and full specialization of the `std::atomic` template defines an atomic type. If one thread writes to an atomic object while another thread reads from it, the behavior is well-defined (see [memory model](https://en.cppreference.com/w/cpp/language/memory_model) for details on data races) |
1 | std :: atomic模板的每个实例化和完全特化都定义了一个原子类型。如果一个线程写入原子对象而另一个线程从中读取,则行为是明确定义的(有关数据争用的详细信息,请参阅内存模型) |
Defined in header <atomic> |
||
---|---|---|
template< class T > struct atomic; | (1) | (since C++11) |
template< class T > struct atomic<T*>; | (2) | (since C++11) |
Defined in header <memory> |
第一种形式的模版类型必须T是 TriviallyCopyable , 类型必须满足以下条件:
- std::is_trivially_copyable
::value - std::is_copy_constructible
::value - std::is_move_constructible
::value - std::is_copy_assignable
::value - std::is_move_assignable
::value
The program is ill-formed if any of following values is false
:
出现以下错误:
1 | error: _Atomic cannot be applied to type '**' which is not trivially copyable |
Specializations :序列化
Primary template : 使用第一种形式的模版
Partial specializations : 使用第二种形式的模版
Specializations of std::atomic for integral types : 类似 std::atomic_bool typedef 类型 header头文件
Specializations of std::atomic for floating point types
Flag : atomic_flag
1 | std::atomic_flag is an atomic boolean type. Unlike all specializations of std::atomic, it is guaranteed to be lock-free. Unlike std::atomic<bool>, std::atomic_flag does not provide load or store operations. |
1 | std::atomic_flag 是一种原子布尔类型。 与std::atomic的所有特化不同,它保证是无锁的。 |
使用语句std :: atomic_flag v = ATOMIC_FLAG_INIT;
定义可用于将std :: atomic_flag初始化为clear(false)状态的表达式
1 | // A spinlock mutex can be implemented in userspace using an atomic_flag |
memory_order
对于atomic对象操作有6种memory ordering 选项,
1 | typedef enum memory_order { |
默认情况下的为 memory_order_seq_cst。尽管有 6种选项,但是它们代表三种模型:
1 | sequentially-consistent ordering(memory_order_seq_cst) |
另外需要注意的是对于不同的memory ordering运行 在不同的cpu架构的机器上运行的代价是不一样的,
比如对于对同步指令的需求 sequentially-consistent ordering模型大于acquire-release ordering或者relaxed ordering ,acquire-release ordering大于relaxed ordering ;
如果是运行在多处理器的操作系统上面,这些额外的同步指令开销可能会消耗重要的 cpu时间,从而造成总体系统性能的下降。对于x86或 x86-64架构的处理器在使用 acquire-release模型的时候不需要任何额外的指令开销,甚至是对于比较严格的sequentially consisten ordering 也不需要特殊处理,并且花费的代价也很少。
relaxed ordering :
在这种模型下,std::atomic
的load()
和store()
都要带上memory_order_relaxed
参数。Relaxed ordering 仅仅保证load()
和store()
是原子操作,除此之外,不提供任何跨线程的同步。
1 |
|
执行完上面的程序,可能出现r1 == r2 == 99
。理解这一点并不难,因为编译器允许调整 C 和 D 的执行顺序。如果程序的执行顺序是 D -> A -> B -> C,那么就会出现r1 == r2 == 99
。
应用场景: 如果某个操作只要求是原子操作,除此之外,不需要其它同步的保障,就可以使用 Relaxed ordering。
1 | // 程序计数器是一种典型的应用场景: |
acquire-release ordering
在这种模型下,store()
使用memory_order_release
,而load()
使用memory_order_acquire
。
这种模型有两种效果,
第一种是可以限制 CPU 指令的重排:在store()
之前的所有读写操作,不允许被移动到这个store()
的后面。在load()
之后的所有读写操作,不允许被移动到这个load()
的前面。
除此之外,还有另一种效果:假设 Thread-1 store()
的那个值,成功被 Thread-2 load()
到了,那么 Thread-1 在store()
之前对内存的所有写入操作,此时对 Thread-2 来说,都是可见的。
1 |
|