目录
1 unique_lock()取代lock_guard()
上节我们知道了lock_guard()的妙用,它可以帮我们填掉我们容易忘记unlock()的坑。另一方面,这虽然很省我们的力气,但是并不灵活。unique_lock()比lock_guard(),更灵活,但效率差点,内存占用多点。unique_lock()的灵活性体现在unique_lock()的第二个参数:
1.1 unique_lock()的第二个参数
std::unique_lock<std::mutex> sbunique(my_mutex,第二个参数)
第二个参数1:std::adopt_lock:表示这个互斥量已经被lock了;就是通知unique_lock不需要在构造函数中再lock了。
前提是在之前已经拿到锁。
示例:
// project5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
//把收到的消息(玩家指令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
std::unique_lock<std::mutex> sbguard(my_mutex);//将unique_lock和mutex绑定,并lock()
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
}
}
bool outMsgState(int &command)
{
//感觉下面两句加起来就是std::lock_guard<std::mutex> sbguard(my_mutex);
my_mutex.lock();//先拿到锁。
std::unique_lock<std::mutex> sbguard(my_mutex,std::adopt_lock);//表示这个互斥量my_mutex已经被lock了;就是通知unique_lock不需要在构造函数中再lock了(前提是在之前已经拿到锁。)并且不用担心unlock()问题
if (!msgRecvQueue.empty())
{
cout << "outMsgRecvQueue()执行,取出一个元素 " << endl;
command = msgRecvQueue.front();//返回第一个元素,在前面取,但不检查元素是否存在。
msgRecvQueue.pop_front();//移除第一个元素,不返回。
//处理数据。
//……
return true;
}
return false;
}
// 把数据从消息队列中取出的线程。
void outMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
int command;
bool result = outMsgState(command);
if (result)
{
cout << "outMsgState is true, and command is " << command << endl;
}
else
{
cout << "outMsgRecvQueue()执行,但是目前消息队列为空 " << i << endl;
}
}
cout << "end......." << endl;
}
private:
std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
std::mutex my_mutex;// 创建一个互斥量(一把锁)
};
int main()
{
vector<thread> mythreads;
A myobja;
thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
cout << "主线程结束!" << endl;
}
第二个参数2:std::try_to_lock:表示尝试用mutex的lock去锁定mutex,但如果没有锁定成功,也会立即返回,并不会阻塞在那。
注意:用它的前提是,你自己不能先去lock。
示例:
// project5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
//把收到的消息(玩家指令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
//std::unique_lock<std::mutex> sbguard(my_mutex);//到这也会等待4秒
// 如果继续用上述方法拿锁,插入线程就会一直卡到这,当程序复杂时此线程有可能除了插入数据还要进行其他与取出线程不冲突的任务,所以我们不希望此线程一直卡在这。
std::unique_lock<std::mutex> sbguard(my_mutex,std::try_to_lock);// 可以这样解决,就不会卡到这了,但是要判断是否拿到锁
if (sbguard.owns_lock())// 因为没拿到锁就不应该插入数据
{
//拿到锁了就干活
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
}
else
{
//没拿到锁
cout << "我没拿到锁,但我可以去干点别的和取出任务不冲突的事。。" << endl;
}
}
}
bool outMsgState(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);//拿锁,且不用担心unlock()问题
// 注意:因为取出线程拿到锁了,这里要等待一段时间,也就是插入线程拿不到锁,而通过互斥量的概念知道拿不到锁就会一直等待。
std::chrono::milliseconds dura(4000);//4秒
std::this_thread::sleep_for(dura);//休眠一定时间,因为此线程已经拿到锁了,这会让插入元素时也是等待的。
if (!msgRecvQueue.empty())
{
cout << "outMsgRecvQueue()执行,取出一个元素 " << endl;
command = msgRecvQueue.front();//返回第一个元素,在前面取,但不检查元素是否存在。
msgRecvQueue.pop_front();//移除第一个元素,不返回。
//处理数据。
//……
return true;
}
return false;
}
// 把数据从消息队列中取出的线程。
void outMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
int command;
bool result = outMsgState(command);
if (result)
{
cout << "outMsgState is true, and command is " << command << endl;
}
else
{
cout << "outMsgRecvQueue()执行,但是目前消息队列为空 " << i << endl;
}
}
cout << "end......." << endl;
}
private:
std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
std::mutex my_mutex;// 创建一个互斥量(一把锁)
};
int main()
{
vector<thread> mythreads;
A myobja;
thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
cout << "主线程结束!" << endl;
}
第二个参数3:std::defer_lock:初始化了一个没有加锁的mutex。目的是为了使用unique_lock()的成员函数方便(成员函数看1.2).
注意:前提是不能先去lock。
示例:
// project5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
//把收到的消息(玩家指令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
std::unique_lock<std::mutex> sbguard(my_mutex,std::defer_lock);//初始化一个没有加锁的mutex;这个就很灵活了。这样我们可以在他的作用域内随意选取共享代码范围了。而且最后一个也不用担心unlock()问题。但是中间得注意。
// 然后就可以再unique_lock的作用域中随意的拿锁和解锁了
sbguard.lock();
// 处理共享代码
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
sbguard.unlock();
//处理非共享代码
sbguard.lock();
// 继续处理其他共享代码
}
}
bool outMsgState(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!msgRecvQueue.empty())
{
cout << "outMsgRecvQueue()执行,取出一个元素 " << endl;
command = msgRecvQueue.front();//返回第一个元素,在前面取,但不检查元素是否存在。
msgRecvQueue.pop_front();//移除第一个元素,不返回。
//处理数据。
//……
//my_mutex.unlock();
return true;
}
//my_mutex.unlock();
return false;
}
// 把数据从消息队列中取出的线程。
void outMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
int command;
bool result = outMsgState(command);
if (result)
{
cout << "outMsgState is true, and command is " << command << endl;
}
else
{
cout << "outMsgRecvQueue()执行,但是目前消息队列为空 " << i << endl;
}
}
cout << "end......." << endl;
}
private:
std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
std::mutex my_mutex;// 创建一个互斥量(一把锁)
};
int main()
{
vector<thread> mythreads;
A myobja;
thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
cout << "主线程结束!" << endl;
}
1.2 unique_lock()的成员函数
对于1.1的第三种情况,可以看出,使用std::defer_lock可以更灵活的控制其作用域内的互斥问题。可能上面的用法还不足以体现其灵活性,下面介绍几unique_lock()的成员函数,进一步体现:
① lock() 加锁和unlock() 解锁。为了处理一些非共享代码,就先解锁,等想继续处理共享代码了可以再锁上。1.1已经用到了。
② try_lock(): 尝试给互斥量加锁,如果没有拿到锁就返回false,否则true
③ release():返回他所管理的mutex对象指针,并释放所有权,也就是说,这个unique_lock和mutex不再有联系。
严格区分unlock()和release()的区别
如果原来mutex对象处于加锁状态,你有责任接管过来并负责解锁。(release返回的就是原始的mutex指针)
示例:
// project5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
//把收到的消息(玩家指令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
std::unique_lock<std::mutex> sbguard(my_mutex,std::defer_lock);//初始化一个没有加锁的mutex
if (sbguard.try_lock() == true)// 可以用unique_lock的成员函数try_lock()直接判断是否拿到锁,和上面那个std::unique_lock<std::mutex> sbguard(my_mutex,std::try_to_lock)一样啊
{
//拿到锁了就干活
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
}
else
{
//没拿到锁
cout << "我没拿到锁,但我可以去干点别的事。" << endl;
}
}
}
bool outMsgState(int &command)
{
my_mutex.lock();
std::unique_lock<std::mutex> sbguard(my_mutex,std::adopt_lock);
std::chrono::milliseconds dura(4000);//毫秒
std::this_thread::sleep_for(dura);//休眠一定时间,因为此线程已经拿到锁了,这会让插入元素时也是等待的。这里用unique_lock的成员函数try_lock()解决。
if (!msgRecvQueue.empty())
{
cout << "outMsgRecvQueue()执行,取出一个元素 " << endl;
command = msgRecvQueue.front();//返回第一个元素,在前面取,但不检查元素是否存在。
msgRecvQueue.pop_front();//移除第一个元素,不返回。
//处理数据。
//……
return true;
}
return false;
}
// 把数据从消息队列中取出的线程。
void outMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
int command;
bool result = outMsgState(command);
if (result)
{
cout << "outMsgState is true, and command is " << command << endl;
}
else
{
cout << "outMsgRecvQueue()执行,但是目前消息队列为空 " << i << endl;
}
}
cout << "end......." << endl;
}
private:
std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
std::mutex my_mutex;// 创建一个互斥量(一把锁)
};
int main()
{
vector<thread> mythreads;
A myobja;
thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
cout << "主线程结束!" << endl;
}
关于release()的用法如下:
//作用域
{
std::unique_lock<std::mutex> sbguard(my_mutex);//将unique_lock和mutex绑定,并lock()
std::mutex *ptx = sbguard.release();//sbguard1和my_mutex解绑了
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
ptx->unlock();//解绑以后得自己负责他的unlock了。
}
1.3 unique_lock()所有权的传递
所有权概念:
std::unique_lock<std::mutex> sbguard(my_mutex);
表示:sbguard拥有my_mutex的所有权,sbguard可以把自己对my_mutex的所有权转移给其他的unique_lock对象。但是,不能复制。
示例:
{
std::unique_lock<std::mutex> sbguard(my_mutex);//将unique_lock和mutex绑定,并lock()
//std::unique_lock<std::mutex> sbguard1(sbguard);// 复制所有权是非法的。
std::unique_lock<std::mutex> sbguard1(std::move(sbguard));// 转移所有权是可以的。
// 所有权转移了,所以此时sbguard解除了和my_mutex的关系
// 现在是sbguard1和my_mutex绑定一起了
std::mutex *ptx = sbguard1.release();//sbguard1和my_mutex解绑了
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
ptx->unlock();//解绑以后得自己负责他的unlock了。
}