C++ 线程和线程管理
1. C++ 线程和线程管理
C++11 引入了线程支持,可以通过 std::thread
创建和管理线程。以下是线程管理的关键概念:
1.1 创建线程
使用 std::thread
类来创建一个线程并启动该线程:
#include <iostream>
#include <thread>
void print_hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(print_hello); // 创建线程并执行print_hello函数
t.join(); // 主线程等待子线程执行完成
return 0;
}
t.join()
:会阻塞主线程,直到线程t
完成其任务。t.detach()
:会将线程与主线程分离,主线程不会等待线程的完成。
1.2 线程与主线程分离(detach()
)
std::thread t(print_hello);
t.detach(); // 线程将在后台运行,主线程不再等待它
- 分离线程意味着主线程无法再与之交互,且程序结束时,分离的线程会被系统自动清理。
- 如果分离的线程在主线程退出时仍然活跃,可能会造成资源泄露或访问未定义行为。
1.3 线程的生命周期
线程的生命周期由创建线程的 std::thread
对象管理。如果线程对象在析构时未调用 join()
或 detach()
,程序将抛出异常,提示线程没有正确管理。
2. 锁和互斥量
在多线程环境中,锁和互斥量用于同步线程访问共享资源,以避免数据竞态和死锁等问题。C++ 提供了多种锁和互斥量类型来应对不同的同步需求。
2.1 std::mutex
(互斥量)
std::mutex
是最常用的锁类型,确保同一时刻只有一个线程能访问共享资源。
std::mutex mtx;
void print_hello(int id) {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Thread " << id << " is working" << std::endl;
}
std::lock_guard
:提供 RAII 风格的锁管理,自动加锁和解锁。std::unique_lock
:功能更强大,支持手动解锁和锁定、延迟锁定等。
2.2 std::recursive_mutex
(递归互斥量)
std::recursive_mutex
允许同一线程多次加锁而不会死锁,适用于递归的场景。
std::recursive_mutex rmtx;
void recursive_print(int id, int count) {
std::lock_guard<std::recursive_mutex> lock(rmtx);
if (count > 0) {
std::cout << "Thread " << id << " printing recursively, count: " << count << std::endl;
recursive_print(id, count - 1);
}
}
- 适合需要递归操作的场景,例如递归函数中的锁保护。
2.3 std::shared_mutex
(共享互斥量)
std::shared_mutex
允许多个线程共享读取数据的锁,但写入数据时会独占访问权。
std::shared_mutex smtx;
void read_data(int id) {
std::shared_lock<std::shared_mutex> lock(smtx); // 共享锁
std::cout << "Thread " << id << " is reading data." << std::endl;
}
void write_data(int id) {
std::unique_lock<std::shared_mutex> lock(smtx); // 独占锁
std::cout << "Thread " << id << " is writing data." << std::endl;
}
std::shared_lock
:获取共享锁,多个线程可以同时读取资源。std::unique_lock
:获取独占锁,确保只有一个线程能够写入数据。
2.4 std::timed_mutex
和 std::try_lock
std::timed_mutex
允许线程尝试在指定时间内获取锁,而 std::try_lock
则允许线程立即返回尝试锁定结果。
std::timed_mutex tmtx;
void try_lock_example() {
if (tmtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Lock acquired successfully" << std::endl;
tmtx.unlock();
} else {
std::cout << "Lock attempt timed out" << std::endl;
}
}
try_lock_for()
:尝试在指定的时间内获取锁。try_lock()
:立即返回是否能够成功获取锁。
3. 线程同步:std::condition_variable
std::condition_variable
用于线程间的通知机制,通过它,线程可以在特定条件发生时被唤醒。主要的两种操作是 notify_one()
和 notify_all()
。
3.1 std::condition_variable
基本用法
std::mutex mtx;
std::condition_variable cv;
void wait_for_data() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready; }); // 等待条件满足
// 数据准备好后继续执行
}
wait()
:阻塞当前线程,直到条件变量被通知。notify_one()
:唤醒一个等待线程。notify_all()
:唤醒所有等待线程。
3.2 notify_one()
和 notify_all()
的区别:
notify_one()
:唤醒一个线程,适合只需要唤醒一个线程的场景。notify_all()
:唤醒所有等待的线程,适合所有线程都需要响应某个条件时。
void producer() {
{
std::lock_guard<std::mutex> lock(mtx);
data_ready = true;
}
cv.notify_all(); // 唤醒所有等待线程
}
void consumer(int id) {
std::unique_lock<std::mutex> lock(mtx);
while (!data_ready) cv.wait(lock); // 等待数据准备好
std::cout << "Consumer " << id << " is processing data" << std::endl;
}
4. 常见的锁类型总结
锁类型 | 适用场景 | 特点 |
---|---|---|
std::mutex | 基本的互斥锁,保护共享资源不被并发访问 | 不可递归,同一时间只有一个线程持有 |
std::recursive_mutex | 需要递归加锁的场景 | 允许同一线程多次加锁 |
std::shared_mutex | 读多写少的场景,如缓存系统、数据库读写操作等 | 读线程共享,写线程独占 |
std::timed_mutex | 需要超时控制的场景 | 可在一定时间内尝试获取锁 |
std::lock_guard | 用于简化锁的管理,RAII 风格的锁管理 | 自动加锁和解锁 |
std::unique_lock | 更灵活的锁管理,支持延迟锁定、解锁、重锁等 | 支持手动解锁与重新锁定 |
总结
- 线程创建与管理:C++11 提供了
std::thread
用于创建和管理线程,支持线程同步与并发操作。 - 锁与互斥量:通过
std::mutex
、std::recursive_mutex
、std::shared_mutex
、std::timed_mutex
等锁类型实现线程间资源同步,避免数据竞态。 - 条件变量同步:使用
std::condition_variable
实现线程间的通知和等待机制,notify_one()
和notify_all()
用于唤醒等待线程。 - RAII 风格的锁管理:使用
std::lock_guard
和std::unique_lock
来简化锁的管理,确保锁的自动释放。