目录
普通互斥锁:
std::mutex确保任意时刻只有一个线程可以访问共享资源,在多线程中常用于保护共享资源。
互斥锁的实现示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex Mutex;
int shared_source = 1;
void Shared_data()
{
//获取锁
Mutex.lock();
shared_source++;
std::cout << "shared_source : " << shared_source <<" by thread :"<<std::this_thread::get_id()<< std::endl;
//锁释放
Mutex.unlock();
}
int main()
{
std::thread thread1(Shared_data);
std::thread thread2(Shared_data);
thread1.join();
thread2.join();
return 0;
}
在实际使用的过程中,如果容易忘记对锁进行释放,可以使用std::unique_lock和std::lock_guard安全的管理锁的释放。
轻量级锁
在C++中,轻量级锁通常指的是一些开销较小,性能较高的锁机制,C++并没有直接提供"轻量级锁"的概念,可以通过一个自旋锁来达到实现轻量级锁的目的。
适用场景:竞争不激烈,比如在大多数时间里中有一个线程需要访问临界区的情况。但如果竞争激烈会导致CUP资源的浪费(CPU自旋),降低性能。
自旋锁:一种简单的锁机制,当线程尝试获取锁时,如果锁已被其他线程占用,则当前线程会不断地循环等待,直到锁可用
自旋锁的示例:
cpp
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<bool>spinlock(false);
void spin_lock()
{
while (spinlock.exchange(true, std::memory_order_acquire))
{
//如果锁被占用则一直自旋,等待锁可用
}
}
void spin_unlock()
{
spinlock.store(false, std::memory_order_release);//释放锁,并将锁的状态写入内存中
}
void critical_section()
{
spin_lock();
std::cout << "mutex acquire by thread : " << std::this_thread::get_id() << std::endl;
// std::this_thread::sleep_for(std::chrono::seconds(1));
spin_unlock();//锁释放
}
int main()
{
std::thread t1(critical_section);
std::thread t2(critical_section);
t1.join();
t2.join();
return 0;
}
独占锁:
std::unique_lock和std::lock_guard都是cpp标准库提供的管理互斥锁的类,他们都提供了自动锁和解锁的功能。但二者存在一些关键区别:
std::lock_guard:
- std::lock_guard并不是一种锁,而是一个作用域锁管理,一个能管理锁生命周期的工具,用于简化互斥锁的使用,确保在作用域结束后自动释放锁,避免死锁问题
- 自动加锁和解锁:在构造时自动加锁,在析构时自动解锁,不需要显示的调用加锁和解锁的方法
- 不支持条件变量配合使用:条件变量需要可以临时释放锁并重新获取锁,但是lock_guard并没有提供unlock().
- 使用场景:适用于不需要显示控制加锁和解锁的场景,或者不需要与条件变量配合使用的场景,适用于在某个作用域中同步访问共享资源的场景
lock_guard示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void worker()
{
std::lock_guard<std::mutex>lock(mtx);//自动加锁
shared_data++;
std::cout << "shared_data ++ by thread : " << std::this_thread::get_id() << std::endl;
//自动解锁,无需显示调用unlock()
}
int main()
{
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
return 0;
}
std::unique_lock:
- std::unique_lock本身不是一种锁,而是一个可选的互斥锁管理器,提供了对互斥锁的更加灵活的控制方式
- 显示加锁和解锁:提供了lock()和unlock()方法,可以显示的加锁和解锁,也可以在构造时自动加锁
- 可以与条件变量配合
- 自动管理锁的生命周期,避免了因忘记解锁而导致的死锁问题
- 使用场景:需要显示控制加锁和解锁时机,或者需要和条件变量配合使用的场景
unique_lock实例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
char str = 'a';
//不显示调用加解锁,自动加锁和解锁
void worker()
{
std::unique_lock<std::mutex>lock(mtx);
//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁
shared_data += 1;
std::cout << "shared_data ++ :" << shared_data << " ,by thread : " << std::this_thread::get_id() << std::endl;
std::cout << "str: " << str++ << " ,by thread: " << std::this_thread::get_id() << std::endl;
}
//显示调用加锁和解锁
void worker()
{
std::unique_lock<std::mutex>lock(mtx,std::defer_lock);
//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁
shared_data+=1;
std::cout << "shared_data ++ :"<<shared_data<<" ,by thread : " << std::this_thread::get_id() << std::endl;
lock.lock();//延迟锁定
std::cout << "str: " << str++ <<" ,by thread: "<< std::this_thread::get_id() << std::endl;
lock.unlock();//显示解锁
}
int main()
{
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
return 0;
}
独占锁与mutex相比,更加的安全,它可以避免忘记手动解锁,会在其作用域结束时自动的释放锁
共享锁:
在C++中,共享锁允许多个线程共享读取资源,但在写入资源时只允许一个线程写入数据,要求独占锁。当某个线程获取了独占锁时,其他线程无法获取任何形式地锁,而当多个线程获取共享锁时,他们可以同时读取共享资源
使用场景:适用于读多写少的场景下,能有效提高多线程程序的性能
示例:
cpp
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex mtx;
int shared_data = 0;
void read_data()
{
//获取共享锁
std::shared_lock <std::shared_mutex> lock(mtx);
std::cout << "Reading data : " << shared_data << std::endl;
}
void write_data()
{
//获取独占锁
std::unique_lock<std::shared_mutex>lock(mtx);
shared_data = 24;
std::cout << "Writing data : " << shared_data << std::endl;
}
int main()
{
std::thread t1(read_data);
std::thread t2(read_data);
std::thread t3(write_data);
t1.join();
t2.join();
t3.join();
return 0;
}
超时的互斥锁
C++标准库提供了timed_mutex来实现该功能,支持超时机制,当线程尝试获取锁时,可以指定一个超时时间,如果在该时间内无法获取锁,线程将返回并继续执行其他任务。通过使用超时互斥锁,可以有效的避免线程在等待锁时无限地阻塞,提高程序地响应和稳定性
示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
//超时锁
//C++标准库提供的互斥锁之一,支持在尝试获取锁时设置超时时间
std::timed_mutex mtx;
void function_time()
{
//在1s内获取锁
//try_lock_for在指定时间内获取锁,成功返回true,失败则false
//参数chrono是一个时间间隔类型的一个域
if (mtx.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "Lock acquired " << std::endl;
//模拟耗时操作
//在当前线程休眠这2s内,其他线程无法获取该锁
//所以当t2尝试获取锁时,t1持有锁并休眠2s,当锁释放后,t2锁获取超时
std::this_thread::sleep_for(std::chrono::seconds(2));
mtx.unlock();//释放锁
}
else
{
std::cout << "Fail to acquire lock within 1s" << std::endl;
}
}
int main()
{
//主线程创建的这俩线程几乎同时开始执行function_time函数
std::thread t1(function_time);
std::thread t2(function_time);
t1.join();
t2.join();
return 0;
}
递归锁
递归锁:同一个线程多次获取同一把锁,而不会导致死锁。cpp中没有直接提供递归锁的实现,但是可以通过reecursive_mutex来实现递归锁的功能。
reecursive_mutex:一个可重入的互斥锁,允许同一个线程多次调用lock或try_lock来获取锁,不会导致死锁。
使用场景:需要在同一个线程中多次获取锁的场景
递归锁示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex mtx;
void function(int n)//递归
{
if (n > 0)
{
mtx.lock();
std::cout << "lock acquired by thread : " << std::this_thread::get_id() << " n ; " << n << std::endl;
function(n - 1);
mtx.unlock();
}
}
int main()
{
std::thread t1(function,3);
std::thread t2(function,2);
t1.join();
t2.join();
return 0;
}