【C++11多线程入门教程】系列之互斥量mutex

互斥量

  谈及C++11多线程里面的互斥量,首先浮现在脑海的是互斥量是有什么用,怎么用?下面简短介绍一下互斥量,有什么用?用来解决多线程对共享数据的访问(读与写)机制,简单说就是实现多个线程间对资源的互斥访问。怎么用?C++11里面的互斥量相关的使用方法比较多样化,我们本章先学习mutex的方式。

代码演示

我们先看下面这段没有加锁的问题代码:

  我们将下面代码的①②③④取消注释,加上锁后程序就正常运行。这部分代码简单说明了多线程对数据的读与取时候需要加锁。订票线程与出票线程函数,问题主要在于假设某一时刻订票线程正准备添加一个票,还未结束。这个时刻另外一个线程需要进行出票,这样会造成数据的竞争。导致程序崩溃。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>

class Tickets
{
public:
	// 订票线程
	void book_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			std::cout << "订票开始,该线程执行:" << i << std::endl;
			//mtx_tickets_.lock();  // ①
			tickets_.push_back(i);
			//mtx_tickets_.unlock(); // ②
		}
		std::cout << "订票线程执行完毕." << std::endl;
	}
	// 出票线程
	void issue_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			if (!tickets_.empty()) // 订票量不为空
			{
				//mtx_tickets_.lock();  // ③
				int ticket = tickets_.front();
				tickets_.pop_front();
				//mtx_tickets_.unlock();  // ④
			}
			else
			{
				std::cout << "订票队列里面为空:" << i << std::endl;
			}
		}
		std::cout << "出票线程执行完毕." << std::endl;
	}

private:
	std::list<int> tickets_;
	std::mutex mtx_tickets_;
};

int main(void)
{
	Tickets tickets;

	std::thread bookTickets(&Tickets::book_tickets, &tickets);
	std::thread issueTickets(&Tickets::issue_tickets, &tickets);

	bookTickets.join();
	issueTickets.join();

	std::cout << "主线程执行结束." << std::endl;

	system("pause");
	return 0;
}

上面程序,我们简单的使用mutex进行演示互斥锁的用途:

  • 使用std::mutex声明一个类对象,调用lock()与unlock()对线程进行上锁,避免多个线程产生竞争机制。
  • 调用类成员函数的线程使用。std::thread bookTickets(&Tickets::book_tickets, &tickets);
死锁

  我们知道多个线程对共享数据进行访问时候添加互斥量会有效的避免数据竞争的问题,但是也会出现死锁的情况。死锁:线程卡死,无法继续有效执行。举例:看下面示意图。

  上图中展示死锁的关键错误处,主要在于不同互斥量上锁的顺序不一致导致的死锁现象。下面我们简单说下死锁的流程:

  • book_tickets函数线程执行,mtx_tickets_0互斥量对该线程上锁,抢到执行资源,但是此时CPU进行上下文切换…
  • issue_tickets函数线程开始执行,mtx_tickets_1互斥量对该线程上锁,抢到继续执行的资源…
  • 这个时候,mtx_tickets_0互斥量对订票线程函数继续向下执行,需要继续上锁mts_tickets_1,无法继续执行因为取票线程的mtx_tickets_1还未解锁,同时mtx_tickets_1互斥量对取票线程函数继续向下执行,需要加锁mtx_tickets_0,同样无法继续执行因为订票线程的mtx_tickets_0还未进行解锁。如此,就会导致线程卡死在这里。

  通过有序的对互斥量上锁来解决这个问题。下面为错误代码的示例,解决下面代码的问题,只需要将①③的顺序一致即可。或者我们可以直接使用std::lock()函数。并且std::lock(mtx_tickets_0, mtx_tickets_1)里面的互斥量对象的顺序不需要对齐。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>

