C++多线程学习(一):C++11 多线程快速入门

参考引用

  • C++11 14 17 20 多线程从原理到线程池实战
  • 代码运行环境:Visual Studio 2019

1. 为什么要用多线程

  • 任务分解
    • 耗时的操作,任务分解,实时响应
  • 数据分解
    • 充分利用多核CPU处理数据
  • 数据流分解
    • 读写分离,解耦合设计

2. 第一个子线程代码示例

  • first_thread.cpp
    #include 
    #include 
    
    using namespace std;
    
    // 创建的子线程的入口函数
    void ThreadMain() {
        // 获取子线程开始时刻的 ID 号
        cout << "begin sub thread ID " << this_thread::get_id() << endl;
        for (int i = 0; i < 5; i++) {
            cout << "in thread " << i << endl;
            // 子线程睡眠(释放)CPU 资源 1000ms
            this_thread::sleep_for(chrono::seconds(1));  
        }
        cout << "end sub thread ID " << this_thread::get_id() << endl;
    }
    
    // 主线程的入口函数 main()
    int main(int argc, char* argv[]) {
        cout << "main thread ID " << this_thread::get_id() << endl;
    
        // 线程创建启动
        thread th(ThreadMain);
        cout << "begin wait sub thread" << endl;
    
        // 阻塞等待子线程退出
        th.join();
        cout << "end wait sub thread" << endl;
    
        return 0;
    }
    
  • 控制台输出
    main thread ID 21580
    begin wait sub thread
    begin sub thread ID 22924
    in thread 0
    in thread 1
    in thread 2
    in thread 3
    in thread 4
    end sub thread ID 22924
    end wait sub thread
    

3. std::thread 对象生命周期、线程等待和分离

  • thread_detach.cpp

    #include 
    #include 
    
    using namespace std;
    
    bool is_exit = false;
    
    void ThreadMain() {
        cout << "begin sub thread ID " << this_thread::get_id() << endl;
        for (int i = 0; i < 5; i++) {
            if (!is_exit)
                break;
            cout << "in thread " << i << endl;
            this_thread::sleep_for(chrono::seconds(1));  // 子线程睡眠(释放)CPU 资源 1000ms
        }
        cout << "end sub thread ID " << this_thread::get_id() << endl;
    }
    
    int main(int argc, char* argv[]) {
        {
            //thread th(ThreadMain); // 出错,thread 对象被销毁 子线程还在运行
        }
    
        {
            thread th(ThreadMain);
            th.detach();  // 子线程与主线程分离 守护线程(在后台运行)
            // 但存在一个问题:主线程退出后 子线程不一定退出
        }
    
        {
            thread th(ThreadMain);
            this_thread::sleep_for(chrono::seconds(1));//1000ms
            is_exit = true;  // 通知子线程退出
            cout << "主线程阻塞,等待子线程退出" << endl;
            th.join();       // 主线程阻塞,等待子线程退出
            cout << "子线程已经退出!" << endl;
        }
    
        getchar();
    
        return 0;
    }
    
  • 控制台输出

    begin sub thread ID 25520
    end sub thread ID 25520
    begin sub thread ID 23324
    end sub thread ID 23324
    主线程阻塞,等待子线程退出
    子线程已经退出!
    

4. 全局函数作为线程入口

  • thread_para.cpp

    #include 
    #include 
    #include 
    
    using namespace std;
    
    class Para {
    public:
        Para() {
            cout << "Create Para" << endl;
        }
        Para(const Para& p) {  // 拷贝构造函数
            cout << "Copy Para" << endl; 
        }
        ~Para() {
            cout << "Drop Para" << endl;
        }
    
        string name;
    };
    
    void ThreadMain(int p1, float p2, string str, Para p4) {
        this_thread::sleep_for(100ms);
        cout << "ThreadMain " << p1 << " " << p2 << " " << str << " " << p4.name << endl;
    }
    
    int main(int argc, char* argv[]) {
        thread th;
        {
            float f1 = 12.1f;
            Para p;
            p.name = "test Para class";
            // 所有的参数做复制
            th = thread(ThreadMain, 101, f1, "test string para", p);
        }
        th.join();
    
        return 0;
    }
    
  • 控制台输出

    Create Para
    Copy Para
    Drop Para
    Copy Para
    ThreadMain 101 12.1 test string para
    Drop Para
    Drop Para
    

5. 线程函数传递指针和引用

  • 参数传递存在的问题

    • 传递空间已经销毁
    • 多线程共享访问一块空间
    • 传递的指针变量的生命周期小于线程
  • thread_para.cpp

#include 
#include 
#include 

using namespace std;

class Para {
public:
    Para() { cout << "Create Para" << endl; }
    Para(const Para& p) { cout << "Copy Para" << endl; }
    ~Para() { cout << "Drop Para" << endl; }
    string name;
};

void ThreadMain(int p1, float p2, string str, Para p4) {
    this_thread::sleep_for(100ms);
    cout << "ThreadMain " << p1 << " " << p2 << " " << str <<" "<<p4.name<< endl;
}

