• C++ 多线程thread


    0、多线程接口介绍

    1. 不同平台的多线程API, 比如unix下有pthread,,win下有_beginthreadex()。
    2. std::thread是属于C++自带的多线程接口,还有boost::blind也是C++自带的多线程接口

    boost是C++的测试版本,许多C++的新特性会在这里测试,待时机成熟后悔合并到C++中。boost的C++特性也是稳定的,但不是C++自带,需要编译成库引用。

    1、 std::thread基本介绍

    1.1两种状态

    一个std::thread对象只可能处于可联结或不可联结两种状态之一:

    • 可联结:当线程己运行或可运行、或处于阻塞时是可联结的。注意,如果某个底层线程(比如主线程)已经执行完任务,但是没有被join的话,仍然处于joinable状态,这时如果退出主线程,因为该子线程还未join仍处于可联结状态,会出现段错误。 即std::thread对象(对象由父线程所有)与底层线程保持着关联时,为joinable状态。
    • 不可联结:
      1. 当不带参构造的std::thread对象为不可联结,因为底层线程还没创建。
      2. 己移动的std::thread对象为不可联结。
      3. 己调用join或detach的对象为不可联结状态。因为调用join()以后,底层线程己结束,而detach()会把std::thread对象和对应的底层线程之间的连接断开。

    综上:std::thread对象析构时,会先判断是否可joinable(),如果可联结,则程序会直接被终止出错。这意味着创建thread对象以后,必须要在随后的某个地方调用join或detach以便让std::thread处于不可联结状态。

    1.2 join和detach

    join():等待子线程执行,调用线程(如主线程)处于阻塞。join()执行完成之后,底层线程id被设置为0,即joinable()变为false。

    detach():分离子线程,与当前线程的连接被断开,joinable()变为false,子线程成为后台线程,被C++运行时库接管。此时子线程与主线程并行运行。不过这样由于没有thread对象指向该线程而失去了对它的控制,当对象析构时线程会继续在后台执行,但是当主程序退出时并不能保证线程能执行完(退出thread_func即是执行完子线程)。如果没有良好的控制机制或者这种后台线程比较重要,最好不用detach而应该使用join。

    2、C++ demo

    2.1 CMakeLists.txt

    # 声明要求的 cmake 最低版本
    cmake_minimum_required(VERSION 3.2)
    
    # 声明一个 cmake 工程
    project(thread_demo)
    
    add_definitions(-std=c++11)
    
    # 添加一个可执行程序
    # 语法: add_executable( 程序名 源代码文件 )
    
    add_executable(thread_demo src/main.cpp)
    
    include_directories(
        ${PROJECT_SOURCE_DIR}
        ${PROJECT_SOURCE_DIR}/include
    )
    
    target_link_libraries(thread_demo pthread)#thread库
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.2 main.cpp

    #include
    #include
    
    using namespace std;
    
    bool close_thread = false;//设置一个标志位,用于退出子线程
    
    void subThread() {
        while(1){
            if (close_thread)
                break;//跳出函数,或者执行完函数,就认为子线程接收
            std::cout << "sub thread" << std::endl;
        }
    }
    
    
    int main(int argc, char* argv[]) {
        thread sub_thread(subThread);   //实例化一个线程对象sub_thread,该线程开始执行
        
        std::cout << "main thread" << std::endl;
        
        close_thread = true;//告诉要退出程序了,子线程也要退出
        sub_thread.join();  //使不可联结
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3、线程安全

    头文件是#include,该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。

    3.1 mutex

    mutex是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

    #include
    #include
    #include
    
    using namespace std;
    
    mutex m;
    int cnt = 10;
    
    void thread1() {
        while (cnt > 5){
            m.lock();
            if (cnt > 0) {
                --cnt;
                cout << cnt << endl;
            }
            m.unlock();
        }
    }
    
    void thread2() {
        while (cnt > 0) {
            m.lock();
            if (cnt > 0) {
                cnt -= 10;
                cout << cnt << endl;
            }
            m.unlock();
        }
    }
    
    int main(int argc, char* argv[]) {
        thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
        thread th2(thread2);
        th1.join();
        th2.join();
        cout << "main..." << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    但是:mutex是不安全的,当一个线程在解锁之前异常退出了,那么其它被阻塞的线程就无法继续下去,因为mutex还是保持锁着的状态。

    3.2 std::lock_guard

    使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。

    #include
    #include
    #include
    
    using namespace std;
    
    mutex m;
    int cnt = 10;
    
    void thread1() {
        while (cnt > 5){
            lock_guard lockGuard(m);
            if (cnt > 0) {
                --cnt;
                cout << cnt << endl;
            }
        }
    }
    
    void thread2() {
        while (cnt > 0) {
            lock_guard lockGuard(m);
            if (cnt > 0) {
                cnt -= 10;
                cout << cnt << endl;
            }
        }
    }
    
    int main(int argc, char* argv[]) {
        thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
        thread th2(thread2);
        th1.join();
        th2.join();
        cout << "main..." << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    Redis7.2.3集群安装,新增节点,删除节点,分配哈希槽,常见问题
    Linux 本地部署私有Stackedit Markdown编辑器远程访问
    SpringBoot+MinIO(三)
    初始化antdv项目,按需引入
    JavaScript 开发的历程
    C++ | Leetcode C++题解之第35题搜索插入位置
    Excel冻结窗格
    第一行代码Android 第十章 10.1-10.2(服务,线程,子线程中更新UI,异步消息处理机制,AsyncTask异步消息处理工具)
    lua 判空的坑
    JIRA8.15.X升级JIRA8.20.X流程概述(适用于其他版本)
  • 原文地址:https://blog.csdn.net/Kevin_Xie86/article/details/126333973