美文网首页C++多线程
{ 1 }CPP_线程管理的基础

{ 1 }CPP_线程管理的基础

作者: 庄阿莫 | 来源:发表于2018-11-23 23:41 被阅读116次

    一,启动线程

    1. 使用C++线程库启动线程,即为构造std::thread对象:

    void do_some_work();
    std::thread my_thread( do_some_work );
    

    std::thread构造方法

    class A
    {
    public:
      void operator()() const
      {
         std::cout << "hello" << std::endl;
      }
    };
    
    A a;
    std::thread my_thread(a);
    

    std::thread my_thread(a);调用函数对象a() 传入thread中 ,并把函数对象a 复制到新线程的存储空间中,函数对象的执行和调用都在线程的内存空间中进行。

    注意:

    • 如果你传递给thread是一个临时变量,而不是一个已经声明的变量时,会出现C++最令人头痛的语法解析(C++’s most vexing parse),C++编译器解析出的结果将会与你所想的出入很大:
    std::thread my_thread( A() );//A()是一个临时的函数对象
    

    这段代码被解析为:
    这是一个函数声明,函数名为my_thread。它可以接收一个没有参数的,并且会返回对象A的函数指针。my_thread函数的返回值是std::thread类型。

    怎么解决?
    使用多组括号,初始化语法 或者 前面代码 都可以避免这个问题:
    std::thread my_thread( (A()) );
    std::thread my_thread{ A() };

    • 如果不等待线程,必须保证线程结束之前,数据的有效性
    struct F
    {
      int &i;
      func(int &i_) : i( i _ )  { }
    
      void operator() ()
      {
        for(unsigned j = 0; j < 100000; ++j)
        {
            std::cout << i << std::endl;
         }
      }
    }
    
    void oop()
    {
       int number = 0;
       F my_f(unmber);
       std::thread my_f(my_f);
       my_f.detach();
    }
    

    由于不等待线程结束, 当oop()执行完成时, 新线程中的函数可能还在运行。 如果线程还在运行, 它就会去调用do_something(i)函数,这时,引用对象 i 已经被销毁了。

    二,等待线程完成

    如果需要等待线程, std::thread 实例可以使用join()。

    class func1{//仿函数对于多线程的运用
    public:
        int &a;
        explicit func1(int &a_) : a(a_) {}
    
        int operator()(int c)
        {
            for(;a < c; ++a)
            {
                cout << a << endl;
            }
            cout << a << endl;
            return a;
        }
    };
    
    int main()
    {
        int st = 6;
        func1 f(st);
    
        int c = 10;
        std::thread my_thread (f, c);
        my_thread.join();
    
        return 0;
    }
    

    三,特殊情况下的等待

    当倾向在无异常的情况下使用join()时, 需要在异常处理过程中调用join(), 从而避免生命周期的问题。

    • 可以通过使用“资源获取即初始化方式”(RAII, Resource Acquisition Is Initialization)解决此问题。它提供一个类, 在析构函数中使用join():
    struct F
    {
      int &i;
      func(int &i_) : i( i _ )  { }
    
      void operator() ()
      {
        for(unsigned j = 0; j < 100000; ++j)
        {
            std::cout << i << std::endl;
         }
      }
    }
    
    class G
    {
      std::thread& t;
    
    public:
      explicit G(std::thread& t_) : t(t_) {}
      ~G()
      {
        if(t.joinable()) 
        {
          t.join(); 
        }
      } 
    
      G( G const& )=delete;//不让编译器自动生成,因为这可能会弄丢已经加入的线程。
      G& operator= ( G const& )=delete;
    };
    
    struct F;
    
    void oop()
    {
      int data = 0;
      F struct_my_f(data);
      std::thread my_t(struct_my_f);
      G my_g(t);
      do_something_in_current_thread();
    } 
    

    函数oop() 执行完,对象my_g 也会被销毁, 即使do_something_in_current_thread() 抛出一个异常, 这个销毁依旧会发生。

    四,后台运行线程

    如果不想等待线程结束, 可以分离(detaching)线程, 从而避免异常安全(exception-safety)问题。 由于使用detach()会让线程在后台运行,如果线程分离, 那么就不可能有 std::thread 对象能引用它, 分离线程的确在后台运行, 所以分离线程不能被加入。

    一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

    注意:

    当在 std::thread 对象中分离线程时,不能对没有执行线程的 std::thread 对象使用detach()。因为和 join()的使用条件相同, 所以可以用同样的方式进行检查,当 std::thread 对象使用 t.detach() 之前,先使用t.joinable()检测该线程是否为可执行线程,如果返回的是true,就可以使用了。

    相关文章

      网友评论

        本文标题:{ 1 }CPP_线程管理的基础

        本文链接:https://www.haomeiwen.com/subject/ebnhqqtx.html