111qqz的小窝

老年咸鱼冲锋!

[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

 

参考资料:

 

 

 

[设计模式] 观察者( Observer )模式学习笔记

最近在学习node.js,里面讲到node.js的事件机制使用了观察者模式,因此来学习一下。

观察者模式的目的是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

因此观察者模式又叫发布-订阅模式。

下面放一个简化之后的例子:

 

update:怪不得觉得熟悉,原来Qt的信号槽机制Signals_and_slots 就是使用了观察者模式。

参考资料:

观察者模式-菜鸟教程

我所理解的设计模式(C++实现)——观察者模式(Observer Pattern)

[设计模式] 组合模式(composite) 学习笔记

目的是忽略单一对象和组合对象的不同。 有点像以前写过的用链表定义一个树结构,每个节点是一个val + 多个*tree 。如果某个节点是叶子节点了,那么对应的*tree都为NULL. 只不过这里用了更加面向对象的实现。

具体看代码:

 

最后打印的结果为:

root 1
Leaf A 2
Leaf B 2
Composite X 2
Leaf XA 3
Leaf XB 3
Composite XY 3
Leaf XYA 4
Leaf XYB 4

[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

把二进制文件按字节读到vector

另外一种C++风格,但是性能较差的方法:

 

记录一次因动态库符号表可见性导致的未定义的引用(undefined reference)

编译某代码,发现报错某函数未定义的引用。该函数的是先前编译得到的动态库中。

先去check了该函数的实现,还有接口与头文件中的声明是否统一。发现没有问题。

然后怀疑.cpp文件没有被编译到,于是在该函数中添加

#pragma message(“******************************8”)
发现的确被编译到了。
使用nm来查看动态库中的符号表,发现也可以找到这个函数的符号。
于是怀疑编译代码的时候没有链接到该动态库。
于是在make的时候打印详细信息。make VERBOSE=1
发现也的确链接了动态库….
见鬼了Orz
然后用readelf -s 来查看动态库,惊讶得发现要找的那个符号的BIND怎么是LOCAL..也就是只有文件内可见。
最后发现…是公司内部的工具和CMakeLists中的add_library冲突…
虽然这个坑的解决方案没什么价值…不过因为这个坑了解了一些之前没有了解的部分,也算值得。
关于动态库的符号可见性:
控制的原因是,如果不控制,那么不同的cpp文件可能有相同的变量名字,如果把所有的符号都暴露,很可能在链接时产生冲突。 另外一个原因是,暴露没有必要的符号,会导致符号表的size变大,从而使得link时速度变慢。
参考资料:
Introduction to symbol visibility
readelf elf文件格式分析
Hiding what’s exposed in a shared library
Why is the new C++ visibility support so useful?

 

c++11 function 与bind 学习笔记

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

见下面的例子

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

看下面的例子:

 

 

C++ 记录代码运行时间

以前用的办法太老土啦

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

 

C++ STL Algotithms 学习笔记

迫于拙劣的cpp水平,这次来记录一些关于STL算法部分的内容。

参考内容是CS106L的course reader

Iterator Categories

Iterators分为以下五种:

  • Output Iterators:可以使用”++”;可以用*myItr = value,不能用value = *myItr
  • Input Iterators:可以使用”++”;可以用value = *myItr,不能用*myItr = value
  • Forward Iterators: 可以使用”++”,可以同时用value = *myItr和*myItr = value
  • Bidirectional Iterators:比起Forward Iterator 对了”–“,但是不能+或者+=
  • Random-Access Iterators:比起Bidirectional Iterators多了+和+=

Algorithm Naming Conventions

一些关于STL Algorithm的命名规则

后缀_if表示只有当满足一定条件的时候该算法才会执行一定任务。

比如:

_n表示执行一个特定的操作n次。

比如:

Reordering Algorithms

  • sort: 传入的必须是Random-Access Iterators,记得定义<函数
  • random_shuffle:传入的必须是Random-Access Iterators,作用是将一个区间内的元素打乱重排。 可以在使用之前先使用srand函数。
  • rotate:作用是循环改变容器中元素的顺序。rotate(v.begin(), v.begin() + 2, v.end());会让(0, 1, 2, 3, 4, 5)变为(2, 3, 4, 5, 0, 1)

Searching Algorithms

  • find:三个参数,前面连个迭代器表示寻找的范围,第三个参数表示要找的值。返回第一个该值所在的位置的迭代器或者返回第二个迭代器(如果没找到)
  • binary_search:需要有序;返回某个值是否在一个范围内。
  • lower_bound:需要有序;返回大于等于某值的第一个位置的迭代器。

需要注意的是,如果某个容器本身有和STL algorithm同名的成员函数(比如set的find),那么优先使用该容器的成员函数。原因是STL Algorithm需要就有普适性,不会针对特定容易优化。因此对于set来说,其成员函数的find的复杂度是logn的,而STL alogithm的find是O(n)的复杂度。

Iterator Adaptors

具有Iterator的性质,但是比Iterator更强..

比如ostream_iterator

通过copy(myVector.begin(), myVector.end(), ostream_iterator<int>(cout, ” “)); 可以直接将一个容器中的元素输出。

比如insert_iterator,对于要从一个容器拷贝元素到另一个容器,但是在编写代码时不知道源容器的元素有多少个的问题,可以很好解决?

Removal Algorithms

需要注意的是并没有真的remove,而是将”remove”的元素放在了容器后面(?),原因是STL Algorithms接受的是Iterator,而不是Container.

Other Noteworthy Algorithms

  • transform:四个参数,前两个迭代器表示要变换的范围,第三个迭代器表示结果的开始位置,第四个参数为一个函数,表示将该范围的每个元素经过该函数的处理。不要求结果和原始的数据类型相同。
  • min_element:两个迭代器表示一个范围,返回该范围中最小元素的迭代器;可以有第三个参数来自定义小于关系。max_element与之类似。
  • accumulate:template< class InputIt, class T T accumulate( InputIt first, InputIt last, T init );
  • inner_product:求内积;T inner_product( InputIt1 first1, InputIt1 last1,
    InputIt2 first2, T value );
  • distance:连个参数,表示这两个迭代器之间元素的个数。

 

 

 

C++ IO Streams 学习笔记

迫于拙劣的cpp水平,来补补以前忽略掉的cpp细节。

老规矩,先放资料。

参考资料:

A Gentle Introduction to C++ IO Streams

“Designing and implementing a general input/output facility for a
programming language is notoriously difficult”
– Bjarne Stroustrup

Stream的基本认识

说说我的理解。stream(流)可以看做输入输出的抽象。我们通过流可以忽略掉device的细节,采取同样的输入输出方式。

对于任何原生的cpp类型,都可以用stream来处理。用户自定义的类,也可以通过重载<<和>>而让stream可以处理。

 

stream大致分为inputstream和outputstream两种,分别对应的类型为std::istream和std::ostream.

stream大概有如下操作:

  •     使用适当的值类型(如stringstream的STD : : string和fstream的文件名)和适当的模式(如用于输入的IOs : : in和用于输出的IOs : : out等,具体取决于流的类型)初始化流
  • 可以通过get和put指针指定I / O应该发生的位置。根据您打开流的方式,可能已经适当地设置了位置(例如,如果使用IOs : : app打开文件,则在流的末尾设置get指针,允许附加)。

    注意:如果需要在一个stream的中间位置插入数据的话,需要手动将指针位置后面的数据移动,否则会被覆盖掉。
  • 使用<<或者>>来读或者写。

stream的错误处理

将stream当成bool来处理是比较常见的,

但是实际上有四种status:

  • good() returns true when everything is okay.
  • bad() returns true when a fatal error has occurred.
  • fail() returns true after an unsuccessful stream operation like an unexpected type of input being encountered.
  • eof() returns true when the end of file is reached.

String Streams

emm,其实string和stream好像挺像的。 区别是,string是可以随机访问的,stream是顺序访问。

输出是:

我们观察到原本句子末尾的英文句号”.”被覆盖掉了。

buffer的使用

I/O操作是相对来说比较花时间的操作,如果我们要多次写很多小文件,那会浪费大量的时间。于是我们的想法是,使用一个临时的Buffer将数据存起来,当这个buffer满了之后再去读或者写。

注意不是所有的stream都采用了这种机制,比如cerr就没有采用。

下面放一段代码来感受下buffer

 

此处flush的含义是将buffer中的内容立即输出

我们观察发现,在testBUffer中,”before loop”是在循环之后才输出的。暗示cout使用了buffer.

 

 

Eigen: C++开源矩阵学习笔记

接触Eigen的原因是最近在看caffe/caffe2源码,caffe2中使用了Eigen库. Eigen 是一个基于C++模板的线性代数库,直接将库下载后放在项目目录下,然后包含头文件就能使用,非常方便。对于Linux用户,只需要把头文件放到/usr/include 下即可此外,Eigen的接口清晰,稳定高效。

之后会更新一些,Eigen中我使用过的函数.

ubuntu14.04LTS 下使用方式:

然后尝试运行如下代码,直接编译即可.如果可以正常运行,表明安装完毕.

map的使用办法:

double arr[9]={1,2,3,4,5,6,7,8,9};
Map<MatrixXd> A(arr,3,3);
得到
1 4 7
2 5 8
3 6 9

以看出默认是按列优先的…
如果需要按行优先,可以修改矩阵的定义方式:
typedef Matrix<double, Dynamic, Dynamic,RowMajor>rMatrixXd;//定义矩阵行优先
double arr[9]={1,2,3,4,5,6,7,8,9};
Map A(arr,3,3);

map使用的时候,只需要指定map<>中,缺少(dynamic)的维度.
比如

得到结果

1
2
3
4

平均值

对于矩阵:

1 4 7
2 5 8
3 6 9

按行求平均值A.rowwise().mean()

得到:

4
5
6

按列求平均值 A.colwise().mean
得到
2 5 8

unaryExpr()

参数为一元函数算子,表示对每一项应用该一元算子.具体看例子

返回的结果为:
1 16 49
4 25 64
9 36 81

 

replicate

将一个对象重复多干次.

语法为A.replicate(x,y)表示将A横向扩展x次(包含本身),纵向扩展y次(包含本身),共得到x*y个

 

粤ICP备18103363