本文基于 C++11 标准( std::thread 为核心),覆盖线程基础、同步机制、常见问题与最佳实践,所有代码均可直接编译运行。

一、核心概念:线程与多线程

1. 基础定义

  • 线程(Thread):操作系统调度的最小执行单元,是进程内的独立执行流,共享进程的内存空间(堆、全局变量、代码段),拥有独立的栈空间、寄存器、程序计数器。
  • 多线程(Multithreading):在一个进程中同时运行多个线程,利用多核CPU并行执行任务,提升程序效率与响应速度。
  • 核心优势
  • 并行处理耗时任务(如IO、计算),避免UI阻塞
  • 充分利用多核CPU资源,提升吞吐量
  • 模块化拆分任务,简化复杂逻辑

2. 线程 vs 进程

特性 线程 进程
内存空间 共享进程内存 独立内存空间
切换开销 小(仅切换上下文) 大(需切换页表、内存空间)
通信方式 共享内存、互斥锁、条件变量 管道、消息队列、共享内存(需同步)
安全性 线程崩溃会导致整个进程崩溃 进程崩溃互不影响
资源占用

简单总结:

  • 线程是进程内的执行单元,共享资源,通信简单但隔离性差
  • 进程是独立的执行实体,资源隔离好但开销大
  • 选择时需权衡通信效率和安全性隔离的需求
二、C++ 线程基础: std::thread 核心用法

C++11 引入 头文件,提供 std::thread 类用于创建和管理线程,是标准库的核心线程组件。

1. 线程的创建与启动

std::thread 构造函数可接收函数lambda仿函数、成员函数等可调用对象,创建后立即启动线程。

示例1:基础线程创建(函数 + lambda)

  
#include <iostream>
#include <thread>
#include <chrono>

// 普通函数作为线程入口
void thread_func(int id, const std::string& msg) {
    for (int i = 0; i < 3; ++i) {
        std::cout << "Thread " << id << ": " << msg << " (count: " << i << ")\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 线程休眠500ms
    }
}

int main() {
    // 1. 创建线程1:绑定普通函数
    std::thread t1(thread_func, 1, "Hello from thread 1");
    
    // 2. 创建线程2:绑定lambda表达式
    std::thread t2([](sslocal://flow/file_open?url=int+id&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
        for (int i = 0; i < 3; ++i) {
            std::cout << "Lambda Thread " << id << ": Running...\n";
            std::this_thread::sleep_for(std::chrono::milliseconds(300));
        }
    }, 2);

    // 3. 主线程任务
    std::cout << "Main thread: Waiting for child threads...\n";

    // 4. 等待子线程执行完毕(必须调用,否则线程销毁时崩溃)
    t1.join();
    t2.join();

    std::cout << "Main thread: All threads finished!\n";
    return 0;
}

关键知识点:

  • std::this_thread::sleep_for :让当前线程休眠指定时间,释放CPU资源。
  • join() :阻塞主线程,等待子线程执行完毕后再继续,是线程安全销毁的核心方法。
  • detach() :将线程与 std::thread 对象分离,线程在后台独立运行,不可再调用 join() ,需确保线程任务执行完毕前进程不退出(否则线程被强制终止)。
  • 线程参数传递: std::thread 会拷贝参数到线程栈中,若需传递引用,需用 std::ref 包装(否则会拷贝对象,导致引用失效)。

示例2:引用参数传递( std::ref )

  
void update_value(int& val) {
    val += 10;
    std::cout << "Thread: val = " << val << "\n";
}

int main() {
    int num = 5;
    // 必须用std::ref传递引用,否则会拷贝num,原变量不会被修改
    std::thread t(update_value, std::ref(num));
    t.join();
    std::cout << "Main: val = " << num << "\n"; // 输出15
    return 0;
}

示例3:成员函数作为线程入口

  
#include <iostream>
#include <thread>

class Worker {
public:
    void do_work(int id) {
        std::cout << "Worker " << id << " is working...\n";
    }
};

int main() {
    Worker w;
    // 成员函数作为线程入口:第一个参数为成员函数指针,第二个为对象指针/引用
    std::thread t(&Worker::do_work, &w, 1);
    t.join();
    return 0;
}

2. 线程的常用方法

