C++11——线程库

1. 简介

thread库是C++11新增的<thread>头文件提供的跨平台线程管理工具,它的优势在于,我们不需要再关注平台差异,thread库屏蔽了底层的实现差异,可以在不同的系统下运行。

2. 基础准备

  1. 必须包含头文件:#include<thread>。

  2. 可调用对象:std::thread 构造函数支持传入以下可执行逻辑

lambda表达式(最常用)

普通函数/函数指针

类成员函数

重载operator()的仿函数对象

线程对象构造完成的瞬间,线程就会立即启动(不是等join()才启动)。

下面我们根据上面列出的依次进行举例说明。

3. 核心用法

3.1 基于 lambda 表达式创建线程(最常用)

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

int main() {
    // lambda直接作为线程执行体,无需定义额外函数
    thread t([]{
        for (int i = 0; i < 3; i++) {
            cout << "子线程执行中,i=" << i << endl;
        }
    });
    
    t.join(); // 主线程等子线程结束
    cout << "主线程继续执行" << endl;
    return 0;
}

运行结果:

子线程执行中,i=0
子线程执行中,i=1
子线程执行中,i=2
子线程执行中,i=3
子线程执行中,i=4
子线程执行中,i=5
主线程继续执行

3.2 基于普通函数创建线程

cpp 复制代码
#include <iostream>
#include <thread>  // 必须包含的头文件
using namespace std;

// 线程要执行的普通函数
void print_num(int num, const string& msg) {
    cout << "子线程执行:" << msg << ",数字:" << num << endl;
}

int main() {
    // 构造thread对象:参数=函数名 + 函数的实参
    // 构造完成的瞬间,子线程立即启动
    thread t(print_num, 10, "hello thread");
    
    // 主线程等待子线程执行完毕(必须!否则子线程可能被强制终止)
    t.join();
    
    return 0;
}

运行结果:

子线程执行:hello thread,数字:10

3.3 基于类成员函数创建线程

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;

class MyClass {
public:
    // 类的成员函数(线程要执行的逻辑)
    void show_msg(const string& msg) {
        cout << "成员函数线程:" << msg << endl;
    }
};

int main() {
    MyClass obj; // 创建类对象
    // 构造参数:成员函数地址 + 对象指针 + 函数实参
    thread t(&MyClass::show_msg, &obj, "类成员函数测试");
    
    t.join();
    return 0;
}

运行结果:

成员函数线程:类成员函数测试

3.4 基于重载operator()的仿函数对象创建线程

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

// 定义"函数对象类":重载operator()
class MyFuncObj {
public:
    // 构造函数:初始化成员变量(函数对象可以携带状态,这是普通函数没有的优势)
    MyFuncObj(int init_val) : count(init_val) {}

    // 重载operator():线程要执行的逻辑写在这里
    void operator()(const string& msg) {
        for (int i = 0; i < 3; i++) {
            // 可以访问类的成员变量(函数对象的"状态")
            cout << "函数对象执行:" << msg << ",当前计数:" << ++count << endl;
        }
    }

private:
    int count; // 函数对象的成员变量(可以携带状态)
};

int main() {
    // 1. 创建仿函数对象(同时初始化成员变量count=0)
    MyFuncObj func_obj(0);

    // 2. 将仿函数对象传给std::thread的构造函数
    // 线程会执行func_obj的operator(),并传入实参"test"
    thread t(func_obj, "hello");

    // 3. 等待线程执行完毕
    t.join();

    return 0;
}

这里我们实现了一个类,它重载了operator(),其实这里本质就是仿函数,我们利用类实例化出一个仿函数对象,再讲这个仿函数对象传给thread,这时线程就会调用operator()函数执行线程的相关任务。(线程会自动调用func_obj.operator(实参)(即示例中的func_obj("hello"))。)

3.5 等待线程结束(join)

thread库中为我们提供了join()函数,它的功能就是等待对应线程结束,线程创建好后必须有等待,如果不调用join函数,那么主线程就会提前结束,主线程一旦终止,子线程也会立即终止,这就可能导致子线程的逻辑还没执行完毕就被强制退出了,这是不合理的。

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono> // 用于休眠
using namespace std;

