多线程编程
锁类型
- 互斥锁
std::mutex mtx;
mtx.lock();
mtx.unlock();
- 递归锁
// 允许同一线程多次锁定同一个互斥锁,而不会导致死锁。
// 适合递归函数或需要多次锁定的场景。
std::recursive_mutex rmtx;
rmtx.lock();
rmtx.lock(); // 同一线程可以多次锁定
rmtx.unlock();
rmtx.unlock();
- 读写锁(C++17)
std::shared_mutex smtx;
smtx.lock_shared(); // 共享锁(读)
smtx.unlock_shared();
smtx.lock(); // 独占锁(写)
smtx.unlock();
- 定时锁
std::timed_mutex tmtx;
if (tmtx.try_lock_for(std::chrono::seconds(1))) {
// 加锁成功
tmtx.unlock();
}
- 无锁
std::atomic<int> counter{0};
counter.fetch_add(1); // 原子加操作
线程池设计: 如何设计一个高效的线程池?任务队列如何实现?
线程池的大小确定:
-
- CPU 密集型任务(N+1): 任务消耗主要是 CPU 资源, 将线程数设置 N(CPU 核心数) + 1, 比核心数多出一个线程是为了放置线程偶发的缺页中断, 或者其他原因的任务暂停带来的影响。一旦任务暂停, CPU 就会处于空闲状态,多出来的一个线程就可以充分利用 CPU 的空闲时间.
-
- I/O 密集型(2N): 系统大部分时间用来处理 I/O 交互, 而线程在处理 I/O 的时间段内不会占用 CPU 来处理, 因此可以设置线程池大小为 2N, 这样可以充分利用 CPU 的空闲时间, 提高系统的并发处理能力.
线程创建和销毁的开销主要原因:
- 内核资源分配: 每个线程需要分配内核栈, 线程控制块,涉及系统调用.
- 内存分配: 线程栈空间(1~2MB/线程)
- 调度和同步: 线程创建和销毁时, 操作系统需要维护调度队列和同步机制
- 上下文切换: 频繁创建/销毁会导致更多的上下文切换.
多线程通信的方式
- 共享内存, 需要同步机制保护
// 多个线程共享同一内存区域, 通过互斥锁(mutex)保护数据一致性
#include <iostream>
#include <thread>
#include <mutex>
int shared_data = 0; // 共享内存
std::mutex mtx; // 互斥锁
void writer() {
std::lock_guard<std::mutex> lock(mtx);
shared_data = 42; // 写共享数据
std::cout << "Writer set shared_data = 42" << std::endl;
}
void reader() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Reader get shared_data = " << shared_data << std::endl;
}
int main() {
std::thread t1(writer);
std::thread t2(reader);
t1.join();
t2.join();
return 0;
}
- 条件变量, 用于线程间的等待和通知
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
int shared_data = 0;
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void producer() {
std::unique_lock<std::mutex> lock(mtx);
shared_data = 42;
ready = true;
cv.notify_one(); // 通知等待的线程
std::cout << "Producer set shared_data = 42" << std::endl;
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件成立
std::cout << "Consumer get shared_data = " << shared_data << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
- 无锁的轻量级通信
#include <atomic>
std::atomic<int> counter{0};
std::atomic<bool> ready{false};
// 线程1
void producer() {
counter.store(42);
ready.store(true);
}
// 线程2
void consumer() {
while (!ready.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
int value = counter.load();
}
- 信号量
#include <semaphore>
std::counting_semaphore<10> sem(3); // 最多3个线程同时访问
void worker() {
sem.acquire(); // 获取信号量
// 执行工作
sem.release(); // 释放信号量
}
- 异步结果传递
#include <future>
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 生产者线程
void producer() {
int result = 42;
prom.set_value(result);
}
// 消费者线程
void consumer() {
int value = fut.get(); // 阻塞等待结果
}
多线程造成的崩溃问题
不等待线程结束, 直接销毁对象, 可能会导致下面的问题
- 访问已释放的内存: 工作线程还在访问变量
- 未定义行为: 线程还在运行时销毁对象会导致程序崩溃
- 访问已经释放的内存:
class DataProcessor {
public:
int data = 42;
void process() {
while(running) {
std::cout << data << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
private:
std::atomic<bool> running{true};
}
void dangerous_usage() {
DataProcessor* processor = new DataProcessor();
std::thread worker(&DataProcessor::process, processor);
delete processor; // 危险!线程还在运行
// worker.join(); // 忘记等待线程结束
}
// 解决办法, 基于类 RAII 机制
class SafeDataProcessor {
public:
SafeDataProcessor() : running(true) {}
~SafeDataProcessor() {
stop(); // 析构时确保线程停止
}
void start() {
worker_thread = std::thread(&SafeDataProcessor::process, this);
}
void stop() {
running = false;
if (worker_thread.joinable()) {
worker_thread.join(); // 等待线程结束
}
}
private:
void process() {
while (running) {
std::cout << data << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int data = 42;
std::atomic<bool> running;
std::thread worker_thread;
};
- 栈溢出
// 线程栈空间不足, 递归调用或大型局部变量时
Cpp 中如何判断代码在那个线程中执行