👻 概念
- 线程 —— 程序中的轻量级执行单元,允许程序同时执行多个任务。
- 多线程编程 —— 在一个程序中创建和管理多个并发执行的线程
多任务处理允许让电脑同时运行两个或两个以上的程序,分为两类:基于进程和基于线程。
- 基于进程的多任务处理是程序的并发执行
- 基于线程的多任务处理是同一程序的片段的并发执行
👻 多线程
👾准备工作
使用信号处理需要包含 <thread>
头文件
#include <thread>
👾创建线程
👽语法格式
thread thread_object(callable, args...);
- 参数
callable
—— 可调用对象,可以是函数指针、函数对象、Lambda 表达式等 - 参数
args...
—— 传递给callable
的参数列表
👽示例代码
-
示例1:使用函数指针
void printMessage(int count) { for (int i=0; i<count; ++i) cout << "Hello from thread (function pointer)!\n"; } ... thread t1(printMessage, 5); // 创建线程,传递函数指针和参数 t1.join(); // 等待线程完成
Hello from thread (function pointer)! Hello from thread (function pointer)! Hello from thread (function pointer)!
-
示例2:使用函数对象
class PrintTask { public: void operator()(int count) const{ for (int i=0; i<count; ++i) std::cout << "Hello from thread (function object)!\n"; } }; ... thread t2(PrintTask(), 5); // 创建线程,传递函数对象和参数 t2.join(); // 等待线程完成
Hello from thread (function object)! Hello from thread (function object)! Hello from thread (function object)!
-
示例3:使用Lambda表达式
// 创建线程,传递 Lambda 表达式和参数 thread t3([](int count) { for (int i=0; i<count; ++i) std::cout << "Hello from thread (lambda)!\n"; }, 5); t3.join(); // 等待线程完成
Hello from thread (lambda)! Hello from thread (lambda)! Hello from thread (lambda)!
👾线程管理
👽等待线程完成
join()
是 std::thread
类的一个成员函数,用于等待线程完成执行。
- 等待线程完成: 阻塞调用线程(通常是主线程),直到被等待的线程完成其执行。
- 同步线程: 确保线程按预期顺序完成,避免主线程在子线程完成之前退出程序。
thread_object.join();
👽分离线程
detach()
是 std::thread
类的一个成员函数,将线程与主线程分离,线程在后台独立运行,主线程不再需要等待它
thread_object.detach();
👾线程传参
👽值传递
传递值参数时,可以直接传递
int arg1 = 0;
thread thread_oject(func, arg1);
👽引用传递
传递引用参数时,需要使用 ref()
函数
int arg1 = 0;
thread thread_oject(func, ref(arg1));
方法原型
ref()
函数用于创建一个引用包装器(reference wrapper),允许将引用传递给函数或模板,而不导致对象的复制或移动。template <class T> reference_wrapper<T> ref(T& t);
- 参数
T& t
—— 要包装的引用。- 返回值
reference_wrapper<T>
—— 包装传入的引用作用
避免复制: 在需要将引用传递给函数或模板时,
std::ref
可以避免对象的复制或移动,从而提高效率。线程安全: 在多线程编程中,
std::ref
可以确保线程函数能够正确地操作原始对象,而不是其副本。
👻 同步互斥
👾互斥量
👽概念
-
互斥量是一种同步原语,用于防止多个线程同时访问共享资源。
-
当线程访问资源时,先锁定
lock
互斥量。若互斥量已被锁定,请求锁定的线程将被阻塞,直到互斥量被解锁unlock
👽准备工作
使用互斥量需要包含 <mutex>
头文件:
#include <mutex>
👽语法格式
使用类 mutex
作为互斥量,保护共享资源,防止线程竞争
-
使用类方法
mutex.lock()
锁定互斥锁 -
使用类方法
mutex.unlock()
释放互斥锁mutex.lock(); // 锁定互斥锁 // 访问共享资源 mutex.unlock(); // 释放互斥锁
👽示例代码
mutex mtx; // 全局互斥量
void safeFunction() {
mtx.lock(); // 请求锁定互斥量
// 访问或修改共享资源
mtx.unlock(); // 释放互斥量
}
...
thread t1(safeFunction);
thread t2(safeFunction);
t1.join();
t2.join();
👾锁管理器
👽语法格式
std::lock_guard
作用域锁管理器
-
类
lock_guard
用于自动管理互斥锁的获取和释放,使用RAII
(Resource Acquisition Is Initialization
,资源获取即初始化)机制。类构造时自动锁定互斥量,析构时自动解锁。lock_guard<mutex> lock_guard_name(Mutex& m);
std::unique_lock
作用域锁管理器
-
类
unique_lock
比类lock_guard
更灵活,提供更多的控制选项,例如手动锁定和解锁、尝试锁定、定时锁定等unique_lock<mutex> unique_lock_name(Mutex& m);
👽示例代码
mutex mtx; // 全局互斥量
void safeFunctionWithLockGuard() {
lock_guard<mutex> lk(mtx);
// 访问或修改共享资源
}
void safeFunctionWithUniqueLock() {
unique_lock<mutex> ul(mtx);
// 访问或修改共享资源
// ul.unlock(); // 可选:手动解锁
}
thread t1(safeFunctionWithLockGuard);
thread t2(safeFunctionWithUniqueLock);
t1.join();
t2.join();
👾条件变量
👽概念
- 条件变量用于线程间的协调,允许一个或多个线程等待某个条件的发生。
- 通常与互斥量使用,以实现线程间的同步
👽准备工作
使用条件变量需要包含 <condition_variable>
头文件:
#include <condition_variable>
👽语法格式
使用类 condition_variable
作为条件变量,实现线程间的等待和通知机制
-
使用类方法
condition_variable.wait()
使线程在某个条件满足之前进入等待状态template <class Predicate> void wait(unique_lock<mutex>& lock, Predicate pred);
- 参数
lock
——unique_lock<mutex>
对象,用于管理互斥锁 - 参数
pred
—— 谓词函数,用于检查条件是否满足。其必须返回一个布尔值,表示条件是否满足。wait()
不断调用谓词函数,直到其返回true
- 参数
-
使用类方法
condition_variable.notify_one()
唤醒一个正在等待条件变量的线程,使其继续执行void notify_one();
👽示例代码
-
示例1
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> using namespace std; mutex mtx; // 全局互斥锁 condition_variable cv; // 全局条件变量 bool ready = false; // 共享标志 void workerThread() { unique_lock<mutex> lock(mtx); cv.wait(lock, [] { return ready; }); // 等待条件变量,直到 ready 为 true cout << "Worker thread: ready flag is set, continuing execution." << endl; } void mainThread() { { unique_lock<mutex> lock(mtx); ready = true; // 设置 ready 标志 } // 离开作用域时解锁 cv.notify_one(); // 通知一个等待的线程 } ... thread t1(workerThread); thread t2(mainThread); t1.join(); t2.join();
Worker thread: ready flag is set, continuing execution.
-
示例2:生产者—消费者问题
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> using namespace std; mutex mtx; // 全局互斥锁 condition_variable cv; // 全局条件变量 queue<int> queue; // 共享队列 const int kMaxQueueSize = 3; // 队列最大长度 void producer() { for (int i=0; i<5; ++i) { unique_lock<mutex> lock(mtx); cv.wait(lock, [] { return queue.size() < kMaxQueueSize; }); queue.push(i); cout << "Produced: " << i << endl; lock.unlock(); cv.notify_one(); } } void consumer() { for (int i=0; i<5; ++i) { unique_lock<mutex> lock(mtx); cv.wait(lock, [] { return !queue.empty(); }); cout << "Consumed: " << queue.front() << endl; queue.pop(); lock.unlock(); cv.notify_one(); } } int main() { thread t1(producer); thread t2(consumer); t1.join(); t2.join(); return 0; }
Produced: 0 Consumed: 0 Produced: 1 Consumed: 1 Produced: 2 Consumed: 2 Produced: 3 Consumed: 3 Produced: 4 Consumed: 4
👾原子操作
👽概念
- 原子操作确保对共享数据的访问是不可分割的。
- 即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。
👽准备工作
使用条件变量需要包含 <atomic>
头文件:
#include <atomic>
👽语法格式
使用类 atomic
实现线程间的原子操作。
-
使用构造函数定义初始化原子变量
atomic<T> atomic_name(T desired)
-
使用类方法
atomic.fetch_add()
对原子变量进行加法操作T fetch_add(T arg, memory_order order = std::memory_order_seq_cst) noexcept;
- 参数 arg —— 要加到原子变量的值
- 参数 order —— 枚举类型,用于指定原子操作的内存顺序语义。默认值是
std::memory_order_seq_cst
,常见的值包括:
枚举值 含义 std::memory_order_relaxed
最弱的内存顺序,不保证任何内存顺序 std::memory_order_acquire
用于读操作,确保在该操作之后的读写操作不会被重排序到该操作之前 std::memory_order_release
用于写操作,确保在该操作之前的读写操作不会被重排序到该操作之后 std::memory_order_acq_rel
用于读写操作,结合 std::memory_order_acquire
和std::memory_order_release
语义std::memory_order_seq_cst
最强的内存顺序,确保所有操作都按照程序顺序执行,且所有线程看到的操作顺序一致
👽示例代码
#include <atomic>
#include <thread>
using namespace std;
atomic<int> count(0);
void increment() {
count.fetch_add(1, memory_order_relaxed);
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
return count; // 返回 2
}
👾线程局部存储
👽概念
- 线程局部存储允许每个线程拥有自己的数据副本
👽语法格式
使用关键字 thread_local
实现数据副本,避免了对共享资源的争用
thread_local value_type value_name;
👽示例代码
#include <iostream>
#include <thread>
thread_local int threadData = 0;
void threadFunction() {
threadData = 5; // 每个线程都有自己的threadData副本
std::cout << "Thread data: " << threadData << std::endl;
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
👾死锁和避免策略
👽概念
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
- 总是以相同的顺序请求资源。
- 使用超时来尝试获取资源。
- 使用死锁检测算法。