方法 功能
join() 阻塞等待线程执行完毕,线程对象销毁前必须调用(否则崩溃)
detach() 分离线程,线程在后台运行,不可再join
joinable() 判断线程是否可join(未join、未detach的线程可join)
get_id() 获取线程ID(类型为thread::id,用于标识线程)
native_handle() 获取原生线程句柄(用于调用系统API,如pthread接口)
this_thread::get_id() 获取当前线程的ID
this_thread::yield() 提示调度器让出当前时间片
this_thread::sleep_for() 让当前线程休眠指定时长
this_thread::sleep_until() 让当前线程休眠到指定时间点

使用注意事项:

  1. 线程对象在析构前必须处于非运行状态(join()完成)或已分离(detach()
  2. 分离后的线程资源由系统自动回收,程序无法再控制其生命周期
  3. 线程ID在线程结束后可能被系统复用,不宜作为唯一标识长期存储

示例4:线程ID与joinable判断

  
#include <iostream>
#include <thread>

void thread_func() {
    std::cout << "Child thread ID: " << std::this_thread::get_id() << "\n";
}

int main() {
    std::thread t(thread_func);
    std::cout << "Main thread ID: " << std::this_thread::get_id() << "\n";
    std::cout << "Child thread object ID: " << t.get_id() << "\n";
    std::cout << "Is t joinable? " << t.joinable() << "\n"; // 输出1(true)

    t.join();
    std::cout << "After join, is t joinable? " << t.joinable() << "\n"; // 输出0(false)

    return 0;
}
三、多线程同步:解决数据竞争与线程安全

多线程的核心问题是数据竞争(Race Condition):多个线程同时读写共享资源,导致数据错乱。C++ 提供多种同步机制解决该问题。

1. 互斥锁: std::mutex 与 std::lock_guard

std::mutex 是最基础的互斥锁,用于保护共享资源,保证同一时间只有一个线程进入临界区。
std::lock_guard 是RAII风格的锁管理器,自动加锁/解锁,避免手动解锁遗漏导致死锁。

示例5:无锁 vs 有锁的对比(数据竞争问题)

  
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

// 共享资源
int counter = 0;
std::mutex mtx; // 互斥锁

// 无锁版本:数据竞争,结果错误
void unsafe_increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++; // 非原子操作,多线程同时读写导致数据错乱
    }
}

// 有锁版本:线程安全,结果正确
void safe_increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // RAII自动加锁,作用域结束自动解锁
        counter++;
    }
}

int main() {
    // 测试无锁版本
    counter = 0;
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(unsafe_increment);
    }
    for (auto& t : threads) t.join();
    std::cout << "Unsafe counter: " << counter << "\n"; // 结果远小于1000000,随机错误

    // 测试有锁

2893137936: 04-09 22:45:24
counter = 0;
     threads.clear();
     for (int i = 0; i < 10; ++i) {
         threads.emplace_back(safe_increment);
     }
     for (auto& t : threads) t.join();
     std::cout << "Safe counter: " << counter << "\n"; // 结果恒为1000000,正确
     return 0;
 }

关键知识点:

  • std::mutex 核心方法
  • lock() :加锁,若锁被占用则阻塞等待
  • unlock() :解锁,需手动调用(易遗漏,推荐用 lock_guard )
  • try_lock() :尝试加锁,成功返回true,失败返回false,不阻塞
  • std::lock_guard :
  • 构造时自动加锁,析构时自动解锁,完全避免死锁
  • 不可拷贝、不可移动,作用域内有效
  • std::unique_lock :比 lock_guard 更灵活,支持手动加锁/解锁、延迟加锁、转移所有权,适合复杂场景(如条件变量)。

2. 递归互斥锁: std::recursive_mutex

普通 std::mutex 不可递归加锁(同一线程重复加锁会导致死锁), std::recursive_mutex 支持同一线程多次加锁,需对应次数解锁。

示例6:递归锁使用场景

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex rmtx;

void recursive_func(int depth) {
    std::lock_guard<std::recursive_mutex> lock(rmtx);
    std::cout << "Depth: " << depth << "\n";
    if (depth > 0) {
        recursive_func(depth - 1); // 同一线程递归加锁,不会死锁
    }
}

int main() {
    std::thread t(recursive_func, 3);
    t.join();
    return 0;
}

3. 条件变量: std::condition_variable

