讲座主题:C++中的锁机制与std::mutex同步入门
大家好,欢迎来到今天的讲座!今天我们将一起探讨C++中的锁(Locks)机制,以及如何使用std::mutex
进行线程同步。如果你对多线程编程感到头疼,别担心,我会用轻松幽默的方式带你一步步掌握这个知识点。
为什么我们需要锁?
在单线程的世界里,程序按部就班地执行,一切井然有序。然而,当我们进入多线程领域时,事情变得复杂起来。多个线程可能会同时访问共享资源(比如一个变量或数据结构),这就可能导致竞态条件(Race Condition)——即线程之间的操作顺序不确定,导致结果不可预测。
举个例子,假设你和你的朋友同时去银行取钱,你们的账户余额是100元。如果两个线程分别试图从账户中取出50元,但没有正确同步,可能会出现以下情况:
- 线程A读取余额为100元。
- 线程B也读取余额为100元。
- 线程A扣除50元后更新余额为50元。
- 线程B扣除50元后更新余额为50元。
最终,账户余额变成了50元,但实际上应该为0元!这就是竞态条件的一个典型例子。
为了避免这种情况,我们需要一种机制来确保同一时间只有一个线程可以访问共享资源。这就是锁的作用!
C++中的锁机制
在C++中,标准库提供了std::mutex
作为最基本的锁机制。mutex
是“Mutual Exclusion”(互斥)的缩写,它的作用就是让多个线程之间互相排斥,确保某个代码段在同一时刻只能被一个线程执行。
如何使用std::mutex
让我们通过一个简单的例子来学习如何使用std::mutex
。
示例代码:保护共享资源
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
// 创建一个全局变量作为共享资源
int sharedResource = 0;
// 定义一个mutex对象
std::mutex mtx;
void increment() {
// 加锁
mtx.lock();
for (int i = 0; i < 1000; ++i) {
++sharedResource;
}
// 解锁
mtx.unlock();
}
int main() {
std::vector<std::thread> threads;
// 创建多个线程,每个线程都会修改sharedResource
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << "Final value of sharedResource: " << sharedResource << std::endl;
return 0;
}
在这个例子中,我们创建了一个std::mutex
对象mtx
,并通过lock()
和unlock()
方法手动控制锁的获取和释放。这样可以确保每次只有一个线程能够进入increment()
函数中的临界区(Critical Section)。
更优雅的锁管理:std::lock_guard
虽然lock()
和unlock()
可以实现基本的同步,但手动管理锁容易出错,比如忘记解锁或在异常情况下未能正确释放锁。为了解决这个问题,C++提供了std::lock_guard
,它是一种RAII(Resource Acquisition Is Initialization)风格的工具,可以在构造时自动加锁,在析构时自动解锁。
使用std::lock_guard
改写上面的例子
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int sharedResource = 0;
std::mutex mtx;
void increment() {
// 使用lock_guard自动管理锁
std::lock_guard<std::mutex> lock(mtx);
for (int i = 0; i < 1000; ++i) {
++sharedResource;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final value of sharedResource: " << sharedResource << std::endl;
return 0;
}
在这个版本中,std::lock_guard
会在其作用域结束时自动释放锁,避免了手动管理锁的风险。
多个锁的死锁问题
当多个线程需要同时获取多个锁时,可能会出现死锁(Deadlock)的情况。例如,线程A先获取锁1再获取锁2,而线程B先获取锁2再获取锁1,这会导致两个线程互相等待对方释放锁,从而陷入僵局。
如何避免死锁?
- 始终以相同的顺序获取锁:确保所有线程都按照相同的顺序获取锁。
- 使用
std::lock
和std::scoped_lock
:C++提供了更高级的工具来帮助管理多个锁。
示例:使用std::lock
避免死锁
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void threadFunction(int id) {
if (id == 1) {
// 线程1先获取mtx1再获取mtx2
std::lock(mtx1, mtx2); // 同时锁定两个锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Thread 1 locked both mutexes." << std::endl;
} else {
// 线程2先获取mtx2再获取mtx1
std::lock(mtx2, mtx1); // 同时锁定两个锁
std::lock_guard<std::mutex> lock1(mtx2, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx1, std::adopt_lock);
std::cout << "Thread 2 locked both mutexes." << std::endl;
}
}
int main() {
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
t1.join();
t2.join();
return 0;
}
在这个例子中,std::lock
确保了两个锁被同时锁定,从而避免了死锁。
总结
今天我们学习了C++中的锁机制,重点介绍了std::mutex
和std::lock_guard
的基本用法。以下是关键点的总结:
概念 | 描述 |
---|---|
std::mutex |
基本的互斥锁,用于保护共享资源。 |
std::lock_guard |
RAII风格的锁管理工具,简化锁的使用并防止忘记解锁。 |
死锁 | 多个线程互相等待对方释放锁,导致程序无法继续运行。 |
std::lock |
用于同时锁定多个锁,避免死锁。 |
希望今天的讲座对你有所帮助!如果你有任何问题,欢迎随时提问。下次见!