int main() {
    thread t([]{
        // 子线程休眠2秒(模拟耗时操作)
        this_thread::sleep_for(chrono::seconds(2));
        cout << "子线程执行完毕" << endl;
    });
    
    // 注释掉join,主线程直接结束,子线程被强制终止
    // t.join(); 
    cout << "主线程提前结束" << endl;
    return 0;
}

3.6 分离线程(detach)

detach() 作用是:将子线程与主线程 "解绑",子线程进入 "后台运行" 状态:

  • 主线程无需等待子线程结束,可继续执行;
  • 子线程的资源由操作系统接管,执行完后自动释放;
  • 解绑后,主线程无法再通过join()等待该子线程。
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

int main() {
    thread t([]{
        this_thread::sleep_for(chrono::seconds(2));
        cout << "detach子线程执行完毕" << endl;
    });
    
    t.detach(); // 解绑线程,后台运行
    cout << "主线程无需等待,直接执行" << endl;
    
    // 主线程休眠3秒(否则主线程太快结束,进程销毁,detach线程也会终止)
    this_thread::sleep_for(chrono::seconds(3));
    return 0;
}

运行结果:

主线程无需等待,直接执行
detach子线程执行完毕

3.7 判断线程状态(joinable)

joinable() 返回bool值,用于判断线程是否 "可被join()/detach()":

  • 返回true:线程未调用过join()/detach(),且未执行完毕;
  • 返回false:线程已调用join()/detach(),或线程对象是空的。

功能:避免重复调用join()/detach()(会直接导致程序崩溃)。

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

void func() {
    cout << "子线程执行" << endl;
}

int main() {
    thread t(func);
    
    // 先判断,再操作(安全写法)
    if (t.joinable()) {
        t.join();
        cout << "第一次join成功" << endl;
    }
    
    // 再次判断,此时返回false(已join)
    if (!t.joinable()) {
        cout << "线程已不可join" << endl;
    }
    
    // 错误:重复join会崩溃,所以必须用joinable判断
    // t.join(); 
    return 0;
}

运行结果:

子线程执行
第一次join成功
线程已不可join

3.8 线程的移动(不支持拷贝)

std::thread 对象不能拷贝(一个线程资源不能被多个thread对象管理),但可以移动(转移线程的所有权)。

3.8.1 直接移动线程对象

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

void func() {
    cout << "被移动的线程执行" << endl;
}

int main() {
    thread t1(func); // 创建线程t1
    // 错误:不能拷贝(编译器直接报错)
    // thread t2 = t1; 
    
    // 正确:移动,t1的所有权转移给t2,t1变为"不可joinable"
    thread t2 = move(t1);
    
    t2.join(); // 只能操作t2,t1已无效
    return 0;
}

运行结果:

被移动的线程执行

这里大家需要注意一下,thread是不允许拷贝的,所以这里我们使用move进行转移,使用move后,它就变成了右值,于是这里就需要调用移动构造来构造t2对象。

3.8.2 容器存储线程

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
using namespace std;

void func(int num) {
    cout << "线程" << num << "执行" << endl;
}

int main() {
    vector<thread> pool; // 存储线程的容器
    
    // 方式1:emplace_back(推荐,原地构造线程,无临时对象)
    for (int i = 0; i < 2; i++) {
        pool.emplace_back(func, i);
    }
    
    // 方式2:push_back + move(先构造临时线程,再移动)
    thread t(func, 2);
    pool.push_back(move(t));
    
    // 等待所有线程结束
    for (auto& th : pool) {
        if (th.joinable()) {
            th.join();
        }
    }
    return 0;
}

3.9 获取线程id

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

int main() {
    // 获取主线程ID
    cout << "主线程ID:" << this_thread::get_id() << endl;
    
    thread t([]{
        // 获取子线程ID
        cout << "子线程ID:" << this_thread::get_id() << endl;
    });
    
    // 获取线程对象的ID
    cout << "线程t的ID:" << t.get_id() << endl;
    
    t.join();
    return 0;
}

运行结果:

主线程ID:11860
线程t的ID:14500
子线程ID:14500

4. 避坑指南

4.1 线程对象析构前未调用 join ()/detach ()(最致命)

线程对象销毁时(比如出作用域),若既没join()也没detach(),程序会调用std::terminate()直接崩溃。

这里我们可以通过join函数,也可以通过detach函数,建议使用join函数进行等待。

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

