C++并发学习笔记
Posted 帝王铠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发学习笔记相关的知识,希望对你有一定的参考价值。
基础的定义略过,这里只记录一些细节。
std::async
自动创建一个线程(或从内部线程池中挑选)和一个promise对象。
- 然后将std::promise对象传递给线程函数,并返回相关的std::future对象
- 当我们传递参数的函数退出时,它的值将被设置在这个promise对象中,所以最终的返回值将在std::future对象中可用
std::async中的第一个参数是启动策略,它控制std::async的异步行为,我们可以用三种不同的启动策略来创建
- std::async·std::launch::async保证异步行为,即传递函数将在单独的线程中执行
- std::launch::deferred当其他线程调用get()来访问共享状态时,将调用非异步行为
- std::launch::async | std::launch::deferred默认行为。有了这个启动策略,它可以异步运行或不运行,这取决于系统的负载,但我们无法控制它。
std::async 会抓走所有异常,保存在 std::future 中,然后在调用 std::future::get 方法时被再次抛出。看起来不错的特性,然而因为 C++ 的异常不携带栈信息,这种操作直接导致现场信息永久性的丢失。
std::future
future_status有三种状态:
- future_status::deferred 2:异步操作还没开始
- future_status::ready 0:异步操作已经完成
- future_status::timeout 1:异步操作超时、
获取future结果有三种方式:
- get、同步阻塞调用,等待异步操作结束并返回结果
- wait、同步阻塞调用,只是等待异步操作完成,没有返回值
- wait_for、是超时等待返回结果
std::promise
std::promise 的拷贝构造函数和 operator= 被禁用,即 std::promise 普通的赋值操作被禁用,operator= 只有 move 语义,所以 std::promise 对象是禁止拷贝的
std::promise::get_future:返回一个与promise共享状态相关联的future对象
std::promise::set_value:设置共享状态的值,此后promise共享状态标识变为ready
std::promise::set_exception:为promise设置异常,此后promise的共享状态标识变为ready
std::promise::set_value_at_thread_exit:设置共享状态的值,但是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready(注意:该线程已设置promise的值,如果在线程结束之后有其他修改共享状态值的操作,会抛出future_error(promise_already_satisfied)异常)
std::promise::swap:交换 promise 的共享状态
std::packaged_task
promise保存了一个共享状态的值,而packaged_task保存的是一个函数
memory barrier 内存屏障
__sync_synchronize (…)
因为目前gcc实现的是full barrier(类似Linux kernel中的mb(),表示这个操作之前的所有内存操作不会被重排到这个操作之后),所以可以忽略掉这个参数。下面是有关memory barrier的东西。
关于memory barrier, cpu会对我们的指令进行排序,一般说来会提高程序的效率,但有时候可能造成我们不希望看到的结果。举例说明,比如我们有一硬件设备,当你发出一个操作指令的时候,一个寄存器存的是你的操作指令(READ),两个寄存器存的是参数(比如地址和size),最后一个寄存器是控制寄存器,在所有的参数都设置好后向其发出指令,设备开始读取参数,执行命令,程序可能如下:
write1(dev.register_size, size);
write1(dev.register_addr, addr);
write1(dev.register_cmd, READ);
write1(dev.register_control, GO);
如果CPU对我们的指令进行优化排序,导致最后一条write1被换到前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory barrier,强制CPU执行完前面的写入后再执行最后一条:
write1(dev.register_size, size);
write1(dev.register_addr, addr);
write1(dev.register_cmd, READ);
__sync_synchronize(); 发出一个full barrier
write1(dev.register_control, GO);
memory barrier有几种类型:
acquire barrier:不允许将barrier之后的内存读取指令移到barrier之前;(linux kernel中的wmb)
release barrier:不允许将barrier之前的内存读取指令移到barrier之后;(linux kernel中的rmb)
full barrier:以上两种barrier的合集;(linux kernel中的mb)
gcc原子操作
从4.1.2开始提供了__sync_*系列的build-in函数,用于提供加减和逻辑运算的原子操作,其声明如下:
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)
type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)
__sync_fetch_and_add反汇编出来的指令(实际上,这部分我还不是很懂,都是从其他博客上摘录的)
804889d:f0 83 05 50 a0 04 08 lock addl $0x1,0x804a050
可以看到,addl前面有一个lock,这行汇编指令前面是f0开头,f0叫做指令前缀,Richard Blum。lock前缀的意思是对内存区域的排他性访问。
其实,lock是锁FSB,前端串行总线,Front Serial Bus,这个FSB是处理器和RAM之间的总线,锁住FSB,就能阻止其他处理器或者Core从RAM获取数据。当然这种操作开销相当大,只能操作小的内存可以这样做,想想我们有memcpy,如果操作一大片内存,锁内存,那么代价太大了。所以前面介绍__sync_fetch_and_add等函数,type只能是int, long, long long以及对应的unsigned类型。
windows原子操作
原子加1操作:
LONG InterlockedIncrement( LONG volatile* Addend);
原子减1操作:
LONG InterlockedDecrement( LONG volatile* Addend);
带有全局的内存屏障比较交换:
InterlockedCompareExchange()
不带全局内存栅障的比较交换:
InterlockedCompareExchangeAcquire()或者InterlockedCompareExchangeRelease()
原子写操作:
InterlockedExchange()
C++11标准原子操作
std::atomic<>
load()
store()
compare_exchange_weak()
compare_exchange_strong()
fetch_add()
fetch_sub()
fetch_or()
fetch_xor()
内存屏障
std::atomic_thread_frence(std::memory_order_release);
六种内存顺序如下:
memory_order_relaxed 松散顺序
memory_order_seq_cst 顺序一致 sequentially consistent
memory_order_consume 获得释放 acquire_release
memory_order_acquire
memory_order_release
memory_order_acq_rel
以上是关于C++并发学习笔记的主要内容,如果未能解决你的问题,请参考以下文章