C++ 多线程

C++ 多线程


一、基本概念

1. 线程与多任务

  • 线程:进程内的轻量级执行单元,是程序最小执行流。
  • 进程:资源分配的最小单位,每个进程独立内存空间。
  • 多任务分类
    • 基于进程:多个独立程序并发运行。
    • 基于线程:同一个程序内部多个执行流并发运行。

2. 线程资源特性

  • 同进程内共享:全局变量、堆内存、文件句柄、地址空间。
  • 每个线程独有:栈空间、寄存器、程序计数器、局部变量。

3. 并发与并行

  • 并发:单核CPU时间片轮转,交替执行多个线程,宏观同时、微观串行。
  • 并行:多核CPU同一时刻多个线程物理上同时执行。

二、C++11 多线程标准库头文件

C++11 引入跨平台标准线程库,无需依赖系统API(Windows CreateThread / Linux pthread):

头文件 作用
<thread> std::thread 线程创建、管理
<mutex> 互斥量、锁管理
<condition_variable> 条件变量,线程等待与唤醒
<future> std::promise / std::future 线程结果传递
<atomic> 原子类型、无锁并发

编译命令(Linux):

bash 复制代码
g++ -std=c++11 main.cpp -o main -lpthread

三、四种创建线程方式

1. 普通全局函数 / 普通函数

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

void taskFunc(int num)
{
    for (int i = 0; i < num; ++i)
    {
        cout << "子线程执行:" << i << endl;
    }
}

int main()
{
    // 创建并启动线程
    thread t1(taskFunc, 3);
    // 等待线程结束
    t1.join();
    return 0;
}

2. 仿函数(重载()运算符的类)

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

class Task
{
public:
    void operator()(int cnt)
    {
        for (int i = 0; i < cnt; ++i)
        {
            cout << "仿函数线程执行:" << i << endl;
        }
    }
};

int main()
{
    thread t2(Task(), 3);
    t2.join();
    return 0;
}

3. Lambda 表达式创建线程

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

int main()
{
    thread t3([](int cnt){
        for (int i = 0; i < cnt; ++i)
        {
            cout << "Lambda线程执行:" << i << endl;
        }
    }, 3);

    t3.join();
    return 0;
}

4. 类成员函数创建线程

注意:必须传对象地址 + 成员函数地址

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

class Work
{
public:
    void run(int n)
    {
        for (int i = 0; i < n; ++i)
        {
            cout << "成员函数线程:" << i << endl;
        }
    }
};

int main()
{
    Work w;
    // 格式:线程对象(类成员函数地址, 对象地址, 参数)
    thread t4(&Work::run, &w, 3);
    t4.join();
    return 0;
}

四、线程核心操作:join / detach

1. join()

  • 主线程阻塞等待子线程执行完毕。
  • 线程结束后自动回收资源。
  • 同一个线程只能 join 一次。
cpp 复制代码
thread t(taskFunc);
t.join();

2. detach() 分离线程

  • 把子线程从主线程分离,后台独立运行。
  • 主线程不再等待,也不能再 join
  • 主线程退出会直接结束整个进程,分离线程也会被强行终止。
cpp 复制代码
thread t(taskFunc);
t.detach();

3. joinable() 判断线程状态

判断线程是否还可以 join

cpp 复制代码
if (t.joinable())
{
    t.join();
}

禁止直接销毁 joinable 状态的线程对象,会直接程序崩溃。

4. 获取线程属性

cpp 复制代码
// 获取当前线程ID
this_thread::get_id();

// 获取CPU核心支持的并发线程数
thread::hardware_concurrency();

五、线程传参详解

1. 默认值传递

线程构造函数会拷贝参数到线程内部。

2. 引用传递 std::ref

普通传参无法引用,必须用 std::ref 包装:

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

void add(int& a)
{
    a += 10;
}

int main()
{
    int x = 5;
    thread t(add, ref(x));
    t.join();
    cout << x << endl;  // 15
    return 0;
}

3. 指针传递

直接传递变量地址即可,注意变量生命周期不能提前结束。

4. 线程捕获引用陷阱

Lambda 直接引用局部变量,若主线程先退出,子线程会访问野指针。


六、线程安全与互斥量 mutex

多线程读写共享全局变量会引发数据竞争、结果错乱,需要加锁。

1. std::mutex 基础互斥锁

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

mutex mtx;
int g_val = 0;

void safeAdd()
{
    for (int i = 0; i < 100000; ++i)
    {
        mtx.lock();     // 加锁
        g_val++;
        mtx.unlock();   // 解锁
    }
}

int main()
{
    thread t1(safeAdd);
    thread t2(safeAdd);
    t1.join();
    t2.join();
    cout << g_val << endl;
    return 0;
}

2. std::lock_guard 自动锁

