C++ 11之多线程(标准库的线程封装类Thread和Future)
(2017-12-29 20:24:20)
标签:
it |
分类: 技术 |
前言
学习C++11的多线程已经有一段时间了,所以打算整理记录下来,分成五篇文章:
一、标准库的线程封装类Thread和Future
二、互斥对象(Mutex)和锁(Lock)
三、条件变量(Condition Variable)
四、原子操作(Atomic Operation)
五、内存序(Memory Order)
多线程概述
线程(Thread)是程序中独立执行的指令流,通常是系统调度的基本单位。含有两个或两个以上线程的程序就称为多线程程序,在硬件允许的情况下多个线程可以彼此独立的并行执行。在许多情况下都需要使用多线程,比如:
程序需要进行一些IO操作,IO操作通常需要一些时间才能完成,在这段时间线程就会被阻塞(Block)。使用多线程就可以启动一个新的线程去等待IO操作完成,而当前线程就可不被阻塞的继续执行别的工作;
在多核心处理器的机器上执行计算密集型的程序。使用多线程就可以将计算工作切分成多个独立的计算工作交给多个线程在CPU的不同核心上并行计算。
C++11开始提供了对多线程的支持。标准库提供了std::thread类来创建和管理线程,std::future类模板来获取异步操作的结果。
std::thread类
下面的代码演示了使用std::thread类在主线程中创建了一个线程对象td,创建的线程执行函数thread_proc中的代码输出线程id。主线程调用thread对象td的join函数等待线程执行结束。
#include
#include
void thread_proc()
{
}
int main()
{
}
线程的执行体
std::thread的执行体并不要求必须是普通的函数,任何可调用(Callable)的对象都是可以的。举例如下:
Lambda表达式
#include
#include
int main()
{
}
重载了operator()的类的对象
#include
#include
struct functor {
};
int main()
{
}
此外还可以使用std::bind表达式和Lambda表达式间接的让非静态成员函数做为执行体
使用std::bind表达式绑定对象和其非静态成员函数
#include
#include
#include
class C {
public:
};
int main()
{
}
使用Lambda表达式调用对象的非静态成员函数
#include
#include
class C {
public:
};
int main()
{
}
以上两段代码都输出
1
this->data_ = 10
等待线程执行结束
无论是使用std::bind表达式还是Lambda表达式都需要注意保证obj对象不能在函数执行完成前被销毁。
需要特别注意的是std::thread对象有个成员函数joinable用于判断线程对象是否是可以join的。当线程对象被析构的时候如果对象joinable()
==
true会导致std::terminate()被调用。所以要让thread对象被正确的析构就需要知道什么情况下joinable()为false了
默认构造函数构造的thread对象
默认构造函数构造的对象不代表任何线程,所以joinable为false。
#include
#include
#include
int main()
{
}
输出
td.joinable() = false
调用过join的thread对象
通过对join成员函数的调用可以使joinable为true的thread对象在join返回后变成false。
#include
#include
#include
int main()
{
}
输出
td.joinable() = true
td.joinable() = false
调用过detach的thread对象
通过对detach成员函数的调用允许线程不再受thread对象管理,所以thread对象的joinable自然变成false了。
#include
#include
#include
int main()
{
}
输出
td.joinable() = true
td.joinable() = false
其他各种原因不再拥有线程所有权的情况
如以移动语义(move
semantic)构造或复制给另一个thread对象,与joinable为false的对象通过std::swap交换等。
线程管理函数
除了std::thread的成员函数外在std::this_thread命名空间中也定义了一系列函数用于管理当前线程。
函数名 说明
yield 建议线程调度者执行其他线程,相当于主动让出剩下的执行时间,具体调度算法取决于实现。
get_id 获取当前线程的线程id。
sleep_for 指定的一段时间内停止当前线程的执行。
sleep_until 停止当前线程的执行直到指定的时间点。
std::future类模板
std::future类模板是标准库提供的一种用于获取异步操作的结果的机制。前面的演示代码中线程执行函数体都没有返回值,事实上std::thread的线程执行函数是可以有返回值的,但是其返回值会被忽略。此外使用std::future还可以延迟异步操作中异常(Exception)的抛出。下面的代码演示了通过std::async启动一个异步操作,并通过std::future::get取得返回值和捕获异步操作抛出的异常。
#include
#include
#include
#include
#include
#include
std::uint32_t add(std::uint32_t a, std::uint32_t b)
{
}
int main()
{
}
输出
thread id = 2, a = 1, b = 2
thread id = 1, sum1 = 3
thread id = 3, a = 4000000000, b = 4000000000
thread id = 1, e.what() = overflow_error
使用std::future获取std::thread对象创建线程异步操作的结果
使用std::future获取std::thread对象创建线程异步操作的结果有两种方法。
使用std::packaged_task类模板
使用std::packaged_task类模板来使std::future能够获取std::thread对象创建线程的异步操作结果的步骤如下。
使用std::packaged_task包装线程执行函数获得一个std::packaged_task对象,该对象会处理被包装函数的返回值和异常;
通过这个std::packaged_task对象获取其关联的std::future对象,用于获取异步操作的结果;
将std::packaged_task对象做为std::thread对象的线程执行函数,启动线程;
通过std::future对象获取其返回值。
演示代码如下
#include
#include
#include
#include
#include
#include
#include
std::uint32_t add(std::uint32_t a, std::uint32_t b)
{
}
int main()
{
}
输出
thread id = 2, a = 1, b = 2
thread id = 1, sum1 = 3
thread id = 3, a = 4000000000, b = 4000000000
thread id = 1, e.what() = overflow_error
使用std::promise类模板
使用std::promise类模板来使std::future能够获取std::thread对象创建线程的异步操作结果的步骤如下。
创建一个std::promise对象;
获取该std::promise对象的关联的std::future对象;
使用std::thread创建线程并将std::promise对象传进去;
线程执行函数内部通过std::promise的set_value、set_value_at_thread_exit、set_exception或set_exception_at_thread_exit设置值或异常供std::future对象获取;
使用std::future对象等待并获取异步操作的结果。
演示代码如下
#include
#include
#include
#include
#include
#include
#include
#include
std::uint32_t add(std::uint32_t a, std::uint32_t b)
{
}
// add函数的包装函数
void add_wrapper(std::promise promise, std::uint32_t a,
std::uint32_t b)
{
}
int main()
{
}
输出
thread id = 2, a = 1, b = 2
thread id = 1, sum1 = 3
thread id = 3, a = 4000000000, b = 4000000000
thread id = 1, e.what() = overflow_error
从实现的角度来看,std::async内部可以使用std::packaged_task来实现,而std::packaged_task内部可以使用std::promise来实现。
前一篇:肝功检查应该注意什么?