多线程编程是任何编程语言的一个重要方面,本文对C++11中的多线程编程库以及一些使用方法进行详细介绍。
1. 与 C++11 多线程相关的头文件
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是:
<atomic>
, <thread>
, <mutex>
, <condition_variable>
, <future>
<atomic>
:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。<thread>
:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。<mutex>
:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。<condition_variable>
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。<future>
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
注意,C++11中没有关于信号量的头文件及相关变量的定义。
2. thread 的”Hello world”
下面是一个最简单的使用 std::thread 类的例子:
1 |
|
Makefile:(需要连接pthread, GCC4.6)
1 | all:Thread |
3. thread 详解
std::thread在头文件<thread>
中声明,因此使用 std::thread 时需要包含<thread>
头文件。
操作 | <thread> 函数 |
---|---|
default(1) | thread() noexcept |
initialization (2) | template <class Fn, class... Args>, explicit thread (Fn&& fn, Args&&... args) |
copy [deleted] (3) | thread (const thread&) = delete |
move (4) | thread (thread&& x) noexcept |
(1). 默认构造函数,创建一个空的 thread 执行对象。
(2). 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
(3). 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
(4). move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached。
*主线程中执行joinable对象的join(),相当于在主线程中添加这些thread,也就是在这些添加的thread执行完毕之后才会继续执行主线程,类似嵌入代码。
join进入主线程的thread并不是从头执行,而是继续执行到完毕,或者说主线程等待其执行到完毕。相当于异步执行变为了同步执行。
线程的move操作
move: thread& operator= (thread&& rhs) noexcept;
thread类中的operator=是move操作,不是copy操作
将rhs这个thread对象的状态赋值到*this中
thread object whose state is moved to *this.
move 赋值操作,如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;
如果当前对象可被 joinable,则 terminate() 被调用。
例子如下:
1 |
|
还有一些其余的常规函数,可以查阅手册
std::thread
4. mutex 详解
Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在<mutex>
头文件中,所以如果你需要使用std::mutex
,就必须包含<mutex>
头文件。
Linux的pthread下有pthread_mutex
mutex 相关头文件介绍
Mutex 系列类(四种)
std::mutex
,最基本的 Mutex 类。std::recursive_mutex
,递归 Mutex 类。std::time_mutex
,定时 Mutex 类。std::recursive_timed_mutex
,定时递归 Mutex 类。
Lock 类(两种)
std::lock_guard
,与 Mutex RAII 相关,方便线程对互斥量上锁。std::unique_lock
,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
其他类型
std::once_flag
std::adopt_lock_t
std::defer_lock_t
std::try_to_lock_t
函数
std::try_lock
,尝试同时对多个互斥量上锁。std::lock
,可以同时对多个互斥量上锁。std::call_once
,如果多个线程需要同时调用某个函数,call_once
可以保证多个线程对该函数只调用一次。
mutex用法介绍
下面以std::mutex
为例介绍 C++11 中的互斥量用法。
std::mutex
是C++11 中最基本的互斥量,std::mutex
对象提供了独占所有权的特性——即不支持递归地对std::mutex
对象上锁,而std::recursive_lock
则可以递归地对互斥量对象上锁。
std::mutex 成员函数
构造函数,
std::mutex
不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。lock()
,调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:- (1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
- (2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
- (3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
unlock()
, 解锁,释放对互斥量的所有权。try_lock()
,尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,- (1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
- (2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
- (3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
mutex例子如下:
1 |
|
std::recursive_mutex 介绍
std::recursive_mutex
与std::mutex
一样,也是一种可以被上锁的对象,但是和std::mutex
不同的是,std::recursive_mutex
允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex
释放互斥量时需要调用与该锁层次深度相同次数的unlock()
,可理解为lock()
次数和unlock()
次数相同,除此之外,std::recursive_mutex
的特性和std::mutex
大致相同。
std::time_mutex 介绍
std::time_mutex
比std::mutex
多了两个成员函数,try_lock_for()
,try_lock_until()
。
try_lock_for
函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与std::mutex
的try_lock()
不同,try_lock
如果被调用时没有获得锁则直接返回false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。- 在指定时间内阻塞,知道获得锁。超时返回false
try_lock_until
函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
time_mutext举例如下:
1 |
|
std::recursive_timed_mutex 介绍
和std:recursive_mutex
与std::mutex
的关系一样,std::recursive_timed_mutex
的特性也可以从std::timed_mutex
推导出来。
lock_guard 用法介绍
与 Mutex RAII 相关,方便线程对互斥量上锁。
1 |
|
unique_lock 用法介绍
与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
1 |
|