作用域托管,出作用域自动解锁 ,无需手动 lock/unlock

cpp 复制代码
void safeAdd()
{
    for (int i = 0; i < 100000; ++i)
    {
        lock_guard<mutex> lg(mtx);
        g_val++;
    }
}

3. std::unique_lock 灵活锁

lock_guard 更灵活:支持手动解锁、延迟加锁、锁转移:

cpp 复制代码
unique_lock<mutex> ul(mtx);
// 手动解锁
ul.unlock();
// 重新加锁
ul.lock();

七、条件变量 condition_variable

作用:线程等待、条件满足再唤醒,常用于生产者-消费者模型。

核心搭配:

  • std::condition_variable
  • std::unique_lock<std::mutex>
  • wait() 等待、notify_one() 唤醒一个、notify_all() 唤醒全部
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false;

void consumer()
{
    unique_lock<mutex> lk(mtx);
    // 等待条件为true
    cv.wait(lk, [](){ return ready; });
    cout << "子线程被唤醒,开始工作" << endl;
}

void producer()
{
    this_thread::sleep_for(chrono::seconds(1));
    lock_guard<mutex> lk(mtx);
    ready = true;
    cv.notify_one(); // 唤醒一个等待线程
}

int main()
{
    thread t1(consumer);
    thread t2(producer);
    t1.join();
    t2.join();
    return 0;
}

八、原子操作 std::atomic

无需互斥锁,底层硬件指令保证变量原子性,效率更高。

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

atomic<int> g_cnt{0};

void addTask()
{
    for (int i = 0; i < 100000; ++i)
    {
        g_cnt++;
    }
}

int main()
{
    thread t1(addTask);
    thread t2(addTask);
    t1.join();
    t2.join();
    cout << g_cnt << endl;
    return 0;
}

九、线程局部存储 thread_local

thread_local 修饰的变量每个线程独有一份,互不干扰,天然线程安全:

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

thread_local int num = 100;

void func(int id)
{
    num += id;
    cout << "线程" << id << " num = " << num << endl;
}

int main()
{
    thread t1(func, 1);
    thread t2(func, 2);
    t1.join();
    t2.join();
    return 0;
}

十、std::promise 与 std::future 线程传值

用于子线程向主线程返回结果

  • std::promise:子线程设置值
  • std::future:主线程获取值,get() 会阻塞等待
cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
using namespace std;

void setValue(promise<int> p)
{
    p.set_value(666); // 设置结果
}

int main()
{
    promise<int> p;
    future<int> f = p.get_future();

    thread t(setValue, move(p));
    // 阻塞等待获取子线程结果
    int res = f.get();
    cout << "收到子线程结果:" << res << endl;

    t.join();
    return 0;
}

十一、死锁产生与解决

1. 死锁产生四个必要条件

  1. 互斥占有
  2. 不可剥夺
  3. 请求保持
  4. 循环等待

2. 常见死锁场景

多个线程互相持有对方需要的锁,都不释放,互相永久等待。

3. 死锁避免方案

  1. 统一锁的申请顺序,所有线程按相同顺序加锁。
  2. 减少嵌套互斥锁。
  3. 使用 std::lock 一次性锁定多个互斥量。
  4. 加锁设置超时时间。

十二、多线程编程通用注意事项

  1. 共享全局变量、静态变量必须加锁或用 atomic
  2. 分离线程 detach 要保证依赖资源生命周期不提前销毁。
  3. 不能对同一个线程多次 join
  4. 线程对象析构前必须 joindetach,否则程序崩溃。
  5. 类成员函数作为线程入口,必须传递对象地址
  6. 多线程尽量少用全局变量,优先 thread_local 或消息队列通信。

相关推荐
CHANG_THE_WORLD1 小时前
<Fluent Python > Unicode 文本与字节
开发语言·python
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
凯瑟琳.奥古斯特1 小时前
Bootstrap快速上手指南
开发语言·前端·css·bootstrap·html
我就是妖怪2 小时前
Kimi K2.6 智能效果实测与能力全景展示
开发语言
中二痞2 小时前
下载Python 版本,环境变量变更以及PyCharm更换python版本
开发语言·python·pycharm
故事和你912 小时前
洛谷-算法2-3-分治与倍增5
开发语言·数据结构·c++·算法·动态规划·图论
SilentSamsara2 小时前
标准库精讲:collections/itertools/functools/pathlib 实战
开发语言·vscode·python·青少年编程·pycharm
逻辑驱动的ken2 小时前
Java高频面试考点场景题17
开发语言·jvm·面试·求职招聘·春招
charlie1145141912 小时前
通用GUI编程技术——图形渲染实战(三十九)——纹理与采样器:从WIC加载到GPU渲染
开发语言·c++·图形渲染·win32