[c++11] std::async std::packaged_task std::promise and std::future notes

把std::async,std::packaged_task,std::promise三个放在一起来说,是因为他们都可以返回一个std::future对象.简单来说,当某个线程需要等待一个特定的一次性事件(one-off event),它可以用一个”future”来表示这个事件.

std::async

有的时候可能你需要做一个花费事件比较长的计算,但是计算结果不是立刻需要.这个时候就可以用一个新的线程来做这个计算.这里比较关键的问题是如何将在新线程进行计算的结果传回到当前线程,因为std::thread并没有提供一个类似的机制.

这个时候就需要std::async登场了.

当然也可以与向std::thread包装的thread function中传参数一样,向std::async中传参数,如下:

此外,std:;async还有一个可选参数,值为std::launch::deferred或std::launch:async或std::launch::deferred|std::launch:async,第三种为默认参数.

std::launch::async表示该task会立即在一个新的thread上执行.

std::launch::deferred 表示该task不会被立刻执行,而是在调用get()或者wait()的时候才会执行.这里需要注意的是,调用get()或者wait()的线程,可以是和调用async()属于同一个线程,也可以属于不同线程.

笼统来说,std::launch:deferred是一种更为灵活的方式.具体请参考cppreference_async

std::packaged_task

std::packaged_task有点类似std::function,将要在其他线程执行的对象封装了起来。

与std::async相比,std::packaged_task不会直接执行任务,而是需要显示调用,因此可以做到将执行函数的时机和获取future的时机分离

std::promise

第一次用c++11写多线程就是用的promisepromise && future leanrning notes

个人认为std::promise与std::async或者std:;packaged_task最明显的区别是,它的值不是一个函数的返回值,而是可以通过set_value来设置,因此更加灵活。

需要注意的是,std::promise不支持拷贝构造,因此需要使用std::move或者传promise的指针。

 

In summary

  • async:提供最高层次的抽象。如果你不需要控制线程的运行时机,就选这个。
  • packaged_task:抽象层次比 async低。如果你需要控制线程的运行时机,且线程执行的结果即目标结果时,选这个。
  • promise:抽象层次最低。当你想在线程中设置目标结果的值,选这个。

 

参考资料:

货比三家:C++ 中的 task based 并发

What is the difference between packaged_task and async

std::promise

 

[C++11]std::condition_variable notes

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

用人话来说,condition_variable,也就是条件变量,是线程间通信的一种方式。

线程之间在很多时候需要通信,比如经典的生产者消费者问题

一个比较naive的方案是,用mutex来保护一个flag,然后另一线程不停得check这个flag的状态是否改变。以及在这个方案上的改进:让另一个线程check之后,可以先睡一段时间。

但是这两种方法都不够好。第一种不好的原因当然是不停得check,肯定会耗费大量的资源。而第二种,由于没办法准确估计要休眠的时间,因此不够实际。

这个时候我们可以考虑使用条件变量。

条件变量是可以用在如下场景: 一个或者多个线程在等某个条件的成立,而这个条件由另外的线程所控制。当该条件成立时,控制该条件的线程会主动通知这些线程,将这些线程唤醒。

如下是一个最简单的例子:

接下来是一个较为复杂的例子,一个线程安全的队列的实现,

以及,虽然现在,这样线程安全的数据结构应该早就有人封装好了,我们直接使用就行了,比如之前用过的intel tbb的concurrent_queue,不过如果对内部实现一无所知,大概没办法感觉到这种精巧之处吧。。

参考资料:

std::condition_variable

std::condition_variable

以及借鉴了 c++并发编程第四章中格式化后的代码

 

 

 

 

std::call_once && std::once_flag notes

多线程保护数据时,一种较为特殊的情况是只需要保护资源的初始化。

资源初始化一般遵循”lazy initialization”的原则,也就是在用到该资源最近的地方再初始化。

比较容易想到的办法是用std::mutex,将资源初始化的地方锁起来,如下:

这确实是一个办法。但是初始化时如果需要耗费比较多的时间,当有比较多的线程时,一个线程初始化时,其他线程会耗时间在不必要的等待上。

在c++11以后,我们可以使用std::once_flag和std::call_once来解决资源初始化时加锁的问题。比起显示调用std::mutex的好处是,资源消耗更少。

 

下面是两个例子:

或者更一般地,可以解决一类在多线程环境下保证某段代码只执行一次的问题。

比如声明的一个static 变量。

参考资料:

std::call_once

once_flag

 

 

 

[c++11 ]std::move 右值引用 转移语义 完美转发 notes

