C++ 多线程(超详细)

C++ 多线程:从零开始掌握并发编程

你有没有遇到过这样的场景:程序在处理一个大任务时,界面卡住不动,用户只能干等?或者,某个计算密集型任务耗时太久,影响了整体响应速度?这时候,你就该考虑使用 C++ 多线程了。

C++ 多线程是一种让程序同时执行多个任务的技术,它能显著提升程序效率,尤其在处理 I/O 操作、数据计算、图像渲染等场景中表现突出。想象一下,你正在做一桌饭菜,如果只靠一个人炒菜、煮饭、切菜,那得花很久。但如果你能同时让一个人炒菜,另一个人煮饭,再一个人切菜,效率自然翻倍。C++ 多线程,就是给程序“分身术”——让多个“小人”同时干活。

今天我们就从基础开始,一步步带你走进 C++ 多线程的世界,不讲虚的,只讲能用、好懂、实用的内容。


为什么需要 C++ 多线程?

在传统的单线程程序中,代码是按顺序一条一条执行的。比如你写了一个函数计算 1 到 1000000 的和,程序会从 1 加到 1000000,期间其他任务无法执行。如果这个计算耗时 5 秒,那用户就得等 5 秒,这显然不理想。

而 C++ 多线程允许你将一个大任务拆分成多个子任务,让它们在不同的线程中并行执行。比如你可以用一个线程计算前 50 万的和,另一个线程计算后 50 万的和,最后合并结果。这样整体时间可能只用 3 秒,效率提升明显。

更进一步,C++ 多线程还能让程序“一边干活一边响应”——比如在后台下载文件的同时,界面仍然可以滚动、点击按钮。这种体验,正是现代软件的标配。


创建线程的两种方式

C++11 引入了标准的线程库 <thread>,这是现代 C++ 多线程编程的基础。我们先来认识两种创建线程的方法。

使用函数作为线程入口

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

// 定义一个普通函数,作为线程执行的入口
void print_message() {
    for (int i = 0; i < 5; ++i) {
        std::cout << "线程 ID: " << std::this_thread::get_id()
                  << " 输出: Hello from thread!" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

int main() {
    // 创建一个线程对象,传入函数名作为入口
    std::thread t1(print_message);

    // 主线程继续执行
    std::cout << "主线程在运行..." << std::endl;

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

    std::cout << "所有线程已结束。" << std::endl;
    return 0;
}

代码注释

  • std::thread t1(print_message);:创建一个线程,让它执行 print_message 函数。
  • std::this_thread::get_id():获取当前线程的唯一 ID,用于区分不同线程。
  • std::this_thread::sleep_for(...):让当前线程暂停指定时间,模拟任务耗时。
  • t1.join():阻塞主线程,直到线程 t1 完成。这是关键!必须调用 join(),否则程序可能崩溃。

使用 Lambda 表达式创建线程

Lambda 表达式是 C++11 的另一个强大特性,它能让线程执行匿名函数。

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

int main() {
    // 使用 Lambda 表达式创建线程
    std::thread t2([]() {
        for (int i = 0; i < 3; ++i) {
            std::cout << "Lambda 线程 ID: " << std::this_thread::get_id()
                      << " 输出: I'm a lambda thread!" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(300));
        }
    });

    std::cout << "主线程正在运行..." << std::endl;

    // 等待线程完成
    t2.join();

    std::cout << "Lambda 线程已结束。" << std::endl;
    return 0;
}

代码注释

  • []() 是 Lambda 的语法,[] 表示不捕获外部变量,括号内是参数列表(空表示无参数)。
  • Lambda 内部可以写任意逻辑,非常灵活,适合小型任务。
  • 与函数方式一样,必须调用 join(),否则资源泄漏。

线程生命周期管理:join 与 detach

线程创建后,必须明确它的“去向”。C++ 提供了两种方式:joindetach

方法 作用 使用场景
join() 阻塞当前线程,直到目标线程完成 你希望等待线程结束再继续执行
detach() 让线程独立运行,脱离主线程控制 线程执行后台任务,无需等待

join 的使用场景

std::thread t3([]() {
    std::cout << "任务开始..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "任务完成。" << std::endl;
});

// 主线程等待 t3 完成
t3.join();
std::cout << "主线程继续执行。" << std::endl;

说明:join() 会阻塞主线程,直到 t3 执行完毕。适用于需要等待结果的场景。

detach 的使用场景

std::thread t4([]() {
    for (int i = 0; i < 5; ++i) {
        std::cout << "后台线程: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
});

// 让线程脱离主线程,独立运行
t4.detach();

std::cout << "主线程继续运行,不等待 t4。" << std::endl;

// 注意:不能再调用 t4.join() 或 t4.detach(),否则程序崩溃

重要提醒:一旦调用 detach(),就不能再调用 join()。如果线程还没结束就被销毁,程序会崩溃。所以使用 detach 时要格外小心。


共享数据与线程安全

多个线程同时访问同一个变量,就可能出问题。这就像几个人同时在一张纸上写字,写到最后谁也看不懂。

问题示例:竞态条件(Race Condition)

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

int counter = 0; // 全局共享变量

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter; // 这里不是原子操作!
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "最终计数: " << counter << std::endl;
    return 0;
}

问题分析
++counter 实际上包含三个步骤:读取 counter 值、加 1、写回。如果两个线程同时读取,可能都读到 0,然后都加 1,最终结果是 1 而不是 2。这就是竞态条件。

解决方案:使用互斥锁(mutex)

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

int counter = 0;
std::mutex mtx; // 互斥锁,用于保护共享资源

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // 自动加锁,作用域结束自动解锁
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "最终计数: " << counter << std::endl; // 输出应为 200000
    return 0;
}

