C++中的 互斥量
1.概念:
为什么:线程的异步性,不是按照时间来的!!!
C++并发以及多线程的秘密-CSDN博客
目的
多线程编程中,当多个线程可能同时访问和修改共享资源时,会导致数据不一致或程序错误。互斥量提供了一种加锁机制,线程在访问共享资源前必须先获取互斥量的锁,访问结束后释放锁,这样其他线程才能获取锁并访问共享资源。
原理:
互斥量内部维护一个状态,表示它是否被锁定。当一个线程成功获取锁时,互斥量被锁定,其他线程试图获取锁时会被阻塞,直到锁被释放
2.基本用法
C++ 标准库在 <mutex>
头文件中提供了几种类型的互斥量, std::mutex std::timed_mutex
lock函数
和unlock函数
#include<mutex>
#include<thread>
#include<iostream>
using namespace std;
int sharedResource = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 10; ++i) {
mtx.lock();
++sharedResource;
cout<<++sharedResource<<endl
mtx.unlock();
}
}
void decrement() {
for (int i = 0; i < 10; ++i) {
mtx.lock();
--sharedResource;
cout<<sharedResource<<endl;
mtx.unlock();
}
}
int main()
{
std::thread thread1(increment);
std::thread thread2(decrement);
thread1.join();
thread2.join();
std::cout << "Final value of sharedResource: " << sharedResource << std::endl;
return 0;
}
结果如下:
1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0
结果展现清晰知道会有独占思想
2.lock_guard函数
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::mutex mtx;
int n=0;
void func1() {
lock_guard<mutex> lock(mtx);
n++;
}
void func2() {
lock_guard<mutex> lock(mtx);
n--;
}
int main() {
thread t1(func1);
cout << n << endl;
thread t2(func2);
cout << n << endl;
t1.join();
t2.join();
}
3.死锁以及解决办法
概念:
死锁是指在多线程或多进程环境中,两个或多个执行单元(线程、进程等)因竞争系统资源或彼此通信而造成的一种阻塞现象,若无外力作用,这些执行单元都将无法推进。
死锁产生的必要条件
-
互斥条件:资源在同一时间只能被一个执行单元使用。例如,打印机在打印一份文档时,不能同时为另一份文档服务。
-
占有并等待条件:一个执行单元持有至少一个资源,并在等待获取其他执行单元持有的额外资源。比如,线程 A 持有资源 R1,同时等待资源 R2,而资源 R2 被线程 B 持有。
-
不可剥夺条件:资源不能被强制从占有者手中夺走,只能由占有者主动释放。例如,一个线程获得了一个文件的独占访问权,其他线程不能强行剥夺这个权限。
-
循环等待条件:存在一个执行单元的循环链,链中的每个执行单元都在等待下一个执行单元持有的资源。例如,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,而线程 C 又等待线程 A 持有的资源。
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mutex1;
std::mutex mutex2;
void threadFunction1() {
mutex1.lock();
std::cout << "Thread 1 has locked mutex1" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
mutex2.lock();
std::cout << "Thread 1 has locked mutex2" << std::endl;
mutex2.unlock();
mutex1.unlock();
}
void threadFunction2() {
mutex2.lock();
std::cout << "Thread 2 has locked mutex2" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
mutex1.lock();
std::cout << "Thread 2 has locked mutex1" << std::endl;
mutex1.unlock();
mutex2.unlock();
}
int main() {
std::thread thread1(threadFunction1);
std::thread thread2(threadFunction2);
thread1.join();
thread2.join();
return 0;
}
结果//Thread 1 has locked mutex1Thread 2 has locked mutex2
解析:
在上述代码中,threadFunction1
先锁定 mutex1
,然后等待 mutex2
,而 threadFunction2
先锁定 mutex2
,然后等待 mutex1
。如果两个线程同时运行,就会满足死锁的四个条件,导致死锁发生。线程 1 持有 mutex1
并等待 mutex2
,线程 2 持有 mutex2
并等待 mutex1
,形成循环等待,且资源不能被剥夺,从而造成死锁。
如何解决?
利用另外一个lock_guard
std::lock_guard
是基于 RAII(资源获取即初始化)机制的互斥量管理类。它在构造时自动锁定互斥量,在析构时自动解锁,从而简化了代码并避免手动解锁失败的风险
代表adopt_lock 以及手动锁了(就是用lock)
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::mutex mutex1;
std::mutex mutex2;
;
void threadFunction1() {
lock(mutex1,mutex2);
lock_guard<mutex>lock1(mutex1,adopt_lock);
lock_guard<mutex>lock2(mutex2,adopt_lock);
std::cout << "Thread 1 has locked mutex1 and mutex2" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread 1 is working with both mutexes" << std::endl;
}
void threadFunction2() {
lock(mutex1,mutex2);
lock_guard<mutex>lock1(mutex2,adopt_lock);
lock_guard<mutex>lock2(mutex1,adopt_lock);
std::cout << "Thread 1 has locked mutex1 and mutex2" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread 1 is working with both mutexes" << std::endl;
}
int main() {
std::thread thread1(threadFunction1);
std::thread thread2(threadFunction2);
thread1.join();
thread2.join();
return 0;
}
Thread 1 has locked mutex1 and mutex2
Thread 1 is working with both mutexes
Thread 1 has locked mutex1 and mutex2
Thread 1 is working with both mutexes
4.unique_lock
std::lock_guard
是一个简单的 RAII(Resource Acquisition Is Initialization)类,用于在其生命周期内自动锁定和解锁互斥量。它的优点是简单易用,开销小。然而,std::unique_lock
提供了更多的功能和灵活性,在很多场景下可以取代std::lock_guard:
-
延迟锁定:
std::unique_lock
可以在构造时不立即锁定互斥量,而std::lock_guard
在构造时总是立即锁定互斥量。 -
锁定所有权的转移:
std::unique_lock
允许通过移动语义转移锁定的所有权,这在函数间传递锁定状态时非常有用。 -
成员函数操作:
std::unique_lock
提供了如lock()
、try_lock()
、try_lock_for()
、try_lock_until()
和unlock()
等成员函数,允许对锁定状态进行更细粒度的控制,而std::lock_guard
只在构造和析构时自动锁定和解锁,没有提供这些操作。
4.1三种构造方法
1.std::adopt_lock_t
:与std::lock_guard
类似,当使用std::adopt_lock
作为第二个参数时,表明调用者已经手动锁定了互斥量,std::unique_lock
在构造时不会再次锁定,而是在析构时负责解锁。例如:
std::mutex mtx;
mtx.lock();
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
2.std::defer_lock_t
:使用std::defer_lock
作为第二个参数时,std::unique_lock
构造时不会锁定互斥量,而是处于未锁定状态。之后可以通过调用lock()
、try_lock()
等成员函数来锁定互斥量。例如
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
if (lock.try_lock()) {
// 成功锁定,执行临界区代码
lock.unlock();
}
3.std::try_to_lock_t
:使用std::try_to_lock
作为第二个参数时,std::unique_lock
构造时会尝试锁定互斥量,但不会阻塞等待。如果成功锁定,unique_lock
对象拥有锁定;否则,处于未锁定状态。例如
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功锁定,执行临界区代码
lock.unlock();
}
4.2成员函数
-
lock()
:锁定关联的互斥量。如果互斥量已被锁定,调用线程将阻塞直到互斥量可用。 -
try_lock()
:尝试锁定关联的互斥量,不会阻塞。如果成功锁定,返回true
;否则,返回false
。 -
try_lock_for(duration)
:尝试在指定的时间段内锁定关联的互斥量。如果在指定时间内成功锁定,返回(一般参数为chrono::seconds())同理 -
try_lock_until(time_point) 只能time_mutex用
-
unlock()
:解锁关联的互斥量。 -
owns_lock()
:返回unique_lock
对象是否拥有互斥量的锁定。 -
release()
:释放unique_lock
对象对互斥量的所有权,返回指向关联互斥量的指针,并且unique_lock
对象变为不拥有任何互斥量
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// 使用 std::timed_mutex 来支持带超时的锁定操作
std::timed_mutex mtx;
// 线程函数
void worker(int id) {
// 使用 unique_lock 管理互斥量
std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock);
// 尝试锁定互斥量,不会阻塞
if (lock.try_lock()) {
std::cout << "Thread " << id << " acquired the lock using try_lock()." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire the lock using try_lock()." << std::endl;
}
// 尝试在指定时间段内锁定互斥量
if (lock.try_lock_for(std::chrono::seconds(2))) {
std::cout << "Thread " << id << " acquired the lock using try_lock_for()." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire the lock using try_lock_for()." << std::endl;
}
// 尝试在指定时间点前锁定互斥量
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
if (lock.try_lock_until(timeout)) {
std::cout << "Thread " << id << " acquired the lock using try_lock_until()." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire the lock using try_lock_until()." << std::endl;
}
// 正常锁定互斥量
lock.lock();
std::cout << "Thread " << id << " acquired the lock using lock()." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// 检查是否拥有互斥量的锁定
if (lock.owns_lock()) {
std::cout << "Thread " << id << " owns the lock." << std::endl;
}
// 释放 unique_lock 对互斥量的所有权
std::timed_mutex* released_mtx = lock.release();
if (!lock.owns_lock()) {
std::cout << "Thread " << id << " released the lock using release()." << std::endl;
}
// 手动解锁互斥量
released_mtx->unlock();
}
int main() {
// 创建两个线程
std::thread t1(worker, 1);
std::thread t2(worker, 2);
// 等待线程完成
t1.join();
t2.join();
return 0;
}