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++并发学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

《C++ 并发编程实战 第二版》学习笔记目录

C++并发学习笔记

{objccn.io}学习笔记-并发编程-底层并发API

吴恩达机器学习学习笔记——代价函数

ng机器学习视频笔记——线性回归代价函数梯度下降基础

ng机器学习视频笔记 ——神经网络的代价函数反向传播梯度检验随机初始化