代码注释

  • std::lock_guard<std::mutex> 是 RAII 机制,进入作用域自动加锁,离开时自动解锁。
  • mtx 是互斥锁对象,任何时刻只能有一个线程持有锁。
  • 使用 lock_guard 可避免忘记解锁导致死锁。

线程池:高效管理线程资源

频繁创建和销毁线程开销大。线程池是一种预先创建多个线程,任务提交后由池中线程处理的模式。

虽然 C++ 标准库没有内置线程池,但我们可以简单实现一个:

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    explicit ThreadPool(size_t num_threads) {
        for (size_t i = 0; i < num_threads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex);
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) return;
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task(); // 执行任务
                }
            });
        }
    }

    template<class F>
    void enqueue(F&& f) {
        {
            std::lock_guard<std::mutex> lock(queue_mutex);
            tasks.emplace(std::forward<F>(f));
        }
        condition.notify_one();
    }

    ~ThreadPool() {
        {
            std::lock_guard<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& worker : workers) {
            worker.join();
        }
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

int main() {
    ThreadPool pool(4); // 创建 4 个线程的线程池

    for (int i = 0; i < 10; ++i) {
        pool.enqueue([i] {
            std::cout << "任务 " << i << " 由线程 ID: " << std::this_thread::get_id()
                      << " 执行。" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }

    std::cout << "所有任务已提交。" << std::endl;
    return 0;
}

说明

  • 线程池避免了频繁创建线程的开销。
  • 使用 std::condition_variable 实现线程间通信,任务就绪时唤醒等待线程。
  • std::forward 保证完美转发,支持各类任务。

总结与建议

C++ 多线程是提升程序性能的重要工具。从创建线程、管理生命周期,到处理共享数据和构建线程池,每一步都值得深入理解。

  • 初学者建议从 std::thread + join 开始,理解基本流程。
  • 中级开发者应掌握 mutexlock_guard,避免竞态条件。
  • 高级应用可考虑线程池、任务队列等模式,提升系统稳定性。

记住:线程不是越多越好,过多线程反而会因上下文切换带来性能下降。合理设计、适度使用,才能真正发挥 C++ 多线程的优势。

在实际项目中,C++ 多线程能让你的程序更快、更流畅、更专业。现在就开始动手写吧,让代码“跑起来”!