C++11------unique_lock
unique_lock
unique_lock独占的是mutex对象,就是对mutex锁的独占。
用法:
(1)新建一个unique_lock 对象
(2)给对象传入一个std::mutex 对象作为参数;
std::mutex mymutex; unique_lock lock(mymutex);
因此加锁时新建一个对象lock,而这个对象生命周期结束后自动解锁。
#include <iostream> #include<thread> #include<unistd.h> #include<mutex> using namespace std; std::mutex mymutex; void sayHello() { int k=0; unique_lock<mutex> lock(mymutex); while(k<2) { k++; cout<<endl<<"hello"<<endl; sleep(2); } } void sayWorld() { unique_lock<mutex> lock(mymutex); while(1) { cout<<endl<<"world"<<endl; sleep(1); } } int main() { thread threadHello(&sayHello); thread threadWorld(&sayWorld); threadHello.join(); threadWorld.join(); return 0; }
std::unique_lock
unique_lock也可以加std::adopt_lock参数,表示互斥量已经被lock,不需要再重复lock。该互斥量之前必须已经lock,才可以使用该参数。
std::try_to_lock
可以避免一些不必要的等待,会判断当前mutex能否被lock,如果不能被lock,可以先去执行其他代码。这个和adopt不同,不需要自己提前加锁。举个例子来说就是如果有一个线程被lock,而且执行时间很长,那么另一个线程一般会被阻塞在那里,反而会造成时间的浪费。那么使用了try_to_lock后,如果被锁住了,它不会在那里阻塞等待,它可以先去执行其他没有被锁的代码。
#include "pch.h" #include <iostream> #include <thread> #include <vector> #include <list> #include <mutex> using namespace std; class A_Mutex { public: void inMsgRecvQueue() //把收到的消息(玩家命令)到一个队列的线程。100000次便于观察 { for (int i = 0; i < 100000; i++) { cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl; { //加上大括号可以让lock_guard提前(在处理其他代码前)解锁(超出作用域时(大括号)析构) //lock_guard <mutex> guard(my_mutex); //unique_lock <mutex> u_lock(my_mutex); //unique_lock /* //try_to_lock unique_lock <mutex> u_lock_try(my_mutex,try_to_lock); if (u_lock_try.owns_lock()) { //拿到了锁 msgRecvQueue.push_back(i); //....处理其他代码 } else { //没拿到锁 cout << "inMsgRecvQueue()执行,但没拿到锁,只能干别的事" << i << endl; } */ unique_lock <mutex> u_lock_defer(my_mutex, defer_lock); //没有加锁的my_mutex /* //lock与unlock的使用 u_lock_defer.lock(); //调用u_lock_defer的成员函数lock(),自带解锁,自己手工解锁也不会有问题(但画蛇添足) u_lock_defer.unlock(); //因为有一些非共享代码要处理,手工暂时解锁,体现灵活性 //处理一些非共享代码 u_lock_defer.lock(); //处理完后,再重新加锁 //再次加锁后,再处理共享代码 */ //成员函数try_lock if (u_lock_defer.try_lock() == true) { //拿到了锁 msgRecvQueue.push_back(i); //....处理其他代码 } else { //没拿到锁 cout << "inMsgRecvQueue()执行,但没拿到锁,只能干别的事" << i << endl; } msgRecvQueue.push_back(i); //...处理代码 //msgRecvQueue.push_back(i); //假设这个数字i就是我收到的命令,直接送到消息队列里去 } //....处理其他代码 } return; } bool outMsgLULProc(int &command) { //lock_guard <mutex> guard(my_mutex); //guard为对象名,使用lock_guard时,lock与unlock不能再使用 unique_lock <mutex> u_lock(my_mutex); //std::chrono::milliseconds dura(20000); //20000毫秒==20秒 //std::this_thread::sleep_for(dura); //sleep 20s if (!msgRecvQueue.empty()) //消息不为空 { command = msgRecvQueue.front();//返回第一个元素 msgRecvQueue.pop_front();//弹出第一个元素(移除) return true; } return false; } void outMsgRecvQueue()//把数据从消息队列中取出的线程 { int command = 0; for (int i = 0; i < 100000; i++) { if (outMsgLULProc(command) == true) { cout << "outMsgRecvQueue()执行" << i << endl; //可以对command进行数据处理 } else { //消息队列为空 cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << endl; } } cout << "end" << endl; return; } private: list <int> msgRecvQueue; //容器(消息队列),专门用于代表玩家发送过来的命令 mutex my_mutex; //创建了一个互斥量(一个锁) }; int main() { A_Mutex myobj_mutex; thread myOutnMsgObj(&A_Mutex::outMsgRecvQueue, &myobj_mutex); //第二参数是引用(就无需拷贝),才能保证线程里用的是同一对象 thread myInMsgobj(&A_Mutex::inMsgRecvQueue, &myobj_mutex); myOutnMsgObj.join(); myInMsgobj.join(); //一:unique_lock取代lock_guard : unique_lock <mutex> u_lock(my_mutex); 取代 lock_guard <mutex> guard(my_mutex); //unique_lock是个类模板。工作中,一般lock_guard()足够了,推荐使用。 //unique_lock比lock_guard灵活很多;效率上差一点,内存占用多一点 //二:unique_lock的第二个参数 //(2.1)std::adopt_lock //用adopt_lock的前提是互斥量已被lock,通知lock_guard的构造函数无需再lock //lock_guard的第二个参数:lock_guard <mutex> guard(my_mutex,std::adopt_lock); //adopt_lock 起标记作用 //unique_lock也可以带std::adopt_lock参数,含义相同 //(2.2)std::try_to_lock //会尝试用mutex的lock去锁定这个mutex,但如果没有锁定成功,也会立即返回,并不会一直阻塞 //用try_to_lock的前提是不能自己手动去lock //(2.3)std::defer_lock //用defer_lock的前提是不能自己手动去lock(同try_to_lock) //使用它并不会给mutex加锁,作用是初始化了一个没有加锁的mutex //三:unique_lock的成员函数 //(3.1)lock(),加锁 //(3.2)unlock(),解锁,虽然成员函数lock()自带解锁,但unlock可以更加灵活(比如有一些非共享代码要处理:可以暂时解锁再加锁) //(3.3)try_lock(),尝试给互斥量加锁,如果拿不到锁,则返回false(不阻塞),如果拿到了锁,返回true。 //(3.4)release(),返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系 //如果原来mutex对象处于加锁状态,需接管过来并负责解锁 //lock锁住的代码段越少,执行越快,整个程序运行效率越高 //锁住的代码的多少称为锁的 粒度。粒度一般用粗细来描述 //选择合适的粒度,是高级程序员的能力和实力的体现 //四:unique_lock所有权的传递 //unique_lock <mutex> u_lock_defer(my_mutex); 所有权概念: u_lock_defer拥有my_mutex的所有权 //u_lock_defer可以把自己对my_mutex的所有权 转移 给其他的unique_lock对象,但是不能 复制 //转移:unique_lock <mutex> u_lock_defer2(std::move(u_lock_defer)); //移动语义,相当于u_lock_defer2与my_mutex绑定在一起了。u_lock_defer则指向空,u_lock_defer2指向空 cout << "主线程执行完毕" << endl; //执行完这句,主线程退出 return 0; }