zoukankan      html  css  js  c++  java
  • C++多线程编程(thread类)

    多线程库

      C++11中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。

      多线程库对应的头文件是#include <thread>,类名为std::thread

    串行程序:

    #include <iostream>
    #include <thread>
    
    void function_1() 
    {
        std::cout << "I'm function_1()" << std::endl;
    }
    
    int main() 
    {
        function_1();
        return 0;
    }

      这是一个典型的单线程的单进程程序,任何程序都是一个进程,main()函数就是其中的主线程,单个线程都是顺序执行。

      将上面的程序改造成多线程程序,让function_1()函数在另外的线程中执行:

    #include <iostream>
    #include <thread>
    
    void function_1() 
    {
        std::cout << "I'm function_1()" << std::endl;
    }
    
    int main() 
    {
        std::thread t1(function_1);
        // do other things
        t1.join();
        return 0;
    }

      1. 构建一个std::thread类的对象t1,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完整个线程也就执行完了;

      2. 线程创建成功后,就会立即启动;

      3. 一旦线程开始运行, 就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。需要在std::thread对象被销毁之前做出决定;

      本例选择了使用t1.join(),主线程(main函数)会一直阻塞,直到子线程(function_1函数)完成,join()函数的另一个任务是回收该线程中使用的资源

      我们也可以调用t1.detach(),从而将子线程放在后台运行,所有权和控制权被转交给C++运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。被分离的线程被称为守护线程(daemon threads)。线程被分离之后,即使该线程对象被析构了,线程还是能够在后台运行,只是由于对象被析构了,主线程不能够通过对象名与这个线程进行通信。线程对象(t1)和对象内部管理的线程(子线程)的生命周期并不一样;如果线程执行的快,可能内部的线程已经结束了,但是线程对象还活着;也有可能线程对象已经被析构了,内部的线程还在运行。

    #include <iostream>
    #include <thread>
    
    void function_1() 
    {
        //延时500ms 为了保证test()运行结束之后才打印
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); 
        std::cout << "I'm function_1()" << std::endl;
    }
    
    void test() 
    {
        std::thread t1(function_1);
        t1.detach();
        // t1.join();
        std::cout << "test() finished" << std::endl;
    }
    
    int main() 
    {
        test();
        //让主线程晚于子线程结束
        std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
        return 0;
    }
    
    // 使用 t1.detach()时
    // test() finished
    // I'm function_1()
    
    // 使用 t1.join()时
    // I'm function_1()
    // test() finished

      1. 由于线程入口函数内部有个500ms的延时,所以在还没有打印的时候,test()已经执行完成了,t1已经被析构了,但是它负责的那个线程还是能够运行,这就是detach()的作用;

      2. 如果去掉main函数中的1s延时,会发现只打印了test() finished,因为主线程执行的太快,整个程序已经结束了,后台线程被C++运行时库回收了;

      3. 如果将t1.detach()换成t1.join()test函数会在t1线程执行结束之后,才会执行结束。

      一旦一个线程被分离了,就不能够再被join了。如果非要调用,程序就会崩溃,可以使用joinable()函数判断一个线程对象能否调用join()

    void test() 
    { std::thread t1(function_1); t1.detach();
    if(t1.joinable()) t1.join(); assert(!t1.joinable()); }

    线程类的构造函数

      std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数;第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数第一个参数是一个可调用对象(Callable Objects),可以是以下几种情况:

    • 函数指针
    • 重载了operator()运算符的类对象,即仿函数
    • lambda表达式(匿名函数)
    • std::function
    函数指针:
    // 普通函数 无参
    void function_1() 
    {}
    
    // 普通函数 1个参数
    void function_2(int i) 
    {}
    
    // 普通函数 2个参数
    void function_3(int i, std::string m) 
    {}
    
    std::thread t1(function_1);
    std::thread t2(function_2, 1);
    std::thread t3(function_3, 1, "hello");
    
    t1.join();
    t2.join();
    t3.join();
     
    仿函数:
    // 仿函数
    class Fctor 
    {
    public:
        void operator() () 
        {}
    };
    
    Fctor f;
    std::thread t4{Fctor()}; //注意加花括号
      一个仿函数类生成的对象,使用起来就像一个函数一样,比如上面的对象f,当使用f()时就调用operator()运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。
     
    lambda表达式:
    //[]捕获列表,用于捕获上下文变量供lambda使用
    //同时,编译器根据该符号可以判断接下来是lambda函数
    //()参数列表,无参数的话可以省略
    //返回值类型
    //{}函数体
    std::thread t1( [](){std::cout << "hello" << std::endl;} );
    
    //“world”为参数m的值
    std::thread t2([](std::string m)
    {std::cout << "hello " << m << std::endl;}, "world");
    std::function:
    class A
    {
    public:
        void func1()
        {}
    
        void func2(int i)
        {}
    
        void func3(int i, int j)
        {}
    };
    
    A a;
    //std::function<返回值类型(参数类型)>
    //std::bind(函数对象,参数)
    std::function<void(void)> f1 = std::bind(&A::func1, &a);
    std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
    std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
    std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
    std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);
    
    std::thread t1(f1);
    std::thread t2(f2);
    std::thread t3(f3, 1);
    std::thread t4(f4, 1);
    std::thread t5(f5, 1, 2);
     
  • 相关阅读:
    JAVA并发之ReentrantLock源码(一)
    java并发之线程池
    Quine--输出程序源码的程序(java)
    【leetcode】Weekly Contest 92
    【java集合类】ArrayList和LinkedList源码分析(jdk1.8)
    【leetcode】Weekly Contest 91
    牛客2018.6模拟考编程题
    MFC 完全自定义控件
    图形学中求平面方程系数以及法向量
    std::function解决函数重载绑定
  • 原文地址:https://www.cnblogs.com/yongjin-hou/p/14527220.html
Copyright © 2011-2022 走看看