用于线程间的等待-通知机制,让线程在条件不满足时阻塞等待,条件满足时被唤醒,避免轮询浪费CPU。
必须配合 std::unique_lock 使用。

示例7:生产者-消费者模型(条件变量经典场景)

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>

std::queue<int> buffer; // 共享缓冲区
std::mutex mtx;
std::condition_variable cv;
const int MAX_BUFFER_SIZE = 5;

// 生产者线程:生产数据放入缓冲区
void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        // 缓冲区满时,阻塞等待消费者消费
        cv.wait(lock,  { return buffer.size() < MAX_BUFFER_SIZE; });

        int data = id * 100 + i;
        buffer.push(data);
        std::cout << "Producer " << id << " produced: " << data << "\n";

        lock.unlock();
        cv.notify_one(); // 唤醒一个等待的消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 消费者线程:从缓冲区取数据
void consumer(int id) {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        // 缓冲区空时,阻塞等待生产者生产
        cv.wait(lock,  { return !buffer.empty(); });

        int data = buffer.front();
        buffer.pop();
        std::cout << "Consumer " << id << " consumed: " << data << "\n";

        lock.unlock();
        cv.notify_one(); // 唤醒一个等待的生产者
        std::this_thread::sleep_for(std::chrono::milliseconds(150));
    }
}

int main() {
    std::thread p1(producer, 1);
    std::thread c1(consumer, 1);
    std::thread c2(consumer, 2);

    p1.join();
    c1.join();
    c2.join();

    return 0;
}

关键知识点:

  • wait(lock, predicate) :阻塞等待,直到 predicate 返回true(避免虚假唤醒)
  • **notify_one() :**唤醒一个等待的线程
  • **notify_all() :**唤醒所有等待的线程
  • 虚假唤醒:线程被唤醒但条件不满足,因此必须用 predicate 二次判断

4. 原子操作: std::atomic

对于简单的共享变量(如计数器、标志位),用原子操作替代互斥锁,性能更高,无锁开销。
std::atomic 是C++11引入的原子类型,保证操作的原子性、可见性、有序性。

示例8:原子计数器(替代互斥锁)

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

std::atomic<int> counter(0); // 原子计数器,初始值0

void atomic_increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++; // 原子操作,线程安全,无需锁
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(atomic_increment);
    }
    for (auto& t : threads) t.join();
    std::cout << "Atomic counter: " << counter << "\n"; // 恒为1000000,正确
    return 0;

常用原子操作:

  • load() :读取原子变量值
  • store() :写入原子变量值
  • fetch_add() :原子加法( counter++ 等价于 fetch_add(1) )
  • fetch_sub() :原子减法
  • exchange() :原子交换值
  • compare_exchange_strong() :比较并交换(CAS操作,无锁编程核心)
四、多线程常见问题与最佳实践

1. 常见错误与解决方案

错误/问题 主要原因 推荐解决方案
线程销毁时崩溃 未调用 join()/detach(),线程对象销毁时线程仍在运行 线程对象销毁前必须调用 join()(推荐)或 detach()
数据竞争 多个线程同时读写共享资源,未加同步 用互斥量/读写锁保护共享资源
死锁 多个线程互相持有对方需要的锁,无限等待 按固定顺序加锁、用 scoped_lock 同时加锁、避免嵌套锁
虚假唤醒 条件变量被唤醒但条件不满足 必须将等待放在 while 循环中进行二次条件判断
UI阻塞/无响应 主线程执行耗时任务,未用子线程 耗时任务放子线程,UI更新切回主线程(Qt 推荐用信号槽机制)

使用建议: 在多线程编程中,建议遵循 RAII 原则管理资源(如使用 lock_guard),在 Qt 中充分利用信号槽的线程安全特性进行跨线程通信,并尽量减少共享状态,采用消息传递模式来降低复杂度。

2. 死锁示例与解决

死锁示例:

std::mutex mtx1, mtx2;

void thread1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock2(mtx2); // 等待mtx2,被thread2持有
}

void thread2() {
    std::lock_guard<std::mutex> lock2(mtx2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock1(mtx1); // 等待mtx1,被thread1持有
}

**解决方案1:固定加锁顺序**

void thread1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::lock_guard<std::mutex> lock2(mtx2); // 先mtx1后mtx2
}

void thread2() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::lock_guard<std::mutex> lock2(mtx2); // 相同顺序,无死锁
}

