C++——互斥量

文章目录

  • 一、基本知识
  • 二、独占互斥量mutex
    • 1.mutex的介绍
    • 2.mutex的成员函数
    • 3.实例演示
  • 三、lock_guard和unique_lock的使用和区别
  • 四、递归互斥量recursive_mutex
    • 1.基本知识
    • 2.演示示例
  • 五、带超时的互斥量std::timed_mutex和std::recursive_timed_mutex


一、基本知识

C++11提供如下4种语义的互斥量(mutex)
std::mutex,独占的互斥量,不能递归使用。
std::time_mutex,带超时的独占互斥量,不能递归使用。
std::recursive_mutex,递归互斥量,不带超时功能。
std::recursive_timed_mutex,带超时的递归互斥量。
使用时需要包含头文件

#include

二、独占互斥量mutex

1.mutex的介绍

下面以 std::mutex 为例介绍 C++11 中的互斥量用法。
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

2.mutex的成员函数

构造函数:std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于unlocked 状态的。
lock():调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
①如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
② 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
③如果当前互斥量被当前调用线程锁住,则会产生死锁。
unlock(): 解锁,释放对互斥量的所有权。
try_lock():尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况:这些
①如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
②如果当前互斥量被其他线程锁住,则当前调用线程返回
false,而并不会被阻塞掉。
③如果当前互斥量被当前调用线程锁住,则会产生死锁。

3.实例演示

#include
#include
#include
using namespace std;

std::mutex mtx;
int counter;

void pthread_fun()
{
	for (int i = 0; i < 10000; i++)
	{
		mtx.lock();
		counter++;
		mtx.unlock();
	}
}

int main() 
{
	thread pthread[10];
	for (int i = 0; i < 10; i++)
	{
		pthread[i] = thread(pthread_fun);
	}
	for (int i = 0; i < 10; i++)
	{
		pthread[i].join();
	}
	cout << counter << endl;
	return 0;
}

运行结果:
C++——互斥量_第1张图片

三、lock_guard和unique_lock的使用和区别

相对于手动lock和unlock,我们可以使用RAII(通过类的构造析构)来实现更好的编码方式。
这里涉及到unique_lock,lock_guard的使用。
C++相较于C引入了很多新的特性, 比如可以在代码中抛出异常, 如果还是按照以前的加锁解锁的话代码会极为复杂繁琐。

unique_lock,lock_guard的区别:
unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁。

#include
#include
#include
using namespace std;

std::mutex mtx;
int counter;

void pthread_fun()
{
	for (int i = 0; i < 10000; i++)
	{
		//mtx.lock();error,必须和unlock配对,否则锁得不到释放
		std::unique_lock<mutex>lck(mtx);//right 这句语句执行会自动上锁,生存期到了之后会调动析构函数自动释放锁
		counter++;
		//lck.unlock();//可以手动释放,也可以等过了作用域,lck调用析构函数析构自动释放锁
	}
}

int main() 
{
	thread pthread[10];
	for (int i = 0; i < 10; i++)
	{
		pthread[i] = thread(pthread_fun);
	}
	for (int i = 0; i < 10; i++)
	{
		pthread[i].join();
	}
	cout << counter << endl;
	return 0;
}

对于locak_guard来说,unique_lock显得比较灵活一点,因为在lock_guard和unique_lock都可以借用对象有生存期调用析构函数释放锁的情况下,unique_lock可以还自己上锁释放锁,显得比较灵活一点,所以平时比较经常使用unique_lock。

四、递归互斥量recursive_mutex

1.基本知识

递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:
(1)需要用到递归锁的多线程互斥处理本身就是可以简化的,允许递归很容易放纵复杂逻辑的产生,并且产生晦涩,当要使用递归锁的时候应该重新审视自己的代码是否一定要使用递归锁;
(2)递归锁比起非递归锁,效率会低;
(3)递归锁虽然允许同一个线程多次获得同一个互斥量,但可重复获得的最大次数并未具体说明,一旦超过一定的次数,再对lock进行调用就会抛出std::system错误。

2.演示示例

#include
#include
#include
using namespace std;

std::recursive_mutex rc_mtx;//可递归的锁
std::mutex mtx;
int g_val = 0;

void fun(int&& x)
{
	if (x == 0) return;

	std::unique_lock<std::recursive_mutex>lck(rc_mtx);//right

	//下面语句error 会造成死锁,因为递归的时候锁没有释放,再次获得锁,造成死锁。
	//std::unique_locklck(mtx);
	g_val++;
	x--;
	fun(std::move(x));
}

int main()
{
	std::thread pthreads[5];
	for (int i = 0; i < 5; i++)
	{
		pthreads[i] = std::thread(fun, 5);
	}
	for (int i = 0; i < 5; i++)
	{
		pthreads[i].join();
	}
	cout << g_val << endl;
	return 0;
}

运行结果:
C++——互斥量_第2张图片

五、带超时的互斥量std::timed_mutex和std::recursive_timed_mutex

std::timed_mutex比std::mutex多了两个超时获取锁的接口:
try_lock_for:获取锁的时间,过了这个时间会释放锁
try_lock_until:尝试获得锁,直到过了某个时间,返回false。

#include
#include
#include
using namespace std;

std::mutex mtx;
std::timed_mutex t_mtx;
int g_val = 0;

//如果fun写成下面那样会发生死锁,因为第一个获取锁的线程没有释放锁,其他线程都会阻塞住
/*
void fun()
{
	mtx.lock();
	g_val++;
}
*/

std::chrono::milliseconds timeout(1000);

void fun()
{
	t_mtx.try_lock_for(timeout);//获取锁1000毫秒,1000毫秒后会释放锁
	g_val++;
}

int main()
{
	std::thread pthreads[5];
	for (int i = 0; i < 5; i++)
	{
		pthreads[i] = std::thread(fun);
	}
	for (int i = 0; i < 5; i++)
	{
		pthreads[i].join();
	}
	cout << g_val << endl;
	return 0;
}

你可能感兴趣的:(C++,c++)