C/C++ 线程高级使用方法全解析:原理、技巧与实战
前言
线程技术是现代软件开发中不可或缺的一部分,尤其在需要高性能、高响应的场景中,线程的合理使用能显著提升程序效率。然而,C/C++ 提供的线程能力虽然强大,但对开发者的要求也更高——内存管理、同步机制、死锁规避、并发设计等均需细致考虑。
本文将带你从 C++11 线程库切入,逐步扩展到线程池、自定义调度器、RAII封装、条件变量优化、原子操作、多线程日志等多个实战领域。适合中高级开发者参考。
目录
1. 线程的基本概念与模型
1.1 什么是线程?
线程是操作系统调度的最小单位,同一进程的多个线程共享地址空间、文件描述符、堆等资源。相比进程,线程切换代价更低。
1.2 用户线程 vs 内核线程
- 用户线程(green threads):由用户空间调度,速度快,但无法充分利用多核。
- 内核线程(kernel threads):由内核管理,支持真正的并行执行。
1.3 多线程模型分类
- 一对一:每个用户线程映射一个内核线程。
- 多对一:多个用户线程映射一个内核线程。
- 多对多:多个用户线程映射多个内核线程(如 Windows 的 I/O Completion Ports)。
2. C++11 的 std::thread
基础用法
#include <thread>
#include <iostream>
void task(int x) {
std::cout << "Thread task with x = " << x << std::endl;
}
int main() {
std::thread t(task, 42);
t.join(); // 等待线程结束
}
2.1 join()
vs detach()
join()
:阻塞主线程直到子线程完成。detach()
:后台执行,主线程不能再控制它(需要注意生命周期问题)。
3. 高级线程同步机制
3.1 std::mutex
的使用与死锁防范
std::mutex mtx;
void safe_print() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Thread-safe print\n";
}
3.2 std::condition_variable
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
std::cout << "Worker is running\n";
}
3.3 模拟信号量
class Semaphore {
private:
std::mutex mtx;
std::condition_variable cv;
int count;
public:
explicit Semaphore(int init = 0) : count(init) {}
void signal() {
std::lock_guard<std::mutex> lock(mtx);
++count;
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return count > 0; });
--count;
}
};
4. RAII 封装与线程安全资源管理
class ScopedLock {
public:
ScopedLock(std::mutex& m) : mtx(m) {
mtx.lock();
}
~ScopedLock() {
mtx.unlock();
}
private:
std::mutex& mtx;
};
5. 线程池设计与实现
class ThreadPool {
public:
ThreadPool(size_t threads) {
for(size_t i = 0; i < threads; ++i)
workers.emplace_back([this] {
for(;;) {
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::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (auto &t : workers)
t.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop = false;
};
6. 协程与线程的对比与协作
特性 | 线程 | 协程 |
---|---|---|
切换方式 | 内核调度 | 用户态调度 |
切换代价 | 较高 | 极低 |
并行性 | 可并发执行(多核) | 通常为协作式单核并发 |
使用场景 | CPU密集 | IO密集、并发任务调度 |
7. 多线程日志系统设计
void log_writer_thread() {
while (running) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !queue.empty(); });
while (!queue.empty()) {
auto msg = std::move(queue.front());
queue.pop();
output_to_file(msg);
}
}
}
8. 死锁问题及解决策略
8.1 死锁的四个必要条件
- 互斥
- 占有且等待
- 不可剥夺
- 循环等待
8.2 常见解决策略
- 使用锁顺序策略
- 使用
std::lock()
防止死锁 - 封装锁操作,避免散乱的 lock/unlock
9. CPU亲和性绑定与 NUMA 优化
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
10. 实战案例:构建高性能异步任务系统
std::future<int> result = pool.enqueue([] {
return heavy_computation();
});
int value = result.get();
11. 小结与最佳实践
常见陷阱
- 使用已销毁的线程
- 忘记 join
- 数据竞争
- 死锁
实践建议
- RAII 封装线程资源
- 原子操作替代锁(如计数器)
- 使用 ThreadSanitizer 检查竞态
结语
C/C++ 提供了强大而灵活的多线程能力。掌握其高级用法,能够让你构建出更加高效、稳定的多线程系统。希望本文能成为你在并发世界中的一份参考手册。