void ThreadMainPtr(Para* p) {
    this_thread::sleep_for(100ms);
    cout << "ThreadMainPtr name = " << p->name << endl;
}

void ThreadMainRef(Para& p) {
    this_thread::sleep_for(100ms);
    cout << "ThreadMainPtr name = " << p.name << endl;
}
int main(int argc, char* argv[]) {
    {
        // 传递引用
        Para p;
        p.name = "test ref";
        thread th(ThreadMainRef, ref(p));
        th.join();
    }
    getchar();

    {
        // 传递线程指针
        Para p;
        p.name = "test ThreadMainPtr name";
        thread th(ThreadMainPtr, &p);  
        th.detach();  // 错误,线程访问的 p 空间会提前释放
    }
    getchar();  // Para 释放

    {  
        // 传递线程指针
        Para p;
        p.name = "test ThreadMainPtr name";
        thread th(ThreadMainPtr, &p);
        th.join();
        getchar();
    }

    thread th;
    {
        float f1 = 12.1f;
        Para p;
        p.name = "test Para class";
        // 所有的参数做复制
        th =  thread(ThreadMain, 101, f1, "test string para", p);
    }
    th.join();

    return 0;
}
  • 控制台输出
    Create Para
    ThreadMainPtr name = test ref
    Drop Para
    

6. 成员函数作为线程入口–封装线程基类接口

  • XThread.h

    • 封装线程基类接口
    #pragma once
    
    #include 
    #include 
    
    class XThread {
    public:
        virtual void Start() {
            is_exit_ = false;
            th_ = std::thread(&XThread::Main, this);
        }
        virtual void Stop() {
            is_exit_ = true;
            Wait();
        }
        virtual void Wait() {
            if (th_.joinable())
                th_.join();
        }
        bool is_exit() {
            return is_exit_;
        }
    
    private:
        virtual void Main() = 0;  // 纯虚函数必须要在基类中实现
        std::thread th_;
        bool is_exit_ = false;    // 符合谷歌代码风格,私有成员变量后缀加 _
    };
    
  • thread_class.cpp

    #include 
    #include 
    #include 
    #include "XThread.h"
    
    using namespace std;
    
    /*
    class MyThread {
    public:
        // 入口线程函数
        void Main() {
            cout << "MyThread Main" << name << ": " << age;
        }
    
        string name;
        int age = 0;
    };
    */
    
    class TestXThread : public XThread {
    public:
        void Main() override {
            cout << "TestXThread Main begin" << endl;
            while (!is_exit()) {
                this_thread::sleep_for(100ms);
                cout << "." << flush;  // 添加 flush 是为了确保 . 正常输出
            }
            cout << "\nTestXThread Main end" << endl;
        }
        string name;
    };
    
    int main(int argc, char* argv[]) {
        TestXThread testth;
        testth.name = "TestXThread name ";
        testth.Start();
        this_thread::sleep_for(3s);
        testth.Stop();
    
        testth.Wait();
        getchar();
    
        /*
        MyThread myth;
        myth.name = "Test name";
        myth.age = 20;
    
        thread th(&MyThread::Main, &myth);
        th.join();
        */
    
        return 0;
    }
    
  • 控制台输出

    TestXThread Main begin
    ............................
    TestXThread Main end
    

7. lambda 临时函数作为线程入口

  • lambda 函数基本格式

    • [捕捉列表] (参数) mutable -> 返回值类型 {函数体}
  • thread_lambda.cpp

    #include 
    #include 
    #include 
    
    using namespace std;
    
    class TestLambda {
    public:
        void Start() {
            thread th([this]() {cout << "name = " << name << endl; });
            th.join();
        }
        
        string name = "Test Lambda";
    };
    
    int main(int argc, char* argv[]) {
        thread th(
            [](int i) {cout << "test lambda " << i << endl;}, 
            123
        );
        th.join();
        
        TestLambda test;
        test.Start();
        
        return 0;
    }
    
  • 控制台输出

    test lambda 123
    name = Test Lambda
    

8. call_once 多线程调用函数只进入一次

初始化函数可以在每一个类型的构造里都调用一遍,不用明确区分,代码可读性提升

  • call_once.cpp

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    void SystemInit() {
        cout << "Call SystemInit" << endl;
    }
    
    void CallOnceSystemInit() {
        static std::once_flag flag;  // 通过 flag 区分是否只调用一次
        std::call_once(flag, SystemInit);
    }
    
    int main(int argc, char* argv[]) {
        CallOnceSystemInit();
        CallOnceSystemInit();
        for (int i = 0; i < 3; i++) {
            thread th(CallOnceSystemInit);
            th.detach();
        }
        
        getchar();
        
        return 0;
    }
    
  • 控制台输出

    Call SystemInit
    

你可能感兴趣的:(C++多线程学习,c++,学习,开发语言,多线程,call_once,线程分离)