起因是在看<CplusplusConcurrencyInAction_PracticalMultithreading>,里面讲到转移一个std::thread的ownership提到了std::move.

之前[C++11 ] std::ref&&std::reference_wrapper notes 提到的情况是在我们想要用引用的时候却进行了拷贝,得到不符合期望的结果。现在的情况是,有些object或许是不支持拷贝构造的。比如std::unique_str,std::ifstream,这个时候如果我们需要传参数进去,就可以使用std::move来实现。比如下面这个例子:

当然这只是使用std::move的一种情形,即传递不允许拷贝构造的object作为参数。

另外,std::move可以更有效率地传递资源。内容之后补orz

实际上std::move()的作用是传进去一个object,返回这个object的右值引用(rvalue reference)

首先区分左值和右值,这其实是一个c语言中就有的概念(作为区分,右值引用是C++11中新引入的概念)

一般来说,右值是不能被取地址的值。在C++11之前,右值是不能被引用的。

语法上为了区分C++11之前的引用(也就是左值引用&),右值引用的符号为&&

那么为什么要引入“右值引用”这个概念? 主要有两个目的:完美转发(Perfect Forwarding)转移语义(Move Sementics)

转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁。转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。

完美转发,也叫精确传递,指的是“需要将一组参数原封不动的传递给另一个函数”。

原封不动的含义是说:数值不变,左值/右值属性不变,const/non-const属性不变。

在泛型函数中,这样的需求非常普遍。

比如下面这个例子:

在右值引用出现,模板的每个参数都必须重载两个类型,总的重载次数是指数级的orz,警察听了想打人

然而有了右值引用,不管模板右多少个参数,我们只需要定义一次,接受一个右值引用的参数,就能够将所有的参数类型原封不动的传递给目标函数。

 

那么最后说回std::move. std::move的作用就是传入左值,返回右值引用,从而使用右值引用带来的好处(节省资源,实现完美转发等)

 

 

 

 

参考资料:

[C++11 ] std::ref&&std::reference_wrapper notes

起因是在看《CplusplusConcurrencyInAction_PracticalMultithreading》的时候,里面讲到初始化std::thread的时候,如果thread funtion的参数列表中有引用,需要传入std::ref才可以得到符合预期的结果。

查阅发现std::ref是用来生成std::reference_wrapper。 按照 cppreference 上的话来说

std::reference_wrapper 是包装引用于可复制、可赋值对象的类模板。它常用作将容器存储入无法正常保有引用的标准容器(类似 std::vector )的机制。

用人话来说,就是有的时候一些地方(比如STL容器中传值,又比如std::bind)会默认使用复制,这可能与我们想使用引用的期望不符。

具体见下面的几个例子:

我们发现直接传进去的参数n1的值没有改变,而使用std::ref传进去的值的结果符合预期。

n1的值不符合预期的原因是,在调用std::bind的时候,会先将参数复制一份,然后传入函数f时,传入的不是在main函数中定义的n1的引用,而是对n1复制了一份得到的临时变量的引用。在函数f中对n1的修改只会使得n1的复制值与其保持一致,而不会改变n1.

下面是一个关于 std::reference_wrapper 的例子

 

最后我们回到开始,将参数在通过创建std::thread传给thread funtion的时候,参数默认情况下仍然是被拷贝了一份,与上面std::bind的情况类似,因此需要使用std::ref

 

参考资料:

 

 

 

[C++11] promise && future leanrning notes

std::promise and std::future

 

用人话就是,主线程传给附属线程一个promise Object,然后主线程想要获取附属线程set给promise Object的值(也就是该线程返回的某个结果),需要通过主线程中的promise object 得到对应的future object(每个promise 对应一个 future),然后调用future 的get方法。如果附属线程没有执行作为参数传入的promise的set方法去返回结果,那么程序就会block住。

 

参考资料:

C++11 Multithreading – Part 7: Condition Variables Explained

c++11 function 与bind 学习笔记

C++11 std::function 是一种通用、多态的函数封装,它的实例可以对任何可
以调用的目标实体进行存储、复制和调用操作

见下面的例子

std::bind 则是用来绑定函数调用的参数的,它解决的需求是我们有时候可
能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,我们可以将
部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调

看下面的例子:

 

 

C++ 记录代码运行时间

以前用的办法太老土啦

看到一个since C++11的方法,我觉得比较优雅

 

c++11 学习笔记

昨天终于搞定了ycm对c++11的支持….

嘛,17都快出来了,我竟然连11都不会用。

不过突然把所有的11特性给我也没办法全部吸收。

所以在这里记录下用过的c++11的用法。

auto可以代替stl的一些容器中的iterator: