C++并发实战第二章笔记

线程在std::thread对象创建的时候启动,而类似Java或Qt的线程,线程在调用start()函数时启动

使用有调用运算符的类实例作为thread实参:

class background_task
{
public:
  void operator()() const
  {
    do_something();
    do_something_else();
  }
};

background_task f;
std::thread my_thread(f);

函数对象会被复制到线程的存储空间中,在线程的内存空间执行和调用

thread构造语法:

1.

  • std::thread my_thread(background_task());    //错误,此处声明了一个返回值是thread,形参是个函数指针,函数指针指向返回值是空,参数是空的函数
  • std::thread my_thread((background_task()));  //对
  • std::thread my_thread{background_task()};    //对

2.std::function f = []() {...} std::thread my_thread(f); //这种方法的好处是线程的启动可以延迟
3.使用异步API std::async或boost::async代替thread

thread t创建后必须要在某处调用t.join()(加入)或t.detach(分离)
thread必须在销毁之前调用join或detach,否则程序会终止(thread在析构的时候会调用std::terminate)
根据文档,在异常处理失败时(此处原因是joinable线程销毁了),C++ runtime会调用std::terminate,而std::terminate会调用std::abort,std::abort默认的行为是终止程序,代码不会继续往下执行

一个很容易犯的错误就是线程分离后,访问了已经销毁的局部变量的指针或引用:

struct func
{
  int& i;
  func(int& i_) : i(i_) {}
  void operator() ()
  {
    for (unsigned j=0 ; j<1000000 ; ++j)
    {
      do_something(i);           // 1. 潜在访问隐患:悬空引用
    }
  }
};

void oops()
{
  int some_local_state=0;
  func my_func(some_local_state);
  std::thread my_thread(my_func);
  my_thread.detach();          // 2. 不等待线程结束
}                              // 3. 新线程可能还在运行

所以当线程参数包含指针或引用的时候一定要小心
目前有两种解决方案:
1.主线程调用t.join等待子线程结束,同步等待
2.传参的时候我个人推荐使用智能指针shared_ptr,不需要关心参数资源释放问题

t.join只能调用一次,所以可以配合t.joinable使用
需要注意的一点是在不使用异常捕获机制的前提下:在线程创建后,t.join调用之前不能抛出异常,否则程序会终止
我认为如果整体架构下不使用异常捕获机制try-catch,就必须由自己保证没有异常
如果使用try-catch,那么可以在catch里面调用t.join保证程序继续执行

更聪明的做法是使用“资源获取即初始化方式”(RAII,Resource Acquisition Is Initialization):

class thread_guard
{
  std::thread& t;
public:
  explicit thread_guard(std::thread& t_):
    t(t_)
  {}
  ~thread_guard()
  {
    if(t.joinable()) // 1
    {
      t.join();      // 2
    }
  }
  thread_guard(thread_guard const&)=delete;   // 3
  thread_guard& operator=(thread_guard const&)=delete;
};

struct func; // 定义在清单2.1中

void f()
{
  int some_local_state=0;
  func my_func(some_local_state);
  std::thread t(my_func);
  thread_guard g(t);
  do_something_in_current_thread();
}    // 4

RAII的例子还有智能指针,lock_guard


为什么要用RAII?
C++保证栈上的对象即使异常也能能够正常被析构,也就是说RAII类是异常安全的

向线程函数传递参数:

void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
  widget_data data;
  std::thread t(update_data_for_widget,w,data); // 2 期望修改data
  display_status();
  t.join();
  process_widget_data(data); // 3	data还是1中传入的data
}

上述代码期望传递引用到函数参数里,但是thread构造的时候对参数进行的是拷贝,
即使传的是引用,也会对引用数据进行拷贝,要想达到在线程函数内部引用数据的目的,需要使用std::ref/std::cref
参数是先传给thread,再由thread传给线程函数的,而thread会默认拷贝参数
这个错还是很容易犯的,当我们需要使用参数保证线程间同步时,就会用到std::ref
这里还有一点要注意的,传给thread的类对象必须是可拷贝的,也就是具有拷贝构造函数,atomic_bool就没有拷贝构造

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于需要学习并发编程的人员来说,《Java并发编程实战》是一本非常经典的书籍。现在这本书已经改版,发布了第二版。想要下载《Java并发编程实战 第二版》PDF文档,可以在网上进行搜索。为了保证下载到正版的电子书,最好选择合法的网站进行下载。如果不清楚哪些是合法的下载网站,可以参考一些知名的IT科技网站,如CSDN、博客园,或直接到出版社官网下载。在学习并发编程的过程中,需要深入理解书中的并发编程基础知识,包括线程安全、锁、原子性等概念,学习掌握Java并发包和工具的使用。同时,在实际应用中要注意并发问题的处理,避免产生死锁、性能问题以及其他并发编程常见的问题。《Java并发编程实战 第二版》不仅可以帮助读者深入理解并发编程,还能为读者提供丰富的例子和实战经验,是一本非常实用的并发编程技术书籍。 ### 回答2: C++ 并发编程实战是一本关于多线程和并发编程的经典著作,本书已经推出了第二版。该书介绍了多线程及其相关技术,包括线程之间的同步、互斥、锁、原子操作等。此外,本书还涵盖了异步编程、并发数据结构、并发算法等高级主题。 如果你需要深入了解 C++ 并发编程,这本书就是你的一个不错的选择。该书主要分为三部分:基础知识、高级话题以及案例研究。第一部分主要介绍了多线程编程的概念、理论和实践,讲述了如何并发编程、如何避免竞争条件以及如何确保线程之间的同步。第二部分涵盖了一些高级话题,如锁、原子操作、并发算法等内容。最后一部分则是案例研究,该部分通过一个完整的示例来演示并发编程的各个方面。 总之,本书是一本系统全面的 C++ 并发编程教材,适合具备 C++ 编程基础的读者阅读。如果你想深入理解多线程和并发编程,这本书是一个不错的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值