• c++11の多线程一----多线程应用


    一、c++11的多线程

    C++98标准中并没有线程库的存在,而在C++11中终于提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。

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

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

    join调用,则会阻塞主线程(调用者)

    1. 首先,构建一个std::thread对象t1,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完了,整个线程也就执行完了。
    2. 线程创建成功后,就会立即启动,并没有一个类似start的函数来显式的启动线程。
    3. 一旦线程开始运行, 就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。注意:只需要在std::thread对象被销毁之前做出这个决定。这个例子中,对象t1是栈上变量,在main函数执行结束后就会被销毁,所以需要在main函数结束之前做决定。
    4. 这个例子中选择了使用t1.join(),主线程会一直阻塞着,直到子线程完成,join()函数的另一个任务是回收该线程中使用的资源。

    线程对象和对象内部管理的线程的生命周期并不一样,如果线程执行的快,可能内部的线程已经结束了,但是线程对象还活着,也有可能线程对象已经被析构了,内部的线程还在运行。

    二、detch方法

    与之对应,我们可以调用t1.detach(),从而将t1线程放在后台运行,所有权和控制权被转交给C++运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。参考UNIX守护进程(daemon process)的概念,这种被分离的线程被称为守护线程(daemon threads)。线程
    被分离之后,即使该线程对象被析构了,线程还是能够在后台运行,只是由于对象被析构了,主线程不能够通过对象名与这个线程进行通信。例如:

    #include <iostream>
    #include <thread>
    
    void function_1() {
        //延时500ms 为了保证test()运行结束之后才打印
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); 
        std::cout << "from 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;
    }
    1. 由于线程入口函数内部有个500ms的延时,所以在还没有打印的时候,test()已经执行完成了,t1已经被析构了,但是它负责的那个线程还是能够运行,这就是detach()的作用。
    2. 如果去掉main函数中的1s延时,会发现什么都没有打印,因为主线程执行的太快,整个程序已经结束了,那个后台线程被C++运行时库回收了。
    3. 如果将t1.detach()换成t1.join()test函数会在t1线程执行结束之后,才会执行结束。

    三、join和detch的使用

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

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

    四、线程类thread的构造方法

    std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数

    第一参数的类型并不是c语言中的函数指针(c语言传递函数都是使用函数指针),在c++11中,增加了可调用对象(Callable Objects)的概念,总的来说,可调用对象可以是以下几种情况:

    • 函数指针
    • 重载了operator()运算符的类对象,即仿函数
    • lambda表达式(匿名函数)
    • std::function

    对于线程t1来说,内部调用的线程函数其实是一个副本,所以如果在函数内部修改了类成员,并不会影响到外面的对象。只有传递引用的时候才会修改。所以在这个时候就必须想清楚,到底是传值还是传引用!

    函数指针

    typedef void (* Pfunction)();
    
    Pfunction pf;
    void Myprint() {
        cout << "hello world!" << endl;
    }
    
    int main() {
        pf = Myprint;
        thread t(pf);
        t.join();
    
        std::getchar();
        return 0;
    }

    仿函数

    class Fctor {
    public:
        void operator() () {
            cout << "i'm factor" << endl;
        }
    };
    
    int main() {
    
        Fctor f;
        thread t(f); //thread t(Factor())会编译错误,因为没有实例化对象,虽然写成了函数形式本质还是一个对象
        t.join();
        std::getchar();
        return 0;
    }

    lambda表达式

    int main() {
    
        thread t([]() {
            cout << "hello world!" << endl;
        });
        t.join();
    
        std::getchar();
        return 0;
    }

    function和函数指针类似

    function<void (void)> fun;
    
    void Myprint() {
        cout << "hello world!" << endl;
    }
    
    int main() {
        fun = Myprint;
        thread t(fun);
        t.join();
    
        std::getchar();
        return 0;
    }

    五、构造的时候可以参数绑定

     std::thread t(&ThreadTest::myThread,this,param1,param2);

    六、线程对象只能移动不可复制

    线程对象之间是不能复制的,只能移动,移动的意思是,将线程的所有权在std::thread实例间进行转移。

    void some_function();
    void some_other_function();
    std::thread t1(some_function);
    // std::thread t2 = t1; // 编译错误
    std::thread t2 = std::move(t1); //只能移动 t1内部已经没有线程了
    t1 = std::thread(some_other_function); // 临时对象赋值 默认就是移动操作
    std::thread t3;
    t3 = std::move(t2); // t2内部已经没有线程了
    t1 = std::move(t3); // 程序将会终止,因为t1内部已经有一个线程在管理了

     多线程一----多线程的应用

     多线程二----简单线程管理

    多线程三----数据竞争和互斥对象

    多线程四----死锁和防止死锁

    多线程五----unick_lock和once_flag

    多线程六----条件变量

    多线程七----线程间通信

  • 相关阅读:
    什么是https?
    Gojs
    GoJs 01讲解
    你真的了解WebSocket吗?
    django channels
    序列化及反序列化
    全角转半角
    Thread Culture
    设置输入法
    token的认证使用
  • 原文地址:https://www.cnblogs.com/xietianjiao/p/6374036.html
Copyright © 2020-2023  润新知