void test() {
    thread t([]{ cout << "子线程执行" << endl; });
    // 未join/detach,t析构时程序崩溃
}

int main() {
    test();
    return 0;
}

4.2 detach () 线程访问主线程局部变量

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
using namespace std;

void test() {
    string msg = "主线程局部变量"; // 局部变量,test()结束后销毁
    thread t([&]{
        // detach后,test()先结束,msg已销毁,访问会崩溃
        this_thread::sleep_for(chrono::seconds(1));
        cout << msg << endl; 
    });
    t.detach();
}

int main() {
    test();
    this_thread::sleep_for(chrono::seconds(2));
    return 0;
}

detach()后的线程后台运行,若主线程先结束,其局部变量会被销毁,子线程再访问会导致 "野指针 / 非法内存访问",程序崩溃。

解决方案:让detach()线程访问全局变量,或拷贝变量到线程内部(不用引用)

cpp 复制代码
void test() {
    string msg = "主线程局部变量";
    thread t([msg]{ // 拷贝msg到线程内部,不再依赖主线程
        this_thread::sleep_for(chrono::seconds(1));
        cout << msg << endl; 
    });
    t.detach();
}

这里补充一点:

线程并不是越多,效率就越高,线程数超过 CPU 核心数时,操作系统会频繁切换线程上下文(保存 / 恢复线程状态),反而增加开销,导致程序变慢。

所以我们在创建线程的时候一定要适量创建,不要创建太多线程。

5. this_thread

std::this_thread是 C++11 <thread>头文件中提供的命名空间(不是类);

核心作用是:操作 "当前正在执行代码的那个线程"(即调用这些函数的线程本身)。

下面介绍一下常用函数:

5.1 get_id

返回当前线程 的唯一标识(类型是std::thread::id),用于区分不同线程(比如调试时打印线程 ID)。

cpp 复制代码
#include <iostream>
#include <thread>
using namespace std;

void func() {
    // 获取当前线程(子线程)的ID
    cout << "子线程ID:" << this_thread::get_id() << endl;
}

int main() {
    // 获取当前线程(主线程)的ID
    cout << "主线程ID:" << this_thread::get_id() << endl;
    
    thread t(func);
    t.join();
    return 0;
}

5.2 sleep_for

让当前线程暂停执行指定的时间长度(比如休眠 2 秒),常用于模拟耗时操作、控制线程执行节奏。

cpp 复制代码
template <class Rep, class Period>
void sleep_for(const chrono::duration<Rep, Period>& rel_time);
  • 参数是std::chrono库的 "时间段"(比如chrono::seconds(2)表示 2 秒,chrono::milliseconds(500)表示 500 毫秒);
  • 需要包含头文件<chrono>
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono> // 必须包含,用于表示时间
using namespace std;

int main() {
    cout << "主线程开始休眠..." << endl;
    // 让当前线程(主线程)休眠2秒
    this_thread::sleep_for(chrono::seconds(2));
    cout << "主线程休眠结束!" << endl;
    return 0;
}
相关推荐
渡我白衣2 分钟前
计算机组成原理(13):多路选择器与三态门
开发语言·javascript·ecmascript·数字电路·计算机组成原理·三态门·多路选择器
HUST4 分钟前
C语言 第十讲:操作符详解
c语言·开发语言
行稳方能走远5 分钟前
Android C++ 学习笔记2
c++
星火开发设计5 分钟前
链表详解及C++实现
数据结构·c++·学习·链表·指针·知识
修炼地6 分钟前
代码随想录算法训练营第五十三天 | 卡码网97. 小明逛公园(Floyd 算法)、卡码网127. 骑士的攻击(A * 算法)、最短路算法总结、图论总结
c++·算法·图论
QQ_4376643147 分钟前
Qt-框架
c++·qt
田里的水稻8 分钟前
matlab_绘图线条颜色显示和点的形状显示
开发语言·matlab
CCPC不拿奖不改名11 分钟前
python基础:python语言的数据结构+面试习题
开发语言·数据结构·python·面试
Tan385112 分钟前
陪读蛙 Read Frog 配置 API 教程|低成本实现高质量翻译
开发语言·机器翻译·自动翻译·api key·tensdaq·陪读蛙·read frog
凑凑的小手办14 分钟前
C语言基础(一)
c语言·开发语言