线程间互斥-mutex互斥锁和lock_guard

要点

  • 锁+双重判断的技法

  • 竟态条件:多线程程序执行的结果一致,不会随着CPU对线程不同的调用顺序

线程间安全实例——3个窗口同时卖票

线程不安全的代码如下

int ticketCount = 100; // 100张车票
// 模拟10个窗口同时卖票
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		//cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
		cout << ticketCount << endl; // 打印当前剩余票数
		ticketCount--;
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
}
int main()
{
	list<std::thread> tlist;
	for (int i = 1; i <= 3; ++i)
	{
		tlist.push_back(std::thread(sellTicket, i));
	}

	for (std::thread& t : tlist)
	{
		t.join();
	}

	cout << "所有窗口卖票结束!" << endl;
	return	0;
}

输出的部分结果里有很多重复的数字,相当于同一张票被卖出多次,原因在于

ticketCount–; 是线程不安全的,理由如下

在这里插入图片描述

某一时刻ticketCount = 99,thread1此时调用ticketCount–,执行到sub eax时执行线程切换到另一个线程thread2中,此时ticketCount仍为99,执行完3条汇编后ticketCount = 98,此时再切换回thread1,继续执行完后面汇编,也使ticketCount = 98,这里就导致同时输出两次98;

解决方法

保证某一线程ticketCount–没做完,其他线程不允许做ticketCount–操作,可以使用加mutex互斥锁的方法:

void sellTicket(int index)
{
	mtx.lock();
	while (ticketCount > 0)
	{
		//cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
		cout << ticketCount << endl; // 打印当前剩余票数
		ticketCount--;
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
	mtx.unlock();
}

但是这样的加锁问题在于锁粒度太大,可以进一步缩小,采用锁+双重判断的方法:

void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		mtx.lock();
		if (ticketCount > 0) // !!!
		{
			cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
			//cout << ticketCount << endl; // 打印当前剩余票数
			ticketCount--;
		}
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
}

这里if (ticketCount > 0) 判断必须加,如果不加,当ticketCount = 1时,切换到其他线程卖完后ticketCount = 0 ,切换回原线程继续卖票就变成可以卖第0张票,不合法

lock_guard和unique_lock

lock_guard

lock_guard不能用在函数参数传递返回过程中,只能用在简单的临界区代码段互斥操作;

类似于scoped_str

lock_guard是对std::mutex的封装,拷贝构造和赋值函数被delete,它是RAII技术的实践,创建对象时就加锁,出作用域析构调用解锁,用lock_guard替换上面案例的mutex:

void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		//mtx.lock();
		{
			lock_guard<std::mutex> lock(mtx); // 
			if (ticketCount > 0)
			{
				cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
				ticketCount--;
			}
		} 
		//mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
}

unique_lock

unique_lock不仅可用在函数参数传递或返回过程中,还能用在函数调用中,如 和条件变量函数一起使用:

unique_lock<std::mutex> lck(mtx);

cv.wait(lck); // => #1.使线程进入等待状态 #2.lck.unlock可以把mtx给释放掉

unique_lock 也是对mutex的封装,它也可以像lock_guard一样使用,同时它支持手动调用lock unlock,会帮助检查是否忘记调用unlock,使用例子如下:

void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			unique_lock<std::mutex> lck(mtx);
			lck.lock();
			if (ticketCount > 0)
			{
				cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
				ticketCount--;
			}
			lck.unlock();
		} 
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值