加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

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()
{
  std::cout << "thread id = " << std::this_thread::get_id() << std::endl;
}

int main()
{
  std::thread td(thread_proc); // 创建thread对象即创建了一个新的线程并执行
  td.join(); // 等待线程执行结束
}

线程的执行体
std::thread的执行体并不要求必须是普通的函数,任何可调用(Callable)的对象都是可以的。举例如下:

Lambda表达式
#include
#include

int main()
{
  std::thread td([](int a, int b){
    std::cout << a << "+" << b << "=" << a + b << std::endl;
  }, 1, 2); // 使用Lambda表达式创建线程对象并传递参数1和2
  td.join(); // 等待线程执行结束
}

重载了operator()的类的对象
#include
#include

struct functor {
  void operator() (int a, int b) {
    std::cout << a << "+" << b << "=" << a + b << std::endl;
  }
};

int main()
{
  std::thread td(functor(), 1, 2); // 使用functor对象创建线程对象并传递参数1和2
  td.join(); // 等待线程执行结束
}

此外还可以使用std::bind表达式和Lambda表达式间接的让非静态成员函数做为执行体
使用std::bind表达式绑定对象和其非静态成员函数

#include
#include
#include

class C {
  int data_;
public:
  C(int data) : data_(data) {}
  void member_func() {
    std::cout << "this->data_ = " << this->data_ << std::endl;
  }
};

int main()
{
  C obj(10);
  std::thread td(std::bind(&C::member_func, &obj));
  td.join(); // 等待线程执行结束
}

使用Lambda表达式调用对象的非静态成员函数

#include
#include

class C {
  int data_;
public:
  C(int data) : data_(data) {}
  void member_func() {
    std::cout << "this->data_ = " << this->data_ << std::endl;
  }
};

int main()
{
  C obj(10);
  std::thread td([&obj](){
    obj.member_func();
  });
  td.join(); // 等待线程执行结束
}
以上两段代码都输出
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()
{
  std::thread td;
  std::cout << "td.joinable() = " << std::boolalpha << td.joinable() << std::endl;
}

输出
td.joinable() = false

调用过join的thread对象
通过对join成员函数的调用可以使joinable为true的thread对象在join返回后变成false

#include
#include
#include

int main()
{
  std::thread td([](){});
  std::cout << "td.joinable() = " << std::boolalpha << td.joinable() << std::endl;
  td.join();
  std::cout << "td.joinable() = " << std::boolalpha << td.joinable() << std::endl;
}

输出
td.joinable() = true
td.joinable() = false

调用过detach的thread对象
通过对detach成员函数的调用允许线程不再受thread对象管理,所以thread对象的joinable自然变成false了
#include
#include
#include

int main()
{
  std::thread td([](){});
  std::cout << "td.joinable() = " << std::boolalpha << td.joinable() << std::endl;
  td.detach();
  std::cout << "td.joinable() = " << std::boolalpha << td.joinable() << std::endl;
}

输出
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)
{
  std::cout << "thread id = " << std::this_thread::get_id() << ", a = " << a << ", b = " << b << std::endl;
  if(std::numeric_limits::max() - a < b) {
    throw std::overflow_error("overflow_error");
  }
  return a + b;
}

int main()
{
  // 使用std::async启动异步操作并返回std::future对象
  std::future f1 = std::async(std::launch::async, add, 1ul, 2ul);
  // 通过std::future::get等待异步操作完成并取得返回值
  std::uint32_t sum1 = f1.get();
  std::cout << "thread id = " << std::this_thread::get_id() << ", sum1 = " << sum1 << std::endl;
  // 4000000000ul + 4000000000ul会抛出异常,异常会被延迟到std::future::get或std::future::wait时抛出
  std::future f2 = std::async(std::launch::async, add, 4000000000ul, 4000000000ul);
  try {
    std::uint32_t sum2 = f2.get();
    std::cout << "thread id = " << std::this_thread::get_id() << ", sum2 = " << sum2 << std::endl;
  }
  catch(const std::overflow_error& e) {
    std::cout << "thread id = " << std::this_thread::get_id() << ", e.what() = " << e.what() << std::endl;
  }
}

输出
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)
{
  std::cout << "thread id = " << std::this_thread::get_id() << ", a = " << a << ", b = " << b << std::endl;
  if(std::numeric_limits::max() - a < b) {
    throw std::overflow_error("overflow_error");
  }
  return a + b;
}

int main()
{
  // 使用std::packaged_task包装add函数
  std::packaged_task task1(add);
  // 取得std::future用于获取异步操作的结果
  std::future f1 = task1.get_future();
  // 将task1对象作为线程的函数执行体
  std::thread(std::move(task1), 1ul, 2ul).detach();
  // 通过std::future对象获取异步操作的结果
  std::uint32_t sum1 = f1.get();
  std::cout << "thread id = " << std::this_thread::get_id() << ", sum1 = " << sum1 << std::endl;
  std::packaged_task task2(add);
  std::future f2 = task2.get_future();
  // 4000000000ul + 4000000000ul会抛出异常
  std::thread(std::move(task2), 4000000000ul, 4000000000ul).detach();
  try {
    std::uint32_t sum2 = f2.get();
    std::cout << "thread id = " << std::this_thread::get_id() << ", sum2 = " << sum2 << std::endl;
  }
  catch(const std::overflow_error& e) {
    std::cout << "thread id = " << std::this_thread::get_id() << ", e.what() = " << e.what() << std::endl;
  }
}

输出
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)
{
  std::cout << "thread id = " << std::this_thread::get_id() << ", a = " << a << ", b = " << b << std::endl;
  if(std::numeric_limits::max() - a < b) {
    throw std::overflow_error("overflow_error");
  }
  return a + b;
}

// add函数的包装函数
void add_wrapper(std::promise promise, std::uint32_t a, std::uint32_t b)
{
  try {
    // 设置值为供std::future对象获取
    promise.set_value(add(a, b));
  }
  catch(...) {
    // 设置异常在std::future获取值时抛出
    promise.set_exception(std::current_exception());
  }
}

int main()
{
  // 创建std::promise对象
  std::promise promise1;
  // 获取关联的std::future对象
  std::future f1 = promise1.get_future();
  // 启动线程执行add函数的包装函数
  std::thread(add_wrapper, std::move(promise1), 1ul, 2ul).detach();
  // 等待并获取异步操作的结果
  std::uint32_t sum1 = f1.get();
  std::cout << "thread id = " << std::this_thread::get_id() << ", sum1 = " << sum1 << std::endl;
  std::promise promise2;
  std::future f2 = promise2.get_future();
  // 4000000000ul + 4000000000ul会抛出异常
  std::thread(add_wrapper, std::move(promise2), 4000000000ul, 4000000000ul).detach();
  try {
    std::uint32_t sum2 = f2.get();
    std::cout << "thread id = " << std::this_thread::get_id() << ", sum2 = " << sum2 << std::endl;
  }
  catch(const std::overflow_error& e) {
    std::cout << "thread id = " << std::this_thread::get_id() << ", e.what() = " << e.what() << std::endl;
  }
}

输出
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来实现

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有