《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; |