多线程开发场景基本是每一个C++开发工程师无法避免的场景,今天就带大家从零基础入门C++多线程编程,掌握其中的基础用法、锁管理工具和条件变量的内容
多线程的认识
多线程就是一个程序内部运行多个任务,每个任务就是一个线程,充分利用CPU的资源,提高效率的技术
在多个线程中,作为程序入口的线程称为主线程,由主线程创建负责独立执行细分任务的线程称为子线程
实现依赖
在C++中想使用多线程技术,就要引入头文件<thread>
cpp
#include <thread>
线程创建基本流程
首先要定义线程要执行的任务函数,然后要通过std::thread实例化线程对象并且绑定该任务函数,最后调用join()函数或者detach()函数
其中join()来阻塞主线程,等待子线程执行完毕;detach()来将主线程和子线程分离
代码示例如下
cpp
#include <iostream>
#include <thread>
// 子线程任务函数
void task(int num) {
cout << "子线程执行任务,传入参数:" << num << endl;
}
int main() {
// 实例化thread对象并绑定任务,传入参数5
std::thread t(task, 5);
// 阻塞主线程,等待子线程执行完成
t.join();
std::cout << "主线程继续执行" << std::endl;
return 0;
}
为什么要调用join(或detach()函数呢,如果不调用线程在对象析构时会触发程序异常终止
互斥锁
当多个线程访问同一块共享内存时,会出现数据读写混乱的情况,这就是竞态条件,为了解决这个问题,可以采用互斥锁std::mutex
std::mutex的核心接口是
lock(),unlock(),try_lock()
lock()给互斥锁加锁,若锁已被其他线程持有,则当前线程会阻塞,直到获取到锁,必须确保只有持unlock()是释放锁,确保只有持有锁的线程才能调用,try_lock()是尝试加锁,成功返回true,失败返回false,不会阻塞当前线程
代码示例
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义全局互斥锁
int shared_counter = 0; // 共享资源
// 子线程任务:对共享计数器进行累加
void increment_counter() {
for (int i = 0; i < 1000; i++) {
mtx.lock(); // 加锁,进入临界区
shared_counter++;
mtx.unlock(); // 解锁,退出临界区
}
}
int main() {
std::thread t1(increment_counter);
std::thread t2(increment_counter);
t1.join();
t2.join();
std::cout << "最终计数器值:" << shared_counter << std::endl; // 预期输出2000
return 0;
}
互斥锁虽然可以解决竞态条件问题,但需要需手动调用lock()和unlock(),若临界区代码抛出异常,会导致unlock()无法执行,进而引发死锁,而且多锁场景下,若加锁顺序不一致,极易出现死锁问题,操作难度太大,因而就有了让互斥锁更安全灵活的锁管理工具
锁管理工具
锁管理工具主要有三种:分别是守卫锁,唯一锁,作用域锁
std::lock_guard(守卫锁)
std::lock_guard是最轻量化的RAII 锁管理工具,构造时自动调用lock()加锁,析构时自动调用unlock()解锁
适用于简单的临界区资源保护,无需手动控制加解锁时机,以下是代码示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_num = 0;
void add_num() {
// 构造lock_guard时自动加锁
std::lock_guard<std::mutex> lock(mtx);
shared_num++;
std::cout << "当前共享变量值:" << shared_num << std::endl;
// 函数结束,lock_guard析构自动解锁,即使中途抛异常也能正常解锁
}
int main() {
std::thread t1(add_num);
std::thread t2(add_num);
t1.join();
t2.join();
return 0;
}
std::unique_lock(唯一锁)
std::unique_lock是最灵活的 RAII 锁管理工具,支持手动加解锁、超时等待、延迟加锁等操作,并且可配合条件变量使用
适用于复杂的锁控制场景,需要灵活调整加解锁时机,代码示例如下:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int count = 0;
void task() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁,构造时不自动加锁
// 手动加锁
lock.lock();
count += 5;
std::cout << "count更新为:" << count << std::endl;
// 手动解锁
lock.unlock();
// 可再次加锁
lock.lock();
count += 3;
std::cout << "count最终值:" << count << std::endl;
}
int main() {
std::thread t(task);
t.join();
return 0;
}
std::scoped_lock(作用域锁)
std::scoped_lock是专为多锁场景设计的RAII管理工具,能自动对传入的多个互斥锁排序加锁,从根源杜绝多锁死锁问题
适用于需要同时获取多个互斥锁的场景,代码示例如下:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
int data1 = 0, data2 = 0;
void update_data() {
// 自动对mtx1、mtx2排序加锁,避免死锁
std::scoped_lock lock(mtx1, mtx2);
data1++;
data2++;
std::cout << "data1=" << data1 << ",data2=" << data2 << std::endl;
}
int main() {
std::thread t1(update_data);
std::thread t2(update_data);
t1.join();
t2.join();
return 0;
}
条件变量
std::condition_variable(条件变量)是 C++ 多线程中实现线程阻塞等待的核心工具,它允许线程阻塞至特定条件满足后再被唤醒,避免了 "忙等" 带来的 CPU 资源浪费
核心特性
阻塞等待 :线程调用wait()后会释放持有的互斥锁,进入阻塞状态,直到被其他线程唤醒
唤醒机制 :通过notify_one()唤醒一个等待线程,或notify_all()唤醒所有等待线程
绑定互斥锁 :必须配合std::unique_lock<std::mutex>使用,无法直接搭配其他锁
虚假唤醒:即使未被主动唤醒,等待线程也可能被唤醒,因此需在循环中检查条件是否真的满足
适用于生产者 - 消费者模型:队列满或者空时让对应线程等待,线程池,多线程同步等待某个事件触发和超时等待场景等场景,代码示例如下:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> task_queue;
std::mutex mtx;
std::condition_variable cv;
// 生产者:往队列中添加任务
void producer() {
for (int i = 1; i <= 5; i++) {
std::unique_lock<std::mutex> lock(mtx);
task_queue.push(i);
std::cout << "生产者生产任务:" << i << std::endl;
lock.unlock();
// 唤醒一个消费者线程
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
// 消费者:从队列中取任务执行
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// 循环检查条件,避免虚假唤醒
cv.wait(lock, []{ return !task_queue.empty(); });
int task = task_queue.front();
task_queue.pop();
std::cout << "消费者执行任务:" << task << std::endl;
lock.unlock();
if (task == 5) break; // 任务执行完毕退出
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
总结
多线程是通过在程序中创建多个线程,并且结合互斥锁以及锁管理工具和条件变量来实现CPU利用效率的提高,在短时间内跑完多个任务的技术,是我们每个C++程序员绕不开的技术
觉得文章对您有帮助的话可以点赞关注,我将会持续分享高质量的内容~