解决方案2: std::lock  同时加锁

void thread1() {
    std::lock(mtx1, mtx2); // 同时加锁,避免顺序问题
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); // 接管已加锁的mtx1
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock); // 接管已加锁的mtx2
}

void thread2() {
    std::lock(mtx1, mtx2);
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}

3. 多线程最佳实践

1. 优先用 std::thread ,避免原生线程API(如pthread):标准库跨平台、更安全。

2. 用RAII锁( std::lock_guard / std::unique_lock ),禁止手动 lock() / unlock() :避免死锁。

3. 简单共享变量用 std::atomic ,复杂资源用 std::mutex :平衡性能与安全性。

4. 线程池替代频繁创建销毁线程:线程创建/销毁开销大,用线程池复用线程(C++20引入 std::jthread ,C++17可自己实现)。

5. 避免线程间共享大量数据:减少同步开销,降低复杂度。

6. 线程数量与CPU核心数匹配:过多线程会导致上下文切换开销,一般设为 std::thread::hardware_concurrency() (获取CPU核心数)。

五、C++20 新特性: std::jthread (自动join的线程)

C++20 引入 std::jthread ( std::thread 的改进版),核心优势:

  • 析构时自动调用 join() ,无需手动 join() ,彻底避免线程销毁崩溃
  • 内置 std::stop_source ,支持线程安全的停止请求

示例9: std::jthread 基础用法

#include <iostream>
#include <thread>
#include <chrono>

void thread_func(std::stop_token st) {
    while (!st.stop_requested()) { // 检查是否收到停止请求
        std::cout << "jthread running...\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    std::cout << "jthread stopped!\n";
}

int main() {
    std::jthread t(thread_func); // 自动启动,无需手动join
    std::this_thread::sleep_for(std::chrono::seconds(3));
    t.request_stop(); // 发送停止请求
    // 析构时自动join,无需手动调用
    return 0;
}
六、Qt 线程补充(结合你的项目场景)

你之前的Qt项目中用 QThread ,与 std::thread 的核心区别:

  • QThread 是Qt封装的线程类,继承自 QObject ,支持信号槽、事件循环
  • QThread 推荐用 moveToThread 模式(将工作对象移到线程中),而非重写 run()
  • QMutex 是Qt的互斥锁,与 std::mutex 功能一致,Qt项目中优先用 QMutex

Qt 线程安全示例(静态锁+成员线程)

// thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include <QMutex>

class Thread : public QThread {
    Q_OBJECT
public:
    static int num;
    static QMutex mutex; // 静态锁,所有实例共享
    void stop();
protected:
    void run() override;
private:
    std::atomic<bool> m_running{true};
};

#endif

// thread.cpp
int Thread::num = 0;
QMutex Thread::mutex;

void Thread::stop() { m_running = false; }

void Thread::run() {
    while (m_running) {
        QMutexLocker locker(&mutex); // RAII锁,自动加锁/解锁
        num++;
        qDebug() << "Thread" << QThread::currentThreadId() << "num:" << num;
        msleep(100);
    }
}

// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "thread.h"

namespace Ui { class Widget; }
class Widget : public QWidget {
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget() override;
protected:
    void closeEvent(QCloseEvent *event) override;
private:
    Ui::Widget *ui;
    Thread thread1, thread2; // 成员线程,生命周期与Widget一致
};

#endif

// widget.cpp
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
    thread1.start();
    thread2.start();
}

Widget::~Widget() { delete ui; }

void Widget::closeEvent(QCloseEvent *event) {
    thread1.stop();
    thread2.stop();
    thread1.wait();
    thread2.wait();
    event->accept();
}
七、编译与运行

所有代码需支持C++11及以上标准,编译命令(GCC):

bash
g++ -std=c++11 thread_demo.cpp -o thread_demo -pthread
./thread_demo

( -pthread 是必须的,用于链接线程库)

八、总结
  • 线程基础: std::thread 是核心, join() 是线程安全销毁的关键。
  • 同步机制: std::mutex 保护共享资源, std::condition_variable 实现线程通信, std::atomic 用于简单原子操作。
  • **最佳实践:**RAII锁、避免死锁、线程池、匹配CPU核心数。
  • Qt 适配: QThread 用成员变量+静态锁,重写 closeEvent 实现安全退出。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