0
点赞
收藏
分享

微信扫一扫

C++ 多线程编程详解:从基础到实战

在现代计算机体系结构中,多核处理器已经成为主流。为了充分发挥硬件性能,多线程编程显得尤为重要。C++ 作为一门强大而灵活的系统级语言,从 C++11 开始正式引入了标准化的线程库,为开发者提供了创建、管理线程及同步资源的现代化接口。

本文将全面系统地讲解 C++ 多线程编程,包括基础线程操作、数据共享、互斥锁、条件变量、异步任务、原子操作及并发容器等内容,并通过多个实战示例加深理解。

一、C++ 中的线程模型

1.1 什么是线程?

线程是操作系统能够调度的最小执行单元,是进程中的一个执行路径。一个进程可以包含多个线程,它们共享同一个内存空间,但每个线程有自己独立的栈。

1.2 C++ 的线程支持

从 C++11 开始,C++ 标准库提供了如下线程支持:

  • std::thread:创建与管理线程
  • std::mutex / std::lock_guard:互斥量与自动加锁
  • std::condition_variable:条件变量
  • std::future / std::async:异步任务
  • std::atomic:原子类型,支持无锁编程

二、线程的基本使用

2.1 创建线程

#include <iostream>
#include <thread>

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

int main() {
    std::thread t(hello);
    t.join(); // 等待线程结束
    return 0;
}

2.2 传递参数给线程

void greet(std::string name) {
    std::cout << "Hello, " << name << std::endl;
}

std::thread t(greet, "Alice");

注意:需要传引用时需用 std::ref 包装。

void modify(int& x) { x += 10; }
int value = 5;
std::thread t(modify, std::ref(value));

2.3 join 与 detach

  • join():等待线程完成,阻塞调用线程。
  • detach():分离线程,后台运行,生命周期由系统管理。

std::thread t(f);
t.detach(); // 不再受 main 控制,需确保线程中不使用已销毁资源

三、共享数据与互斥机制

3.1 多线程共享数据的问题

多个线程对同一资源进行读写操作时,可能导致数据竞争(race condition)。

int counter = 0;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        ++counter;
    }
}

多个线程同时修改 counter,结果不确定。

3.2 使用 mutex 加锁

#include <mutex>

std::mutex mtx;

void safe_increment() {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

3.3 lock_guard 与 unique_lock

  • std::lock_guard:简单的 RAII 锁管理
  • std::unique_lock:支持延迟锁定、手动解锁、条件变量使用

std::unique_lock<std::mutex> lock(mtx);
lock.unlock(); // 可手动释放

四、条件变量与线程通信

4.1 生产者消费者模型

#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        buffer.push(i);
        cv.notify_one();
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !buffer.empty(); });
        int item = buffer.front();
        buffer.pop();
        std::cout << "Consumed: " << item << std::endl;
    }
}

4.2 notify_one vs notify_all

  • notify_one():唤醒一个等待线程
  • notify_all():唤醒所有等待线程

五、异步编程与 future/promise

5.1 std::async

#include <future>

int compute() {
    return 42;
}

int main() {
    std::future<int> fut = std::async(compute);
    int result = fut.get(); // 阻塞直到返回值准备好
}

5.2 使用 promise 传递值

void worker(std::promise<int> p) {
    p.set_value(123);
}

std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t(worker, std::move(p));
int val = f.get();

六、原子操作与无锁编程

6.1 std::atomic 基本用法

#include <atomic>

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

void task() {
    for (int i = 0; i < 10000; ++i)
        acounter.fetch_add(1);
}

常用方法:

  • fetch_add, fetch_sub
  • compare_exchange_weak/strong
  • load, store

6.2 原子与内存序

std::memory_order 控制内存同步行为:

  • memory_order_relaxed:不保证同步
  • memory_order_acquire/release:适度同步
  • memory_order_seq_cst:最严格同步,默认

七、并发容器与线程安全 STL

7.1 为什么要并发容器?

STL 中的容器如 vectormap 等并不是线程安全的。

并发容器如:

  • concurrent_queue
  • concurrent_vector(TBB 或 boost)
  • std::atomic<T*> 实现的无锁链表等

7.2 自己实现线程安全队列

template <typename T>
class ThreadSafeQueue {
    std::queue<T> q;
    std::mutex mtx;
    std::condition_variable cv;

public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx);
        q.push(std::move(value));
        cv.notify_one();
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&]{ return !q.empty(); });
        T val = std::move(q.front());
        q.pop();
        return val;
    }
};

八、实战案例:并发网页下载器

8.1 设计目标

  • 支持多个 URL 同时下载
  • 下载结果保存到 map 中
  • 控制最大线程数量(线程池模型)

8.2 关键代码

#include <thread>
#include <map>
#include <mutex>
#include <vector>
#include <curl/curl.h>

std::mutex mtx;
std::map<std::string, std::string> result;

void download(const std::string& url) {
    CURL* curl = curl_easy_init();
    std::string data;
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
            [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
                std::string* s = static_cast<std::string*>(userdata);
                s->append(ptr, size * nmemb);
                return size * nmemb;
            });
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
        curl_easy_perform(curl);
        curl_easy_cleanup(curl);
    }
    std::lock_guard<std::mutex> lock(mtx);
    result[url] = data;
}

主程序并发控制:

std::vector<std::string> urls = {"http://a.com", "http://b.com"};
std::vector<std::thread> threads;

for (const auto& url : urls) {
    threads.emplace_back(download, url);
}
for (auto& t : threads) t.join();

九、C++20 中的并发增强

9.1 std::jthread

自动 join 的线程,避免遗忘 join()

std::jthread t([]{ std::cout << "run\n"; });

9.2 stop_token 与可中断线程

std::jthread t([](std::stop_token st) {
    while (!st.stop_requested()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
});
t.request_stop();

十、总结与最佳实践

10.1 总结要点

技术点

建议

线程创建

使用 std::threadstd::jthread

资源共享

std::mutex + lock_guard

同步控制

std::condition_variable

异步任务

std::async / promise / future

原子操作

使用 std::atomic

并发容器

自实现或使用库

线程数量控制

推荐封装线程池

10.2 最佳实践建议

  • 不要过度创建线程,推荐线程池
  • 多线程代码要尽可能保持无共享状态(状态隔离)
  • 善用 RAII + lock_guard 管理锁
  • 推荐使用 std::async 编写并行任务
  • 合理选择同步机制:mutex vs atomic vs condition_variable
举报

相关推荐

0 条评论