C++多线程程序员

C++ 并发编程学习(二)

2018-12-20  本文已影响39人  rmrfany

线程管理的基础

一. 启用线程

  1. 线程的使用需要头文件<thread>,以及在编译的时候需要使用 -lpthread;
  2. 起线程时初始函数为普通函数时,第一个参数直接给函数名,后面可携带多个参数;
  3. 起线程时初始函数为一个函数调用操作符重载的类时,可使用以下三种发放进行调用,后面可携带多个参数;
  4. 无论如何,确保线程正确的加入(joined)或分离(detached),必须在 std::thread 对象销毁之前做出决定,否则程序将终止(std::thread的析构函数会调用std::terminate());
#include <iostream>
#include <thread>

class background_task{
public:
  void operator()(int a) {
    do_something();
    do_something_else();
    std::cout << "a = " << 9 <<std::endl;
  }

  void do_something(){
    std::cout << "do_something()" << std::endl;
  };

  void do_something_else(){
    std::cout << "do_something_else()" << std::endl;
  };
};

void task_fun(int a){
  std::cout << "task_fun(): "<< a << std::endl;
  return ;
}

int main(){
  // method 1:
  background_task f;
  std::thread my_thread(f, 9);
  std::thread my_thread2(task_fun,9);

  // method 2:
  // std::thread my_thread((background_task()));
  // std::thread my_thread2((task_fun()));      //x 错误的使用方法

  // method 3:
  // std::thread my_thread{background_task()};
  // std::thread my_thread2{task_fun()};    //x 错误的使用方法

  my_thread.join();
  my_thread2.join();
  return 0;
}
/*程序输出  并发进行,两个线程同时打印,所以顺序乱了
task_fun(): 9do_something()

do_something_else()
a = 9
*/

   join()是简单粗暴的等待线程完成或不等待。当你需要对等待中的线程有更灵活的控制时,比如,看一下某个线程是否结束,或者只等待一段时间(超过时间就判定为超时)。想要做到这些,你需要使用其他机制来完成,比如条件变量和期待(futures)。调用join()的行为,还清理了线程相关的存储部分,这样 std::thread 对象将不再与已经完成的线程有任何关联。这意味着,只能对一个线程使用一次join();一旦已经使用过join(),std::thread 对象就不能再次加入了,当对其使用joinable()时,将返回false。

二. 特殊情况下的等待

#include <iostream>
#include <thread>
#include <unistd.h>

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

private:
  std::thread& t;
};

void task_fun(int *a){
  sleep(10);
  std::cout << "task_fun(): "<< *a << std::endl;
  return ;
}

int main()
{
  int a = 100;
  
  std::thread t(task_fun, &a);
  thread_guard g(t);
  std::cout << "主线程已经结束" << std::endl;

  return 0; 
}
/*程序输出
主线程已经结束
task_fun(): 100  //十秒后
*/

如上程序,如果把thread_guard的析构函数的内容注释掉,那么在程序结束了,可局部变量a还在线程中被使用,这样就会导致析构函数调用std::terminate() ,从而程序崩溃。如上方式可以很好的去防止线程主线程结束后,等待子线程结束任务。拷贝构造函数和拷贝赋值操作被标记为 =delete,是为了不让编译器自动生成它们。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。

三. 后台运行线程

  使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有 std::thread 对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。
  为了从 std::thread 对象中分离线程(前提是有可进行分离的线程),不能对没有执行线程的 std::thread 对象使用detach(),也是join()的使用条件,并且要用同样的方式进行检查——当 std::thread 对象使用t.joinable()返回的是true,就可以使用t.detach()。

上一篇 下一篇

猜你喜欢

热点阅读