c++并发编程(一)---基本线程管理

1. 启动线程

线程是通过构造std::thread对象开始的,对该对象指定了线程上要运行的任务,在最简单的情况下,该任务仅仅是一个普普通通的返回void而且不接受参数的函数,这个函数在自己的线程上运行,直到返回,然后线程停止。但从另一个极端看,该任务可能是一个接收额外参数的函数对象,放它运行时,会执行一系列由某种消息机制所指定的相互独立的操作,并且只有当线程再次通过某种消息机制接收到信号才会停止。无论线程将要做什么或是从哪里启动,都需要构造std::thread对象。
 #include<thread>
 void do_some_work()
 std::thread my_thread(do_some_work);

也可以将一个带有函数调用操作符的实例(如仿函数)传递给std::thread的构造函数来进行替代。

class background_task
{
     public:
          void operator() ()const
          {
               do_something();
               do_something_else();
          }
};
background_task f;
std::thread my_thread(f);

还可以使用lambda表达式。

std::thread my_thread([](){
               do_something();
               });

一旦线程启动,你需要显式的决定等待完成还是让它自行运行,如果你在std::thread对象被销毁前还未做决定,那么你的程序会被终止(std::thread的析构函数会调用std::terminate())。因此在异常存在的情况下,确保线程正确地结合或者分离是当务之急。如果你不等待线程完成,那么你需要确保通过该线程访问的数据是有效的,直到线程完成为止。在对象销毁后还访问它是未定义行为。
如:

struct func
{
     int& i;
     func(int& i_):i(i_){}
     void operator() ()
     {
           for(unsigned int j=0;j<1000000;++j)
           {
               do_something(i);
           }
     }
};
void oops()
{
      int some_local_state=0;
      func my_func(some_local_state);
      std::thread my_thread(my_func);
      my_thread.detach();    //不等待线程完成  
                                           //新的线程仍在运行
}

在这种情况下,当oops退出时线程还在运行,但是存储在栈中的局部变量随着函数的退出被释放,此时线程中仍在访问该变量,所以会发生未定义行为。此时可在代码中加入my_thread.join()解决。使用join()需要注意一个问题,为了避免应用程序在引发异常的时候被终止,需要注意在存在异常的时候调用join(),以避免意外的生命周期问题。

struct fun;   //详见上图代码
void f()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try
    {
        do_something_in_current_thread();
    }
    catch(....)
    {
        t.join();    //异常中断条件下
        throw;
    }
    t.join();    //正常退出条件下
}

使用try/catch块很啰嗦,而且很容易将作用域弄luan,这里可以使用资源获取即初始化(RAII)惯用方法,并提供一个类,在它的析构函数中进行join()。

class thread_guard
{
     std::thread &t;
     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;//将拷贝构造函数和拷贝运算符标记为delete,防止对象被复制或赋值。
}
struct func;
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();
}

当代码执行到f末尾时,局部对象会按照构造函数的逆序被销毁。因此,thread_guard对象g首先被销毁,并且析构函数中t.join()被结合,即使函数因为do_something_in_current_thread引发异常而退出的情况下也会发生。如果无需等待线程完成,可以通过分离来避免这个异常安全问题。这打破了线程与std::thread对象联系并确保当std::thread对象被销毁时std::terminate()不会被调用,即使线程仍在后台运行。

2. 传递参数给线程函数

    传递参数给可调用对象或函数,基本上就是简单的将额外的参数传递给std::thread的构造函数,但重要的是参数会以默认的方式被复制到内部的存储空间,在那里新创建的执行线程可以访问它们,即便函数中相应参数期待着引用。
   

void f(int i,std::string const & s);
std::thread t(f,3,"hello");

这里创建一个新的与t相关联的执行线程,称为f(3,”hello”)。注意即使f接收std::string作为第二个参数时,字符串字面值仅在新线程的上下文中才作为char const*传送,并转换为std::string。尤其重要的是当提供的参数是一个自动变量的指针时。

void f(int i,std::string const& s);
void oops(int some_param)
{
      char buffer[1024];
      sprintf(buffer,"%i",some_param);
      std::thread t(f,3,buffer);
      t.detach();
}

如果oops在buffer在转化成std::string之前退出会发生未定义行为,因此解决之道是直接在std::thread的构造函数中传入std::string(buffer);

3.转移线程所有权。

c++标准库有许多拥有资源的类型,如std::ifstream和std::unique_ptr,都是可移动的而非可复制的,而且std::thread也是其中之一,都将拷贝构造函数和拷贝运算符写入私有,或者声明为delete。一个特定执行的线程所有权可以在std::thread之间移动。如下例所示,该实例创建了两个执行线程,以及三个std::thread实例t1,t2和t3之间对那些线程的所有权进行转移。

 void some_function();
 void some_other_function();
 std::thread t1(some_function);   //①
 std::thread t2=std::move(t1);     //②
 t1=std::thread(some_other_function); //③
 std::thread t3;                             //④
 t3=std::move(t2);                        //⑤
 t1=std::move(t3);                        //⑥       

首先启动一个新线程①与t1关联,然后t2构建完成时所有权被转移给t2,通过调用std::move()来显式地转移所有权②。此刻t1不再拥有相关联的执行线程,运行some_function的线程现在与t2相关联。
然后,启动一个新的线程并与一个临时的std::thread对象相关联③,接下来将所以权转移到t1中,是不需要调用std::move()来显式转移所有权的,因为此处所有者是一个临时对象-从临时对象中进行移动是自动的和隐式的。
t3是默认构造的④,这意味着没有默认相关联的线程,当前t2转移给t3,t1与some_other_function()相关联,t2没有关联的线程,t3与运行的some_function()关联。
最后一次移动⑥将运行some_function()的线程的所以权转回给t1,但t1已经有一个相关联的线程,所以会调用std::terminate()来终止程序。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值