std::thread I – 加载(launch)新线程

标准库使用std::thread()启动一个线程, 原型如下:

最直接的方式就是顶一个函数, 将函数及其实参一同传入构造的thread实例, 此外, 在实际编码中, 还有几种常见的编码形式来启动一个线程

使用类

可以借助C++的类的封装, 将一个线程的资源和运行上下文封装在一起:

使用std::thread构造线程对象时, 如果使用临时对象而不是有名对象来构造, 就要注意下面这种写法是错误的.

事实上, 在编译器眼中, std::thread t(BackGroundTask()); 并不是用构造一个临时的BackGroundTask对象并用它来构造一个 std::thread对象, 而是声明了一个 “以函数对象为参数, 返回值为std::thread的函数t, 它的入参函数的参数是空, 返回值为BackGroundTask类型”. 如果修改这句话使之能够按照我们期望的方向构造std::thread实例, 可以改成下面两种写法:

使用lambda

从C++ XI开始, 有了引入的lambda表达式, 我们也可以不用上述的封装方式, 使用lambda的capture list也可以方便的实现有参线程的启动, 重要的是不用单独定义线程执行函数, 特别适合线程函数只启动一次的场景

条件变量 II – std::condition_variable

条件变量是多线程同步的一种常用方法, 两个线程同步, 如果一个线程在到达同步点之前可能等待很久, 以至产生的额外开销超过了线程调度的开销, 这种情况就可以使用条件变量来同步, 反之, 如果等待的开销比较小, 就可以使用while(){sleep_1ms()}的方式来完成同步, 类似的思路和内核的spin_lock和mutex的区别一样.

C++ STL 提供了相对完善的并行编程接口, 当然少不了对于条件变量的实现. 和pthread实现相比, 除了接口更加面向对象, 并无明显不同, 毕竟, “条件变量的正确使用方式只有一种”. 使用条件变量, 需要准备三件材料: 1个条件 + 2个变量 + 多个线程.

  • 1个条件: 和所有的并行代码一样, 搞清楚执行流之间何时同步, 如何同步是整个程序的重中之重
  • 2个变量: 1个条件变量 + 1把mutex, 当然, 在不同的语言中这种mutex的实现可以不同, 但其本质并没有改变
  • 多个线程: 条件变量只能用于多线程同步, 不能用于多进程
Continue reading

C++ MiniTrick

时间戳打印

master-slave工作线程各自初始化

在master-worker模型中, 二者的初始化逻辑往往不同, 如果第一个执行的线程是master, 可以使用下面的代码来标识出:

Continue reading

C++ 右值引用与move()

C++11之前的标准有两种构造函数: 构造与拷贝构造, 前者用于直接分配一个对象并初始化: Instance original, 后者用于分配一个对象并通过”拷贝已有对象”方式初始化: Instance other = original, 编译器缺省会为每一个类提供一个构造和拷贝构造函数, 但对于管理堆内存等资源的类来说, 这种缺省提供的函数最大的问题就在于很容易造成资源的”浅拷贝”, 而要完成正确的”深拷贝”, 通常需要自己实现一个拷贝构造.

浅拷贝的问题显而易见, 两个对象底层管理着同样一块内存, 一旦一个对象执行了析构函数释放了内存, 那么会导致另外一个对象不可用, 通过重载完成底层内存的拷贝构造可以很好的解决这个问题. 但是, 深拷贝同样也有问题, 如果类中资源巨大, 那么每次拷贝一遍的开销就变得不可接受, 所以 有时候我们还想要这样一种功能: 将资源的管理权交接给另一个对象, 既防止了复制内存的开销, 又防止了两个对象引用了同一块内存. 就功能上来说, 我们完全可以自己封装函数实现这样的功能, 但作为一类和构造相关的功能, 显然如果编译器原生支持更好.

这种管理权交割看起来和拷贝构造如出一辙, 只要提供另外一种构造函数即可. 回顾拷贝构造的场景:

为了控制L3和L4的行为, 我们分别重载了拷贝构造和赋值运算符:

在上述代码中, original_0original_1都是左值, 即既能在等号右边, 也能在等号左边的值, 而我们在拷贝构造的实现中, 用const Instance &来表示这类值.

此外, C++的设计还有一类右值, 即只能出现在等号右边的值, 典型的就是常量

上述代码中的3就是一个右值, 我们只能取其值, 不能赋其值.

Continue reading

C++的引用与const引用

C++ 的引用源自C语言中对指针的使用, 众所周知, C的强大和巨坑都来自于指针, 所以C++着力进行了取其精华, 去其槽粕的改良, 其中, 引用(reference)就是其中一个关键. C++ 的引用可以看做是C中的”指针常量”的封装, “指针常量”, 即不能修改其指向的指针, 该指针的指向必须在初始化时指定, 在此基础上, C++ 引用针对代码的安全性和可读性进行了分类整理升级. C++ 的引用可以分为“引用”“常量引用”,

引用

引用按照 type& ref_name = object的形式初始化, 所以, 初始化引用之前一定要明确即将绑定(bind)的对象的类型. 比如, 变量的地址就是”指针常量”类型, 因为一个变量的地址在编译时期被分配的时候即被确定, 所以不是 type *而是type * const

const 引用

如果说引用是”指针常量”, 那么”常量引用”就是”常量指针常量”: 不但指针的指向不可以改变, 也不可以通过指针改变指向的内容.

有了指针的基础来理解引用, 很多问题就方便解释的多, 比如, 不能用将一个non-const 引用bind到一个const变量/引用上, 因为const指针是比指针更严格的限定, 在全靠程序员自觉的C语言中, 缺省编译器配置下这是一个warning:

但在作为强类型语言的C++中, 这就是一个error:

Continue reading