C++: 多线程同步

最后更新:2026-08-26

第43课我们学了多线程基础。

现在,我们要深入多线程同步——让多个线程协调工作,避免竞争和死锁。

多线程编程的难点不在于创建线程,而在于同步


1. 死锁

(1) 1.1 什么是死锁?

死锁(Deadlock)是指两个或多个线程互相等待对方释放资源,导致谁也无法继续。

四个必要条件:

  1. 互斥:资源不能共享
  2. 持有并等待:持有资源的线程等待其他资源
  3. 不可抢占:资源不能被强制释放
  4. 循环等待:存在线程的循环等待链

(2) 1.2 死锁示例

示例:死锁(难度⭐⭐⭐)

▶ 示例 2:多线程编程演示(难度⭐)

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

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
 std::cout << "Thread 1 done" << std::endl;
}

void thread2() {
 std::lock_guardstd::mutex lock2(mtx2);
 std::this_thread::sleep_for(std::chrono::milliseconds(100));
 std::lock_guardstd::mutex lock1(mtx1); // 等待mtx1
 std::cout << "Thread 2 done" << std::endl;
}

int main() {
 std::thread t1(thread1);
 std::thread t2(thread2);
 
 t1.join();
 t2.join();
 
 return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
Thread 1 done
Thread 2 done

💡 提示:


(3) 1.3 避免死锁

方法1:固定加锁顺序

CPP
void thread1() {
 std::lock_guardstd::mutex lock1(mtx1);
 std::lock_guardstd::mutex lock2(mtx2); // 总是先mtx1,再mtx2
 std::cout << "Thread 1 done" << std::endl;
}

void thread2() {
 std::lock_guardstd::mutex lock1(mtx1); // 和thread1一样的顺序
 std::lock_guardstd::mutex lock2(mtx2);
 std::cout << "Thread 2 done" << std::endl;
}

方法2:用std::lock同时锁多个互斥量

CPP
void safe_function() {
 std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);
 std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);
 std::lock(lock1, lock2); // 同时加锁,避免死锁
 
 // ...
}

方法3:用std::scoped_lock(C++17,推荐)

CPP
void safe_function() {
 std::scoped_lock lock(mtx1, mtx2); // 自动避免死锁
 
 // ...
}


2. 读写锁

(1) 2.1 为什么需要读写锁?

问题: 互斥量太"保守"——即使多个线程只是读取数据,也要排队。

解决方案: 读写锁(std::shared_mutex,C++17)

锁类型 功能
独占锁 写操作,其他线程不能读写
共享锁 读操作,多个线程可以同时读