class Tickets
{
public:
	// 订票线程
	void book_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			std::cout << "订票开始,该线程执行:" << i << std::endl;
			mtx_tickets_0.lock();  // ①
			mtx_tickets_1.lock();  // ①
			tickets_.push_back(i);
			mtx_tickets_0.unlock(); // ②
			mtx_tickets_1.unlock(); // ②
		}
		std::cout << "订票线程执行完毕." << std::endl;
	}
	// 出票线程
	void issue_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			if (!tickets_.empty()) // 订票量不为空
			{
				mtx_tickets_1.lock();   ③
				mtx_tickets_0.lock();   ③
				int ticket = tickets_.front();
				tickets_.pop_front();
				mtx_tickets_1.unlock();  // ④
				mtx_tickets_0.unlock();  // ④
			}
			else
			{
				std::cout << "订票队列里面为空:" << i << std::endl;
			}
		}
		std::cout << "出票线程执行完毕." << std::endl;
	}

private:
	std::list<int> tickets_;
	std::mutex mtx_tickets_0;
	std::mutex mtx_tickets_1;
};

int main(void)
{
	Tickets tickets;

	std::thread bookTickets(&Tickets::book_tickets, &tickets);
	std::thread issueTickets(&Tickets::issue_tickets, &tickets);

	bookTickets.join();
	issueTickets.join();

	std::cout << "主线程执行结束." << std::endl;

	system("pause");
	return 0;
}
小结

  互斥量为多线程进行数据访问竞争提供一种有效的解决办法。但是使用互斥量mutex时候需要注意一下问题:

  • 使用mutex时候,先lock()锁住数据,操作共享的数据,然后unlock()解锁。
  • 记住lock()加锁与unlock()解锁一定要成对使用,这里的成对使用是lock()加锁之后,千万记得unlock()。特别会在条件判断过程中,unlock()解锁一定要保证任何流程执行下来都与之前的lock()对应。
  • 为了避免使用mutex时候发生死锁,建议上锁时候互斥量的顺序一致。
  • 如果多个线程对共享数据都只读的话,那么不需要使用mutex;但是,如果存在读与写,那么一定使用互斥量进行操作,避免错误发生。同理
  • 关于使用mutex进行加锁lock()与解锁unlock()的位置也是需要仔细考虑的,例如上面的程序,如果你把订票线程加锁lock()与解锁unlock()在for循环外面,出票线程,那么整个程序的效率将会降低(这里我们几乎忽略上下文切换的耗时)。
  • 避免死锁产生,建议对上锁的互斥量顺序尽量保持一致。或者直接使用std::lock()函数来对多个互斥量对象上锁出进行操作。

  上面总结了mutex的一些使用策略与避坑方法,当然lock()与unlock()成对使用难免会忘记,C++11多线程还提供了lock_guard类上锁机制来降低这个mutex的代码写错的可能性。但是,mutex的灵活性更高,控制上锁的区域更加灵活。lock_guard就稍微逊色些mutex。

参考

https://zh.cppreference.com/w/cpp/thread/mutex
https://www.jianshu.com/p/ce782ac3150b

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于C++实现的轻级Web服务器源码+项目说明.zip 开发部署环境 操作系统: Ubuntu 16.04 编译器: g++ 5.4 版本控制: git 自动化构建: cmake 集成开发工具: CLion 编辑器: Vim 压测工具:WebBench 核心功能及技术 状态机解析HTTP请求,目前支持 HTTP GET、HEAD方法 添加定时器支持HTTP长连接,定时回调handler处理超时连接 使用 priority queue 实现的最小堆结构管理定时器,使用标记删除,以支持惰性删除,提高性能 使用epoll + 非阻塞IO + 边缘触发(ET) 实现高并发处理请求,使用Reactor编程模型 epoll使用EPOLLONESHOT保证一个socket连接在任意时刻都只被一个线程处理 使用线程池提高并发度,并降低频繁创建线程的开销 同步互斥的介绍 使用RAII手法封装互斥器(pthrea_mutex_t)、 条件变(pthread_cond_t)等线程同步互斥机制,使用RAII管理文件描述符等资源 使用shared_ptr、weak_ptr管理指针,防止内存泄漏 下一步开发计划 添加异步日志系统,记录服务器运行状态 增加json配置文件,支持类似nginx的多网站配置 提供CGI支持 类似nginx的反向代理和负载均衡 必要时增加可复用内存池。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值