【C++11多线程】线程同步之线程互斥:mutex、lock_guard、unique_lock

1.mutex

头文件 #include <mutex>

mutex 是一个类,其源码如下图所示。

在这里插入图片描述

在这里插入图片描述

互斥锁其实就是一个 mutex 类对象,多个线程尝试用成员函数 lock() 来加锁,只有一个线程能锁定成功,成功的标志是 lock() 函数返回,如果没有锁成功,那么流程将卡在 lock() 这里不断尝试去锁定。

lock() 和 unlock() 要成对使用,每调用一次 lock(),必然要调用一次 unlock(),非对称数量的调用都会导致代码的不稳定甚至崩溃。

注意:如果程序有多个出口,那么每个出口都要调用一次 unlock()。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		mtx.lock();
		cout << index << " => " << ticketCount << endl;
		ticketCount--;
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

上面的代码存在问题,当 ticketCount 1 1 1 时,会出现下图的结果,可以使用锁+双重判断来解决。

在这里插入图片描述

2.锁+双重判断

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		mtx.lock();
		// 临界区代码段
		if (ticketCount > 0) // 双重判断
		{
			cout << index << " => " << ticketCount << endl;
			ticketCount--;
		}
		mtx.unlock();
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

3.lock_guard

为了防止大家忘记 unlock(),引入了一个叫做 std::lock_guard 的类模板,你忘记 unlock() 不要紧,它替你 unlock(),其源码如下图所示。

在这里插入图片描述

3.1 lock_guard的构造函数

在构造函数中,执行了 lock()。

3.2 lock_guard的析构函数

在离开作用域时,调用析构函数,执行了 unlock()。

3.3 lock_guard禁止拷贝构造和拷贝赋值

lock_guard 禁止拷贝构造和拷贝赋值,也就是说,lock_guard 不能用在函数参数传递或返回过程中,只能用在简单的临界区代码段的互斥操作中。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			lock_guard<std::mutex> mylock(mtx);
			// 临界区代码段
			if (ticketCount > 0)
			{
				cout << index << " => " << ticketCount << endl;
				ticketCount--;
			}
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.unique_lock

unique_lock 也是一个类模板,与 lock_guard 相比,它有自己的参数和成员函数可以更加灵活地进行锁的操作。

4.1 unique_lock的析构函数

在这里插入图片描述

在离开作用域时,调用析构函数,执行了 unlock()。

4.2 unique_lock的构造函数

在这里插入图片描述

4.2.1 没有第二个参数

如果构造函数没有第二个参数,则默认执行 lock(),此时 unique_lock 和 lock_guard 没有什么区别,都是通过析构函数来 unlock() 的。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			unique_lock<std::mutex> mylock(mtx);
			// 临界区代码段
			if (ticketCount > 0)
			{
				cout << index << " => " << ticketCount << endl;
				ticketCount--;
			}
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.2.2 第二个参数是std::adopt_lock

如果构造函数的第二个参数是 std::adopt_lock,表示这个互斥量已经被 lock() 了,不需要再重复 lock(),即“假设互斥量已经 lock() 成功,不需要再在构造函数中 lock() 这个互斥量了”。使用 std::adopt_lock 参数的前提是这个互斥量之前必须被 lock() 过。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			mtx.lock();
			unique_lock<std::mutex> mylock(mtx, std::adopt_lock);
			// 临界区代码段
			if (ticketCount > 0)
			{
				cout << index << " => " << ticketCount << endl;
				ticketCount--;
			}
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.2.3 第二个参数是std::try_to_lock

如果构造函数的第二个参数是 std::try_to_lock,则会尝试用 lock() 去锁定这个互斥量,但如果没有锁定成功,也会立即返回,并不会阻塞在那里。也就是说,会判断当前互斥量能否被 lock(),如果不能被 lock(),可以先去执行其他代码,避免一些不必要的等待。使用 std::try_to_lock 参数的前提是这个互斥量不能提前加锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			unique_lock<std::mutex> mylock(mtx, std::try_to_lock);
			if (mylock.owns_lock() == true)
			{
				// 如果加锁成功,则执行临界区代码段
				if (ticketCount > 0)
				{
					cout << index << " => " << ticketCount << endl;
					ticketCount--;
				}
			}
			else
			{
				// 如果加锁失败,则执行一些不是临界区的其它代码
				cout << "lock fail" << endl;
			}
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.2.4 第二个参数是std::defer_lock

如果构造函数的第二个参数是 std::defer_lock,表示暂时先不 lock(),之后手动去 lock(),即初始化了一个没有加锁的互斥量。使用 std::defer_lock 参数的前提是这个互斥量不能提前加锁。

std::defer_lock 参数一般和 unique_lock 的成员函数搭配起来使用。

4.3 unique_lock的成员函数

在这里插入图片描述

4.3.1 lock()和unlock()

当使用了 std::defer_lock 参数时,在创建 unique_lock 对象时就不会自动加锁了,此时就需要借助成员函数 lock() 来进行手动加锁,当然也有 unlock() 来手动解锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		unique_lock<std::mutex> mylock(mtx, std::defer_lock); // mtx没有加锁
		mylock.lock();
		// 如果加锁成功,则执行临界区代码段
		if (ticketCount > 0)
		{
			cout << index << " => " << ticketCount << endl;
			ticketCount--;
		}
		mylock.unlock();
		// 如果没有加锁,则执行一些不是临界区的代码
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.3.2 try_lock()

当使用了 std::defer_lock 参数时,在创建 unique_lock 对象时就不会自动加锁了,此时可以调用 try_lock() 函数尝试给互斥量加锁,如果拿到了锁,返回 true;如果拿不到锁,返回 false。try_lock() 函数是不阻塞的。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		{
			unique_lock<std::mutex> mylock(mtx, std::defer_lock); // mtx没有加锁
			if (mylock.try_lock() == true)
			{
				// 如果加锁成功,则执行临界区代码段
				if (ticketCount > 0)
				{
					cout << index << " => " << ticketCount << endl;
					ticketCount--;
				}
			}
			else
			{
				// 如果加锁失败,则执行一些不是临界区的代码
				cout << "lock fail" << endl;
			}
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.3.3 release()

release() 函数:解除 unique_lock 和 mutex 对象的联系,并将原 mutex 对象的指针返回出来。如果 mutex 对象之前已经加锁了,那么后面需要自己手动 unlock() 解锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

int ticketCount = 100; // 一共有100张车票,由三个窗口一起卖票

std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
	while (ticketCount > 0)
	{
		unique_lock<std::mutex> mylock(mtx); // mtx自动加锁
		std::mutex* pmtx = mylock.release();
		// 临界区代码段
		if (ticketCount > 0)
		{
			cout << index << " => " << ticketCount << endl;
			ticketCount--;
		}
		pmtx->unlock(); // 自己负责手动unlock()解锁
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

int main()
{
	list<std::thread> threadList;

	for (int i = 1; i <= 3; ++i)
	{
		threadList.push_back(std::thread(sellTicket, i));
	}

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

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

	return 0;
}

4.4 unique_lock禁止拷贝构造和拷贝赋值

在这里插入图片描述

4.5 unique_lock允许移动构造和移动赋值

在这里插入图片描述

与 lock_guard 不同,unique_lock 不仅可以用在简单的临界区代码段的互斥操作中,还能用在函数参数传递或返回过程中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值