《C++ 并发编程实战》读书笔记(2)
等待一个具有超时条件的条件变量
1 |
|
一个简单的启动线程的实现,不推荐实际使用, 因为创建一个线程时间成本很高。
1 | template<typename F, typename A> |
内存模型
C++程序中的所有数据均是对象(object)
组成的。这并不是说你可以创建一个派生自int
的新类,或是基本类型具有成员函数。这只是一句关于C++中数据的构造块的一种陈述。C++标准定义对象那个为存储区域,尽管它会为这些对象分配属性,如它们的类型和生存周期。
无论什么类型,对象均被存储与一个或多个内存位置中。每个内存位置要么是一个标量类型的对象,比如unsigned short
或my_class*
, 要么是相邻位域的序列。如果使用位域,有非常重要的一点必须注意:虽然相邻的位域是不同的对象,但他们仍然算作相同的内存位置。
- 每个变量都是一个对象,包括其他对象的成员。
- 每个对象占据至少一个内存位置。
- 如
int
或char
这样的基本类型的变量恰好一个内存位置,不论其大小,即使它们相邻或是数组的一部分。 - 相邻的位域是相同内存位置的一部分。
原子操作
原子操作是一个不可分割的操作。从系统中的任何一个线程中,你都无法观察到一个完成到一半的这种操作,他要么做完了要么没做完。
传统意义上,标准原子类型是不可复制且不可赋值的,因为它们没有拷贝构造函数和拷贝赋值运算符。但是,它们确实支持从相应的内置类型的赋值进行隐式转换并赋值。由于他是一个泛型类模板,操作只限为load()
、store()
、exchange()
、compare_exchange_weak()
和compare_exchange_strong()
。
在原子类型上的每一个操作均具有一个可选的内存顺序参数,它可以用来指定所需的内存顺序语义。顺序运算分为三种类型:
- 存储(
store
)操作, 可以包括memory_order_relaxed
、memory_order_release
或memory_order_seq_cst
顺序 - 载入(
load
)操作,可以包括memory_order_relaxed
、memory_order_consume
、memory_order_acquire
或memory_order_seq_cst
顺序。 - 读-修改-写(
read_modify_write
)操作, 可以包括memory_order_relaxed
、memory_order_consume
、memory_order_acquire
、memory_order_release
、memory_order_acq_rel
或memory_order_seq_cst
顺序。
所有操作的默认顺序为memory_order_seq_cst
。
使用std::atomic_flag
实现一个自旋锁
1 | class spinlock_mutex |
根据当前值存储一个新值
这个新的操作被称为比较/交换,它以compare_exchange_weak()
和compare_exchange_strong()
成员函数形式出现。
比较/交换操作是是使用原子类型编程的基石,它比较原子变量值和所提供的期望值,
如果两者相等,则存储提供的期望值。
如果两者不等,则期望值更新为原子变量的实际值。
比较/交换函数的返回类型为bool
, 如果执行了存储即为true
, 反之则为false
。
对于compare_exchange_weak()
,
即使原始值等于期望值也可能出现存储不成功,在这种情况下变量的值是不变的, compare_exchange_weak()
的返回值为false
。
这最有可能发生在缺少单个的比较并交换指令的机器上,此时处理器无法保证该操作被完成–这就是所谓的伪失败,因为失败的原因是时间的函数而不是变量的值。
由于compare_exchange_weak()
可能会伪失败,它通常必须用在循环中。
另一方面,仅当实际值不等于expected
值时compare_exchange_strong()
才保证返回false
。这样可以消除对循环的需求。
如果你想要改变变量,无论其初始值是多少, expected
的更新就变得很有用,每次经过循环时,excepted
被重新载入,因此如果没有其他线程在此期间修改其值,那么compare_exchange_weak()
或compare_exchange_strong()
的调用在下一次循环中应该是成功的。
如果计算待存储的值很简单,为了避免在compare_exchange_weak()
可能会伪失败的平台上的双重循环,(于是compare_exchange_strong
包含一个循环), 则使用compare_exchange_weak()
可能是有好处的。另一方面,
如果计算待存储的值本身是耗时的,当expected
值没有变化时,使用compare_exchange_strong()
来避免被迫重新计算待存储的值可能时有意义的。对于std::atomic<bool>
而言这并不重要,毕竟只有两个值,但对于较大的原子类型这会有所不同。
比较/交换函数还有一点非同寻常,他们可以接受两个内存顺序参数。这就允许内存顺序的语义在成功和失败的情况下有所区别。对于一次成功调用具有memory_order_acq_rel
语义而一次失败的调用有着memory_order_relaxed
语义,这想必是极好的。一次失败的比较/交换并不进行存储,因此它无法具有memory_order_release
或memory_order_acq_rel
语义。因此再失败时禁止提供这些值作为顺序。你也不应为失败提供比成功更严格的内存顺序。如果你希望memory_order_acquire
或者memory_order_seq_cst
作为失败的语义,你也必须为成功指定这些语义。
如果你没有为失败指定一个顺序,就会假定它与成功是相同的,除了顺序的release
部分被除去:memory_order_release
变成memory_order_relaxed
, memory_order_acq_rel
变成memory_order_acquire
。如果你都没有指定,他们它们通常默认为memory_order_seq_cst
, 这为成功和失败都提供了完整的序列顺序。以下对compare_exchange_weak()
的两个调用时等价的。
1 | std::atomic<bool> b; |