服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++多线程互斥锁和条件变量的详解

C++多线程互斥锁和条件变量的详解

2022-10-25 13:30神厨小福贵! C/C++

这篇文章主要为大家详细介绍了C++多线程互斥锁和条件变量,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!!

先来看下面这段简单的代码:

int g_num = 0;
void print(int id)
{
	for (int i = 0; i < 5; i++)
	{
		++g_num;
		cout << "id = " << id << "==>" << g_num << endl;
		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}
int main()
{
	thread tha(print, 0);
	thread thb(print, 1);
	tha.join();
	thb.join();
	return 0;
}

上述代码功能大致就是在线程tha和thb中运行函数print,每个线程对g_num进行加加一次,最后加出来的g_num的值应该是10,那么我们现在来看结果:

C++多线程互斥锁和条件变量的详解

我们看到运行结果,为什么打印结果最后,按理来说两个线程各加五次,最后结果应该是10呀,怎么会是9呢?

如上图所示,是因为++这个运算符不是原子操作(不会被线程调度机制打断的操作),我们可以将g_num设置为原子数,改为atomic_int g_num = 0;

atomic_int g_num = 0; //将g_num设置为原子操作数
//atomic<int> g_num = 0;这个和上面是一样的 下面这行是模板化之后的
void print(int id)
{
	for (int i = 0; i < 5; i++)
	{
		++g_num;
		cout << "id = " << id << "==>" << g_num << endl;
		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}
int main()
{
	thread tha(print, 0);
	thread thb(print, 1);
	tha.join();
	thb.join();
	return 0;
}

将g_num设置为原子操作数之后,在++阶段就不会被线程调度机制给打断,我们来看运行结果:

C++多线程互斥锁和条件变量的详解

运行结果是我所期望的但是中间那块又出了一点小状况连着打着两个4,两个6,这种情况该怎么办呢?

下面就该说道我们的互斥锁了:

 

互斥锁:

在上述代码中我们使用了共享资源----->全局量g_num,两个线程同时对g_num进行++操作,为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

来看下面代码:

int g_num = 0;
std::mutex mtx;  //创建锁对象
void print(int id)
{
	for (int i = 0; i < 5; i++)
	{
		mtx.lock();  //上锁
		++g_num;
		cout << "id = " << id << "==>" << g_num << endl;
		mtx.unlock(); //解锁
		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}
}
int main()
{
	thread tha(print, 0);
	thread thb(print, 1);
	tha.join();
	thb.join();
	return 0;
}

我们来看运行结果:符合我们最初的预期。

C++多线程互斥锁和条件变量的详解

打开官方文档,可以看到

C++多线程互斥锁和条件变量的详解

创建锁对象只有这个方法,拷贝构造被删除了。

std::mutex::try_lock

对于互斥锁的lock和unlock我们都很熟悉了,下面来说一下std::mutex::try_lock这个成员函数!

C++多线程互斥锁和条件变量的详解

try_lock字面意思就是说尝试上锁,如果上锁成功,返回true,上锁失败则返回false,但是如果上锁失败,他还是会接着往下运行,不会像lock哪运被阻塞在上锁那块,所以try_lock必须得在循环中使用:

int g_num = 0;
std::mutex mtx;
void print(int id)
{
	for (int i = 0; i < 5; i++)
	{
		mtx.try_lock();  //代码只是将lock换成了try_lock且没把try_lock扔在循环中执行
		++g_num;
		cout << "id = " << id << "==>" << g_num << endl;
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}
}
int main()
{
	thread tha(print, 0);
	thread thb(print, 1);
	tha.join();
	thb.join();
	return 0;
}

我们来看运行结果:

C++多线程互斥锁和条件变量的详解

unlock of unowned mutex,这玩意思就是说你在给个没上锁的互斥锁解锁,所以报这错误,因此try_lock搁在普通语句中,会有很大的问题,现命我们演示一下将这玩意放到循环中去弄一边:

int g_num = 0;
std::mutex mtx;
void print(int id)
{
	for (int i = 0; i < 5; i++)
	{
		while (!mtx.try_lock())  //try_lock失败时为false 前面加了!,所以失败时为true 然后打印尝试加锁
		{                    //然后再次尝试加锁,只有加锁成功了,才能出这个while循环
			cout << "try  lock" << endl;
		}	
		++g_num;
		cout << "id = " << id << "==>" << g_num << endl;
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}
}
int main()
{
	thread tha(print, 0);
	thread thb(print, 1);
	tha.join();
	thb.join();
	return 0;
}

我们来看运行结果:

C++多线程互斥锁和条件变量的详解

运行结果符合我们的预期,但是try_lock这个函数有个不好处是太损耗资源了,当它加锁失败时,一直尝试加锁一直尝试加锁,损耗CPU资源。

 

条件变量:condition_variable

C++多线程互斥锁和条件变量的详解

框住这三个函数较为重要,下面着重来说下面这三个函数:这里顺便说一下,下面代码将会是条件变量和互斥锁的结合使用,至于为什么要将互斥锁和条件变量一起使用,原因就是互斥锁状态太单一了,而条件变量允许阻塞,接收信号量等刚好弥补了互斥锁的缺陷所以这些一起使用!!!

这三个函数呢,通过一个小实验来实现,通过多线程分别打印123一直到100:

std::mutex mtx;
std::condition_variable cv;
int isReady = 0;
const int n = 100;
void print_A()
{
	std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlock
	int i = 0;
	while (i < n)
	{
		while (isReady != 0) 
		{
			cv.wait(lock);//互斥锁和信号量一起使用  wait参数为锁对象
		}
		cout << "A" ;
		isReady = 1;
		++i;
		std::this_thread::sleep_for(std::chrono::microseconds(100));
		cv.notify_all(); //当isReady等于0时print_B 和 print_C 处于阻塞状态  
        //该函数就是唤醒所有等待的函数,然后通过isReady来进行判断要进行那个函数的运行
	}
}
void print_B()
{
	std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlock
	int i = 0;
	while (i < n)
	{
		while (isReady != 1)
		{
			cv.wait(lock);
		}
		cout << "B" ;
		isReady = 2;
		++i;
		std::this_thread::sleep_for(std::chrono::microseconds(100));
		cv.notify_all();
	}
}
void print_C()
{
	std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlock
	int i = 0;
	while (i < n)
	{
		while (isReady != 2)
		{
			cv.wait(lock);
		}
		cout << "C" ;
		isReady = 0;
		++i;
		std::this_thread::sleep_for(std::chrono::microseconds(100));
		cv.notify_all();
	}
}
int main()
{
	thread tha(print_A);
	thread thb(print_B);
	thread thc(print_C);
	tha.join();
	thb.join();
	thc.join();
	return 0;
}

上面代码解析:

C++多线程互斥锁和条件变量的详解

运行结果:

C++多线程互斥锁和条件变量的详解

我们可以看到上述代码最后唤醒其他线程使用的是notify_all()函数,notify_all()函数作用就是环球其他阻塞的函数,然后因为isready这个数的存在,所以就会选择合适的线程来进行执行,如果我们使用notify_one()呢,先来说下notify_one()函数的作用是什么。notify_one()函数是唤醒其他线程(随机唤醒,这道题中不适合,因为如果打印完A之后唤醒了C线程那么就会一直阻塞在那块)

我们试一下notify_one()函数,可以发现这玩意确实会堵塞在那块:

C++多线程互斥锁和条件变量的详解

 

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://blog.csdn.net/qq_45829112/article/details/123541772

延伸 · 阅读

精彩推荐
  • C/C++详细解读C++编程中的匿名类类型和位域

    详细解读C++编程中的匿名类类型和位域

    这篇文章主要介绍了C++编程中的匿名类类型和位域,是C++入门学习中的基础知识,需要的朋友可以参考下...

    C++教程网9632021-03-23
  • C/C++c++11新增的便利算法实例分析

    c++11新增的便利算法实例分析

    这篇文章主要介绍了c++11新增的便利算法,主要有用于判断、查找、数组、序列等的操作算法,非常具有实用价值,需要的朋友可以参考下...

    C++教程网7982021-01-31
  • C/C++EasyC++,动态联编

    EasyC++,动态联编

    当我们使用程序调用函数的时候,究竟应该执行哪一个代码块呢?将源代码中的函数调用解释为执行特定的函数代码块这个过程被称为函数名联编(binding)。...

    Coder梁11582021-12-30
  • C/C++详解C++赋值操作符重载

    详解C++赋值操作符重载

    这篇文章主要介绍了C++赋值操作符重载的相关资料,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下...

    Dabelv7782021-09-23
  • C/C++C++语言实现hash表详解及实例代码

    C++语言实现hash表详解及实例代码

    这篇文章主要介绍了C++语言实现hash表详解及实例代码的相关资料,需要的朋友可以参考下...

    C++教程网9652021-04-29
  • C/C++C++获取字符串长度的几个函数方式

    C++获取字符串长度的几个函数方式

    这篇文章主要介绍了C++获取字符串长度的几个函数方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    zhouhongkai0612042021-10-11
  • C/C++Qt使用QWT绘制柱状图详解

    Qt使用QWT绘制柱状图详解

    QT中提供了一个叫做QWT的库。QWT,全称是Qt Widgets for Technical Applications,是一个基于LGPL版权协议的开源项目,可生成各种统计图。本文将通过它绘制柱状图,...

    中国好公民st5602022-09-05
  • C/C++C++常用函数之XML JSON格式转换问题

    C++常用函数之XML JSON格式转换问题

    XML在Json出现前应用很广泛,灵活性好,应用语言也没有限制,发展了这么长时间后xml标准已经很臃肿。这篇文章主要介绍了C++常用函数之XML JSON格式转换问...

    程序员的成长10942021-08-20