【C++多线程编程:六种锁】

目录

普通互斥锁:

轻量级锁

独占锁:

std::lock_guard:

std::unique_lock:

共享锁:

超时的互斥锁

递归锁


普通互斥锁:

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;
}
相关推荐
2401_858286111 分钟前
123.【C语言】数据结构之快速排序挖坑法和前后指针法
c语言·开发语言·数据结构·算法·排序算法
_WndProc9 分钟前
【C++/控制台】2048小游戏
开发语言·c++·游戏·游戏程序
白白白白纸呀12 分钟前
ADO.NET知识总结6---SqlDataAdapter桥接器
开发语言·c#·.net
C++小厨神20 分钟前
SQL语言的函数实现
开发语言·后端·golang
闲人编程32 分钟前
CAPL概述与环境搭建
开发语言·自动化测试·数据分析·capl·canoe·故障注入·canalyzer
涛ing42 分钟前
【Ubuntu 上搭建 Nginx-RTMP 服务】
linux·服务器·c++·nginx·ubuntu·ffmpeg·音视频
网硕互联的小客服1 小时前
云服务器加了安全组端口还是无法访问
开发语言·php
倔强的石头1061 小时前
【C++经典例题】求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句
开发语言·c++
九离十1 小时前
C语言教程——指针进阶(1)
c语言·开发语言
DevOpsDojo1 小时前
Julia语言的软件工程
开发语言·后端·golang