《C++ Concurrency In Action》读书笔记 - 线程管理
C++11标准中加入了对多线程编程的支持,主要添加了下面一些方面:
- 定义了内存模型
<thread>,引入std::thread来管理、控制线程。<atomic>,原子操作相关类。<mutex>,引入mutex,以及相关的类,用于锁等互斥操作。<condition_variable>,引入condition_variable以及相关类,用于线程间同步。<future>,引入future,promise以及相关的一些类,主要用于线程间同步和通信的一些机制。
这篇主要介绍对于线程管理的相关话题,对书中的内容进行总结并适当扩展。
- 如何创建一个线程
- 如何销毁一个线程
std::thread的所有权(owenership)- 如何控制线程数量
- 线程ID
如何创建一个线程
1 | template< class Function, class... Args > |
std::thread构造函数如上,它可以接受任何callable的类型,包括全局方法、成员方法、std::function、仿函数、lambda表达式等。并且后面可以带参数,用来传递给方法。
例子:
1 |
|
注意事项:
std::thread对象一旦创建,线程马上就起来了,没有办法延迟启动。std::thread一旦起来以后没有办法通过api它杀掉,除非通过native_handle拿到平台相关的线程句柄进行操作。这个非C++提供而是操作系统提供。
传参
启动线程的时候可以传递参数,传参的形式默认为拷贝,如果有类型转换则是在线程启动时进行。所以需要保证传递的参数不依赖于方法栈上的其他内存。
如果不满足于默认的按值拷贝,可以通过std::ref或std::move进行传引用或转成右值进行move操作。下面给出几个例子。
1 |
|
注意事项:
std::ref注意引用的生命周期和线程的生命周期的关系。
如何销毁一个线程
你没法销毁一个线程,你只能等它跑完或者让他在后台运行并且把其管理权移交给C++运行库。
下面介绍一组和线程结束相关的api:
bool jionable(): 如果是默认线程(即std::thread t;)则是false。只要一个线程被join过一次以后则变为false,即使一个线程已经结束了,但是没有被join过,还是会返回true。void join()。等待线程结束运行,一个线程只能被调用一次,如果在jioinable为false的情况下调用,会直接抛出std::system_error。void detach()。将线程放到后台运行,并且将管理权转给C++运行库。
注意事项:
- 一个线程在析构前必须调用
join或者detach来显示的处理它的生命周期,否则的话会析构时直接抛出异常。 join本身并非线程安全,如果多个线程在没有保护的情况下同时join同一个std::thread会undefined behavior
std::thread的所有权(owenership)
No two std::thread objects may represent the same thread of execution; std::thread is not CopyConstructible or CopyAssignable, although it is MoveConstructible and MoveAssignable.
http://en.cppreference.com/w/cpp/thread/thread
std::thread 只能move不能被拷贝,所以如果想要把std::thread放进容器的话必须确保是右值。直接构造出来或者用std::move进行转换都可以。
比如下面这样:
1 | void calc_something(int i){ std::cout << i; } |
如何控制线程数量
线程数量并非越多越好,数量太多会导致CPU的频繁上下文切换,反而会适得其反。std::thread::hardware_concurrency静态方法可以获得。
Returns the number of concurrent threads supported by the implementation. The value should be considered only a hint.
http://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency
但是这个数量仅供参考,因为依赖于不同编译器和平台的实现。有可能会返回0,表示这个数量未定义,也就是说不知道在这个硬件上有多少个并发线程可以执行。
线程ID
通过 std::thread::id std::thraed::get_id()可以拿到一个线程的id。这个id主要的用途是用来打印log和作为关系容器的key。
1 | std::map<std::thread::id, std::thread> thread_map; |

