在多线程编程中,C++多线程同步与互斥用于控制多个线程对共享资源的访问,以避免数据竞争、死锁等问题。可先参考之前博客 C++多线程运行整理
1. 互斥量(Mutex)
互斥量是一种同步原语,用于防止多个线程同时访问共享资源。当一个线程需要访问共享资源时,它首先需要锁定(lock)互斥量。如果互斥量已经被其他线程锁定,那么请求锁定的线程将被阻塞,直到互斥量被解锁(unlock)。
std::mutex:用于保护共享资源,防止数据竞争。
std::lock_guard 和 std::unique_lock:自动管理锁的获取和释放
python
/*
线程管理
join() 用于等待线程完成执行,如果不调用join() 或detach(),而直接销毁线程对象,会导致系统崩溃.
detach() 将线程与主线程分离,线程在后台独立运行,主线程不再等待它.
线程同步与互斥
1.互斥量 Muter
如果互斥量已经被其他线程锁定,那么请求锁定的线程将被阻塞,直到互斥量被解锁(unlock)
std::mutex:用于保护共享资源,防止数据竞争
std::lock_guard 和 std::unique_lock:自动管理锁的获取和释放。
2.锁 Locks
多种锁类型,用于简化互斥量的使用和管理
std::lock_guard 作用域锁,当构建时自动锁定互斥量,当析构时自动解锁
std::unique_lock 与std::lock_guard 类型,但是提供更多的灵活性(如可转移所有权和手动解锁)
3.条件变量 Condition Variable
条件变量用于 线程之间的协调,允许一个或多个线程等待某个条件的发生.通常与互斥量一起使用,以实现线程同步.
std::condition_variable 用于实现线程间的等待和通知机制
4. 原子操作(Atomic Operations)
原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。
5. 线程局部存储(Thread Local Storage, TLS)
线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。
6. 死锁(Deadlock)和避免策略
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
a.总是以相同的顺序请求资源。
b.使用超时来尝试获取资源。
c.使用死锁检测算法。
*/
#include <mutex>
#include <iostream>
#include <thread>
std::mutex mtx;//全局互斥量
void sateFunction(){
mtx.lock(); //锁定互斥锁
//访问或者修改共享资源
mtx.unlock(); //释放互斥所
}
int main()
{
std::thread t1(sateFunction);
std::thread t2(sateFunction);
t1.join();
t2.join();
return 0;
}
python
//多线程_互斥量
#include <iostream> // for std::cout, std::endl
#include <mutex> // for std::mutex, std::lock_guard
#include <thread> // for std::thread, std::this_thread::sleep_for
// 全局互斥锁,保护共享资源(如 std::cout)
std::mutex mtx;
// 全局共享资源
int shared_counter = 0;
/**
* @brief 安全的线程函数(使用 RAII 的 lock_guard)
* 推荐做法:使用 lock_guard,自动加锁/解锁
*/
void safeFunction() {
// 使用 RAII:lock_guard 在构造时自动加锁,析构时自动解锁
std::lock_guard<std::mutex> lk(mtx);
// 此处可以安全访问或修改共享资源
++shared_counter;
std::cout << " 线程 " << std::this_thread::get_id()
<< " 访问共享资源,当前计数: " << shared_counter << std::endl;
// 模拟一些工作耗时
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
int main() {
std::cout << " 主线程开始执行 (ID: " << std::this_thread::get_id() << ")\n\n";
// 创建两个线程
std::thread t1(safeFunction);
std::thread t2(safeFunction);
// 等待两个线程完成
t1.join();
t2.join();
std::cout << "\n 所有线程执行完毕,主线程退出。\n";
std::cout << " 最终共享计数: " << shared_counter << std::endl;
return 0;
}
2. 锁(Locks)
C++提供了多种锁类型,用于简化互斥量的使用和管理。
常见的锁类型包括:
• std::lock_guard:作用域锁,当构造时自动锁定互斥量,当析构时自动解锁。
• std::unique_lock:与std::lock_guard类似,但提供了更多的灵活性,例如可以转移所有权和手动解锁。
python
/*
线程管理
join() 用于等待线程完成执行,如果不调用join() 或detach(),而直接销毁线程对象,会导致系统崩溃.
detach() 将线程与主线程分离,线程在后台独立运行,主线程不再等待它.
线程同步与互斥
2.锁 Locks
多种锁类型,用于简化互斥量的使用和管理
std::lock_guard 作用域锁,当构建时自动锁定互斥量,当析构时自动解锁
std::unique_lock 与std::lock_guard 类型,但是提供更多的灵活性(如可转移所有权和手动解锁)
*/
// #include <mutex>
// #include <thread>
// std::mutex mtx;
// void safeFunctionWithLockGuard()
// {
// std::lock_guard<std::mutex> lk(mtx);
// // 访问或修改共享资源
// }
// void safeFunctionWithUniqueLock(){
// std::unique_lock<std::mutex> ul(mtx);
// //访问或修改共享资源
// ul.unlock(); //可选:手动解锁
// //...
// }
// int main()
// {
// return 0;
// }
#include <iostream> // for std::cout, std::endl
#include <mutex> // for std::mutex, std::lock_guard, std::unique_lock
#include <thread> // for std::thread, std::this_thread::sleep_for
#include <chrono> // for std::chrono::milliseconds
// 全局互斥锁,保护共享资源
std::mutex mtx;
/**
* @brief 使用 lock_guard 的安全函数
* lock_guard 是 RAII 风格的互斥锁,构造时自动加锁,
* 析构时自动解锁,防止忘记解锁导致死锁。
*/
void safeFunctionWithLockGuard() {
// 构造 std::lock_guard 时自动调用 mtx.lock()
std::lock_guard<std::mutex> lk(mtx);
// 此处可以安全访问或修改共享资源
std::cout << " [lock_guard] 线程 " << std::this_thread::get_id()
<< " 正在访问共享资源...\n";
// 模拟一些工作耗时(可选)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
/**
* @brief 使用 unique_lock 的安全函数
* unique_lock 比 lock_guard 更灵活,支持:
* - 手动解锁(unlock())
* - 手动加锁(lock())
* - 移动所有权(move-constructible)
* - 与条件变量配合使用(cv.wait())
*/
void safeFunctionWithUniqueLock() {
// 构造 unique_lock 时自动加锁
std::unique_lock<std::mutex> ul(mtx);
std::cout << " [unique_lock] 线程 " << std::this_thread::get_id()
<< " 正在访问共享资源...\n";
// 模拟工作耗时
std::this_thread::sleep_for(std::chrono::milliseconds(150));
// 可选:手动解锁(例如:想让其他线程短暂访问)
ul.unlock();
std::cout << " [unique_lock] 手动解锁,允许其他线程访问\n";
// 假设在此期间有其他线程尝试访问(可观察输出顺序)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// 再次加锁(恢复保护)
ul.lock();
std::cout << " [unique_lock] 重新加锁,继续操作\n";
// 作用域结束,析构时自动解锁(即使未显式调用 unlock)
std::cout << " [unique_lock] 线程 " << std::this_thread::get_id()
<< " 完成操作,自动解锁\n";
}
int main() {
std::cout << " 主线程开始执行 (ID: " << std::this_thread::get_id() << ")\n\n";
// 创建两个线程,分别调用不同锁函数
std::thread t1(safeFunctionWithLockGuard);
std::thread t2(safeFunctionWithUniqueLock);
// 等待两个线程完成
t1.join();
t2.join();
std::cout << "\n 所有线程执行完毕,主线程退出。\n";
return 0;
}
python
//多线程_锁(生产者消费者)
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>
// 共享缓冲区(队列)
std::queue<int> buffer;
std::mutex mtx; // 保护队列的互斥锁
std::condition_variable not_empty; // 条件变量:当队列非空时通知消费者
std::condition_variable not_full; // 条件变量:当队列不满时通知生产者
// 缓冲区最大容量
const int MAX_SIZE = 5;
bool done = false; // 标志:生产者是否完成
/**
* @brief 生产者线程函数
* 模拟生产数据并放入缓冲区
*/
void producer(int id) {
for (int i = 1; i <= 10; ++i) {
// 模拟生产耗时
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// 1. 加锁,准备向队列中放入数据
std::unique_lock<std::mutex> lk(mtx);
// 2. 等待队列未满(避免缓冲区溢出)
not_full.wait(lk, []{ return buffer.size() < MAX_SIZE; });
// 3. 放入数据
buffer.push(i);
std::cout << "生产者 " << id << " 生产了数据: " << i
<< " (当前队列大小: " << buffer.size() << ")\n";
// 4. 通知消费者:队列中有数据了
not_empty.notify_one();
}
// 生产结束,通知所有消费者
{
std::lock_guard<std::mutex> lg(mtx);
done = true;
}
not_empty.notify_all(); // 唤醒所有等待的消费者
}
/**
* @brief 消费者线程函数
* 从缓冲区取出数据并处理
*/
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lk(mtx);
// 1. 等待队列非空,或生产者已结束
not_empty.wait(lk, []{
return !buffer.empty() || done;
});
// 2. 如果队列为空且生产者已完成,则退出
if (buffer.empty() && done) {
std::cout << "消费者 " << id << " 收到结束信号,停止工作。\n";
break;
}
// 3. 取出数据
int data = buffer.front();
buffer.pop();
std::cout << "消费者 " << id << " 消费了数据: " << data
<< " (剩余队列大小: " << buffer.size() << ")\n";
// 4. 通知生产者:队列有空位了
not_full.notify_one();
}
}
int main() {
std::cout << "=== 生产者-消费者模型开始运行 ===\n";
std::cout << "缓冲区最大容量: " << MAX_SIZE << "\n";
std::cout << "生产者线程数: 2\n";
std::cout << "消费者线程数: 3\n\n";
// 创建线程
std::thread prod1(producer, 1);
std::thread prod2(producer, 2);
std::thread cons1(consumer, 1);
std::thread cons2(consumer, 2);
std::thread cons3(consumer, 3);
// 等待所有线程完成
prod1.join();
prod2.join();
cons1.join();
cons2.join();
cons3.join();
std::cout << "\n=== 生产者-消费者模型执行完毕 ===\n";
return 0;
}
3. 条件变量(Condition Variable)
条件变量用于线程间的协调,允许一个或多个线程等待某个条件的发生。它通常与互斥量一起使用,以实现线程间的同步。
std::condition_variable用于实现线程间的等待和通知机制。
python
/*
3.条件变量 Condition Variable
条件变量 用于线程间的协调,允许一个或者多个线程等待某个条件的发生.通常与互斥量一起使用,以实现线程间的同步.
std::condition_variable用于实现线程间的等待和通知机制。
std::condition_variable 必须在 std::unique_lock 作用域内使用
*/
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void workerThread()
{
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{ return ready; }); // 等待条件满足
std::cout << "工作线程:数据已准备,开始执行...\n";
// 模拟工作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "工作线程:任务完成!\n";
}
void mainThread()
{
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟准备时间
{
std::lock_guard<std::mutex> lg(mtx);
std::cout << "主线程:数据准备完成,设置 ready = true\n";
ready = true;
} // lock_guard 释放锁
cv.notify_one(); // 通知一个等待的线程
std::cout << "主线程:已通知工作线程!\n";
}
int main()
{
std::thread worker(workerThread);
std::thread main_t(mainThread);
worker.join();
main_t.join();
return 0;
}
4. 原子操作(Atomic Operations)
原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。
python
/*
4.原子操作 Atomic Operations
原子操作确保对共享数据的访问是不可分割的,即多线程环境下,原子操作只能完全执行或完全不执行.
*/
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> count(0);//声明一个共享变量 count
void increment() {
count.fetch_add(1, std::memory_order_relaxed);
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
// 使用 count.load() 显式获取值
std::cout << "多线程原子操作: " << count << std::endl;
std::cout << "多线程原子操作: " << count.load() << std::endl;
return 0; // 显式返回值,更清晰
}
python
//多线程_原子操作
#include <atomic>
#include <thread>
#include <iostream>
#include <mutex>
std::atomic<int> count{0};
std::mutex cout_mutex;
void increment() {
count.fetch_add(1, std::memory_order_relaxed);
std::lock_guard<std::mutex> lk(cout_mutex);
std::cout << "线程 " << std::this_thread::get_id()
<< ",计数: " << count.load() << '\n';
}
int main() {
std::cout << "主线程开始 (ID: " << std::this_thread::get_id() << ")\n";
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "\n操作完成\n";
std::cout << "最终计数: " << count.load() << '\n';
return 0;
}
5. 线程局部存储(Thread Local Storage, TLS)
线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。
python
/*
5.线程局部存储 Tread Local Storage(TLS)
线程局部存储 允许每个线程拥有自己的数据副本,也可以通过 thread_local关键字来实现,避免对共享资源的争用.
*/
#include <iostream>
#include <thread>
thread_local int threadData = 0;
thread_local int threadData1 = 0;
void threadFunction()
{
threadData = 42;//每个线程都有自己的threadData副本
std::cout << "线程数据:" << threadData << std::endl;
}
void threadFunction1()
{
threadData1 = 53;//每个线程都有自己的threadData副本
std::cout << "线程数据:" << threadData1 << std::endl;
}
int main()
{
std::thread t1(threadFunction);//线程42
t1.join();
std::thread t2(threadFunction1);//线程53
t2.join();
return 0;
}
6. 死锁(Deadlock)和避免策略
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
a.总是以相同的顺序请求资源。
b.使用超时来尝试获取资源。
c.使用死锁检测算法。
python
/*
6. 死锁(Deadlock)和避免策略
死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:
a.总是以相同的顺序请求资源。
b.使用超时来尝试获取资源。
c.使用死锁检测算法。
*/
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// 使用 timed_mutex 替代 mutex,支持超时机制
std::timed_mutex account1, account2;
// 演示死锁:两个线程以不同顺序请求锁
void transfer_with_deadlock(int amount) {
std::cout << "线程 " << std::this_thread::get_id()
<< " 尝试转账 " << amount << " 元\n";
account1.lock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
account2.lock();
std::cout << "转账成功:从 account1 转 " << amount
<< " 到 account2\n";
account2.unlock();
account1.unlock();
}
// 策略 a:总是以固定顺序获取锁(避免死锁)
void transfer_with_ordering(int amount) {
std::cout << "线程 " << std::this_thread::get_id()
<< " 使用固定顺序锁尝试转账 " << amount << " 元\n";
// 固定顺序:先获取 account1,再获取 account2
account1.lock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
account2.lock();
std::cout << "转账成功:从 account1 转 " << amount
<< " 到 account2(固定顺序)\n";
account2.unlock();
account1.unlock();
}
// 策略 b:使用超时机制尝试获取锁
bool try_transfer_with_timeout(int amount, int timeout_ms) {
std::cout << "线程 " << std::this_thread::get_id()
<< " 使用超时尝试转账 " << amount << " 元\n";
// 尝试获取 account1,超时 150ms
auto deadline = std::chrono::steady_clock::now()
+ std::chrono::milliseconds(timeout_ms);
if (!account1.try_lock_until(deadline)) {
std::cout << "获取 account1 锁失败(超时),放弃本次尝试\n";
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// 尝试获取 account2,超时 150ms
if (!account2.try_lock_until(deadline)) {
std::cout << "获取 account2 锁失败(超时),释放 account1 锁并放弃\n";
account1.unlock();
return false;
}
std::cout << "转账成功:从 account1 转 " << amount
<< " 到 account2(使用超时机制)\n";
account2.unlock();
account1.unlock();
return true;
}
// 策略 c:简化死锁检测(模拟)
void transfer_with_deadlock_detection() {
std::cout << "线程 " << std::this_thread::get_id()
<< " 使用死锁检测机制尝试转账\n";
// 尝试获取 account1 锁,最大等待 100ms
if (account1.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "成功获取 account1 锁\n";
// 尝试获取 account2 锁,最大等待 100ms
if (account2.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "成功获取 account2 锁,转账成功\n";
account2.unlock();
} else {
std::cout << "无法获取 account2 锁,可能死锁风险!\n";
account1.unlock();
std::cout << "释放 account1 锁,避免死锁\n";
}
} else {
std::cout << "无法获取 account1 锁,放弃尝试\n";
}
}
int main() {
std::cout << "=== 死锁演示与三种避免策略 ===\n\n";
// 测试 1:死锁演示
std::cout << "1. 演示死锁:两个线程以不同顺序请求锁\n";
std::thread t1([&]() { transfer_with_deadlock(100); });
std::thread t2([&]() { transfer_with_deadlock(200); });
t1.join();
t2.join();
std::cout << "\n" << std::string(60, '-') << "\n\n";
// 测试 2:策略 a ------ 固定顺序获取锁
std::cout << "2. 策略 a:固定顺序获取锁(避免死锁)\n";
std::thread t3([&]() { transfer_with_ordering(150); });
std::thread t4([&]() { transfer_with_ordering(250); });
t3.join();
t4.join();
std::cout << "\n" << std::string(60, '-') << "\n\n";
// 测试 3:策略 b ------ 使用超时机制
std::cout << "3. 策略 b:使用超时机制尝试获取锁\n";
std::thread t5([&]() { try_transfer_with_timeout(100, 150); });
std::thread t6([&]() { try_transfer_with_timeout(200, 150); });
t5.join();
t6.join();
std::cout << "\n" << std::string(60, '-') << "\n\n";
// 测试 4:策略 c ------ 简化死锁检测
std::cout << "4. 策略 c:使用死锁检测机制(模拟)\n";
std::thread t7(transfer_with_deadlock_detection);
t7.join();
std::cout << "\n=== 所有测试完成 ===\n";
return 0;
}
补充:线程间通信
std::future 和 std::promise:实现线程间的值传递。
python
//使用 std::future 和 std::promise 实现线程通信
// 一个线程计算结果,另一个线程获取结果
#include <iostream>
#include <thread>
#include <future>
#include <vector>
int compute_data(int value) {
// 模拟耗时计算
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return value * 2;
}
int main() {
// 创建 promise 和 future
std::promise<int> result_promise;
std::future<int> result_future = result_promise.get_future();
// 启动计算线程
std::thread worker([&result_promise](int input) {
int output = compute_data(input);
result_promise.set_value(output); // 发送结果
}, 10);
// 主线程等待结果
std::cout << "主线程:等待结果..." << std::endl;
int result = result_future.get(); // 获取结果(阻塞)
std::cout << "主线程:接收结果:" << result << std::endl;
// 等待工作线程结束
worker.join();
return 0;
}
python
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
// 全局共享数据:一个线程安全的队列
std::queue<int> data_queue;
std::mutex queue_mutex;
std::condition_variable queue_condition;
// 生产者线程函数
void producer(int id, int count) {
for (int i = 0; i < count; ++i) {
int value = id * 100 + i;
// 加锁,写入数据
std::lock_guard<std::mutex> lock(queue_mutex);
data_queue.push(value);
std::cout << "生产者" << id << " produced: " << value << std::endl;
// 通知消费者有新数据
queue_condition.notify_one();
}
}
// 消费者线程函数
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lock(queue_mutex);
// 等待队列中有数据,或无数据时阻塞
queue_condition.wait(lock, [] { return !data_queue.empty(); });
// 取出数据
int value = data_queue.front();
data_queue.pop();
lock.unlock(); // 释放锁
// 处理数据
std::cout << "消费者" << id << " consumed: " << value << std::endl;
// 模拟处理耗时
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
// 创建多个生产者和消费者线程
std::vector<std::thread> producers;
std::vector<std::thread> consumers;
// 启动 2 个生产者线程,每个生产 5 个数据
for (int i = 0; i < 2; ++i) {
producers.emplace_back(producer, i, 5);
}
// 启动 2 个消费者线程
for (int i = 0; i < 2; ++i) {
consumers.emplace_back(consumer, i);
}
// 等待生产者完成任务
for (auto& t : producers) {
t.join();
}
// 通知消费者停止(由于消费者使用无限循环,只能通过外部控制)
// 为了安全终止,可以添加一个标志位,这里简化处理
std::cout << "所有生产者已完成,等待消费者完成..." << std::endl;
// 等待消费者线程(实际不会退出,除非手动终止)
// 在真实场景中,应使用共享标志(如 atomic<bool>)来控制退出
for (auto& t : consumers) {
t.join();
}
return 0;
}
整理不易,诚望各位看官点赞 收藏 评论 予以支持,这将成为我持续更新的动力源泉。若您在阅览时存有异议或建议,敬请留言指正批评,让我们携手共同学习,共同进取,吾辈自当相互勉励!