C++ 多线程库 <thread>(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言

在现代高性能计算和复杂系统开发中,C++ 多线程库 <thread> 是一个不可或缺的工具。它允许开发者通过并行执行任务,充分利用多核处理器的性能优势,从而提升程序的效率和响应速度。然而,对于许多编程初学者和中级开发者而言,多线程编程可能显得抽象且充满挑战。本文将从基础概念出发,逐步深入讲解 <thread> 库的核心功能、实际应用场景和常见问题,帮助读者掌握这一强大工具。


基础概念:线程与进程

在理解 <thread> 库之前,需要先明确两个关键概念:线程(Thread)进程(Process)

  • 进程:是一个独立运行的程序实例,拥有自己的内存空间、代码和资源。
  • 线程:是进程内部的执行单元,多个线程可以共享进程的资源(如内存),但各自独立执行代码。

比喻:可以将进程想象成一家工厂,而线程则是工厂里的工人。工厂(进程)负责分配资源(如原材料、设备),而工人(线程)则负责具体的工作任务(如组装零件)。多个线程可以在同一进程中并行工作,从而提升整体效率。


如何使用 <thread> 库:从创建到启动

1. 引入头文件与命名空间

在使用 <thread> 库之前,需在代码开头包含头文件:

#include <thread>  

2. 创建线程对象

通过 std::thread 类创建线程对象,并指定线程要执行的函数。例如:

void print_message() {  
    std::cout << "Hello from thread!" << std::endl;  
}  

int main() {  
    std::thread t1(print_message);  // 创建线程并启动  
    return 0;  
}  

注意:线程对象创建后会立即启动,执行目标函数。

3. 等待线程结束:join() 方法

如果不调用 join(),主线程可能在子线程结束前终止,导致未定义行为。因此,必须确保线程结束后再退出程序:

int main() {  
    std::thread t1(print_message);  
    t1.join();  // 等待线程 t1 完成  
    return 0;  
}  

参数传递与函数兼容性

1. 传递参数给线程函数

可以通过以下方式将参数传递给线程函数:

void print_numbers(int start, int end) {  
    for (int i = start; i <= end; ++i) {  
        std::cout << i << " ";  
    }  
}  

int main() {  
    std::thread t1(print_numbers, 1, 5);  // 传递整数参数  
    t1.join();  
    return 0;  
}  

2. 使用引用或右值引用传递参数

若需修改外部变量,可传递引用:

void increment(int& counter) {  
    counter += 1;  
}  

int main() {  
    int count = 0;  
    std::thread t1(increment, std::ref(count));  // 使用 std::ref 明确传递引用  
    t1.join();  
    std::cout << "Final count: " << count << std::endl;  // 输出 1  
    return 0;  
}  

3. 支持的函数类型

  • 普通函数:如 print_message()
  • Lambda 表达式
    std::thread t1([]() {  
        std::cout << "Lambda thread running!" << std::endl;  
    });  
    
  • 成员函数:需绑定对象和函数指针,例如:
    class Worker {  
    public:  
        void do_work() { /* ... */ }  
    };  
    
    Worker w;  
    std::thread t1(&Worker::do_work, &w);  // 传递成员函数和对象指针  
    

线程同步:避免竞争条件

1. 竞争条件与互斥锁(Mutex)

当多个线程同时访问共享资源(如计数器)时,可能引发数据不一致的问题,称为竞争条件。为解决这一问题,可以使用 std::mutex

#include <mutex>  

std::mutex mtx;  
int shared_counter = 0;  

void increment_mutex() {  
    mtx.lock();  
    shared_counter++;  
    mtx.unlock();  
}  

int main() {  
    std::thread t1(increment_mutex);  
    std::thread t2(increment_mutex);  
    t1.join(); t2.join();  
    std::cout << "Counter: " << shared_counter << std::endl;  // 期望输出 2  
    return 0;  
}  

更简洁的方式:使用 std::lock_guard 自动管理锁的生命周期:

void increment_guard() {  
    std::lock_guard<std::mutex> lock(mtx);  
    shared_counter++;  
}  

2. 条件变量(Condition Variable)

当需要线程间通信(如等待某个条件满足时再执行操作),可以使用 std::condition_variable

#include <condition_variable>  

std::condition_variable cv;  
std::mutex mtx;  
bool ready = false;  

void worker() {  
    std::unique_lock<std::mutex> lock(mtx);  
    cv.wait(lock, []{ return ready; });  // 等待条件为真  
    std::cout << "Worker started!" << std::endl;  
}  

int main() {  
    std::thread t(worker);  
    {  
        std::lock_guard<std::mutex> lock(mtx);  
        ready = true;  
    }  
    cv.notify_one();  // 通知等待的线程  
    t.join();  
    return 0;  
}  

3. 原子操作(Atomic)

对于简单类型(如整数),可使用 std::atomic 直接实现线程安全操作:

#include <atomic>  

std::atomic<int> atomic_counter(0);  

void increment_atomic() {  
    atomic_counter++;  
}  

int main() {  
    std::thread t1(increment_atomic);  
    std::thread t2(increment_atomic);  
    t1.join(); t2.join();  
    std::cout << "Atomic Counter: " << atomic_counter << std::endl;  // 输出 2  
    return 0;  
}  

实际案例:多线程图像处理

假设需要对一张大图进行灰度化处理,可以将其划分为多个块,分配给不同线程并行处理:

#include <vector>  
#include <thread>  

struct ImageBlock {  
    int x, y, width, height;  
    // 其他图像数据  
};  

void process_block(const ImageBlock& block) {  
    // 灰度化算法  
}  

int main() {  
    const int num_threads = std::thread::hardware_concurrency();  
    std::vector<std::thread> threads;  
    std::vector<ImageBlock> blocks = split_image_into_blocks();  

    for (size_t i = 0; i < blocks.size(); ++i) {  
        threads.emplace_back(process_block, blocks[i]);  
    }  

    for (auto& t : threads) {  
        t.join();  
    }  
    return 0;  
}  

说明

  • std::thread::hardware_concurrency() 返回系统支持的并行线程数。
  • 将任务划分为多个块,利用线程池(此处简化为 std::vector<std::thread>)分配任务,实现高效并行处理。

进阶话题:线程池与线程局部存储

1. 线程池(Thread Pool)

线程池通过复用固定数量的线程,减少线程创建和销毁的开销。例如:

#include <queue>  
#include <future>  

class ThreadPool {  
public:  
    void enqueue(std::function<void()> task) {  
        {  
            std::unique_lock<std::mutex> lock(queue_mutex);  
            tasks.push(task);  
        }  
        cv.notify_one();  
    }  

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

    void worker_thread() {  
        while (true) {  
            std::function<void()> task;  
            {  
                std::unique_lock<std::mutex> lock(queue_mutex);  
                cv.wait(lock, [this]{ return stop || !tasks.empty(); });  
                if (stop && tasks.empty()) return;  
                task = std::move(tasks.front());  
                tasks.pop();  
            }  
            task();  
        }  
    }  

public:  
    ThreadPool(size_t thread_count) {  
        for (size_t i = 0; i < thread_count; ++i) {  
            workers.emplace_back([this]{ worker_thread(); });  
        }  
    }  

    ~ThreadPool() {  
        stop = true;  
        cv.notify_all();  
        for (auto& worker : workers) worker.join();  
    }  
};  

2. 线程局部存储(TLS)

通过 std::thread_local 关键字,可以为每个线程分配独立的变量副本:

std::thread_local int thread_id = 0;  

void set_and_print_id() {  
    thread_id = std::hash<std::thread::id>()(std::this_thread::get_id());  
    std::cout << "Thread ID: " << thread_id << std::endl;  
}  

int main() {  
    std::thread t1(set_and_print_id);  
    std::thread t2(set_and_print_id);  
    t1.join(); t2.join();  
    return 0;  
}  

常见问题与解决方案

问题描述解决方案
死锁(Deadlock)避免嵌套锁,使用 std::lock_guard 管理锁的生命周期,检查锁的获取顺序。
资源竞争(Race Condition)使用互斥锁、原子操作或条件变量保护共享资源。
线程泄漏(Thread Leak)确保每个 std::thread 对象调用 join()detach()
优先级反转(Priority Inversion)在实时系统中使用优先级继承策略。

结论

C++ 多线程库 <thread> 为开发者提供了强大的工具,能够显著提升程序的性能和响应能力。通过合理设计线程间通信、同步机制和资源管理,开发者可以避免常见的陷阱,如竞争条件和死锁。对于初学者而言,建议从简单案例入手,逐步掌握线程创建、参数传递和同步技术;中级开发者则可以探索线程池、TLS 等高级功能,以实现更复杂的并行计算场景。

随着多核处理器的普及,多线程编程已成为现代软件开发的必备技能。掌握 <thread> 库不仅能提升代码的执行效率,还能帮助开发者设计出更健壮、可扩展的系统架构。


(全文约 1800 字)

最新发布