(2) 2.2 示例:用读写锁保护缓存(难度⭐⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <unordered_map>

std::unordered_map<int, int> cache;
std::shared_mutex smtx;

int get(int key) {
 std::shared_lock<std::shared_mutex> lock(smtx); // 共享锁(读)
 auto it = cache.find(key);
 if (it != cache.end()) {
 return it->second;
 }
 return -1;
}

void set(int key, int value) {
 std::unique_lock<std::shared_mutex> lock(smtx); // 独占锁(写)
 cache[key] = value;
}

int main() {
 // 多个线程可以同时读,写的时候排他
 return 0;
}

输出:

TEXT 📖 仅展示
(程序输出)


3. 原子操作

(1) 3.1 什么是原子操作?

原子操作(Atomic Operation)是不可分割的操作——要么全部执行,要么都不执行。

优势: 无需加锁,性能高。


(2) 3.2 std::atomic

C++11提供了 std::atomic 模板。

示例:原子计数器(难度⭐⭐)

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

std::atomic<int> counter(0); // 原子变量

void increment() {
 for (int i = 0; i < 1000; i++) {
 counter++; // 原子操作,无需加锁
 }
}

int main() {
 std::vector<std::thread> threads;
 for (int i = 0; i < 10; i++) {
 threads.emplace_back(increment);
 }
 
 for (auto& t : threads) {
 t.join();
 }
 
 std::cout << "Counter: " << counter << std::endl; // 一定是10000
 return 0;
}

(3) 3.3 内存顺序

std::atomic 支持内存顺序(Memory Order),用于控制同步范围。

常用内存顺序:



4. 线程池

(1) 4.1 为什么需要线程池?

问题: 频繁创建/销毁线程开销大

解决方案: 线程池——预先创建一组线程,复用它们。


(2) 4.2 简单线程池实现

示例:线程池(难度⭐⭐⭐⭐)

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

class ThreadPool {
private:
 std::vector<std::thread> workers;
 std::queue<std::function<void()>> tasks;
 std::mutex mtx;
 std::condition_variable cv;
 bool stop;

public:
 ThreadPool(size_t numThreads) : stop(false) {
 for (size_t i = 0; i < numThreads; i++) {
 workers.emplace_back([this] {
 while (true) {
 std::function<void()> task;
 {
 std::unique_lock<std::mutex> lock(mtx);
 cv.wait(lock, [this] {
 return stop || !tasks.empty();
 });
 
 if (stop && tasks.empty()) {
 return;
 }
 
 task = std::move(tasks.front());
 tasks.pop();
 }
 task();
 }
 });
 }
 }
 
 ~ThreadPool() {
 {
 std::lock_guard<std::mutex> lock(mtx);
 stop = true;
 }
 cv.notify_all();
 for (auto& t : workers) {
 t.join();
 }
 }
 
 template<class F>
 void enqueue(F&& f) {
 {
 std::lock_guardstd::mutex lock(mtx);
 tasks.emplace(std::forward<F>(f));
 }
 cv.notify_one();
 }
};

int main() {
 ThreadPool pool(4); // 创建4个线程
 
 for (int i = 0; i < 10; i++) {
 pool.enqueue([i] {
 std::cout << "Task " << i << " running" << std::endl;
 });
 }
 
 return 0;
}


5. 实战:并行排序

▶ 示例 1:用多线程加速排序(难度⭐⭐⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <thread>
#include <algorithm>

void parallelSort(std::vector<int>& v, int left, int right) {
 if (left >= right) return;
 
 std::sort(v.begin() + left, v.begin() + right + 1);
}

int main() {
 std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
 
 // 用2个线程并行排序
 std::thread t1(parallelSort, std::ref(v), 0, v.size()/2);
 std::thread t2(parallelSort, std::ref(v), v.size()/2 + 1, v.size()-1);
 
 t1.join();
 t2.join();
 
 // 合并两个有序部分
 std::inplace_merge(v.begin(), v.begin() + v.size()/2 + 1, v.end());
 
 for (int x : v) {
 std::cout << x << " ";
 }
 std::cout << std::endl;
 
 return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
3 1 4 1 5 9 2 6

▶ 示例 3:用 mutex 保护共享计数器(难度⭐)

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

int counter = 0;
std::mutex mtx;

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

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(increment);
    }

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

    std::cout << "counter = " << counter << std::endl;
    return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
counter = 10000

❓ 常见问题

Q 多线程一定更快吗?
A 不一定。如果任务太小,线程创建和同步的开销可能超过收益。

Q 怎么调试多线程程序?
A - 用 std::cout 打印日志(但要加互斥量) - 用Valgrind、Helgrind等工具检测数据竞争 - 用Thread Sanitizer(GCC/Clang)

Q C++20有什么新特性?
A - std::jthread(自动join) - 信号量(std::counting_semaphore) - 闩锁(std::latch) - 屏障(std::barrier

📖 小节

知识点 要点
死锁 四个条件,三种避免方法
读写锁 std::shared_mutex(C++17)
原子操作 std::atomic,无需加锁
线程池 复用线程,减少开销
并行算法 C++17 std::execution

📝 作业

  1. **基础题 (Difficulty ⭐):创建两个线程同时对一个全局变量做 100000 次自增,观察结果是否正确。然后用 std::mutex 保护后重新测试。

  2. **进阶题 (Difficulty ⭐⭐):用 std::lock_guard 保护一个"账户余额"变量,模拟两个线程同时存款/取款操作,确保最终余额正确。

  3. **挑战题 (Difficulty ⭐⭐⭐):用 std::condition_variable 实现一个"生产者-消费者"模型。一个线程生产数据放入队列,另一个线程消费数据。队列为空时消费者等待。



Phase 6 结束!下一步:Phase 7(进阶与实战)

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