目录
1.unique_lock取代lock_guard
unique_lock是一个类模板,使用方法上与lock_guard完全一致。
工作中一般用lock_guard。lock_guard取代了mutex的lock()和unlock()。unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。
用法示例:
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex);
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 1000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result <<",这是第"<<i<<"次取操作"<<",此时队列中还剩下"<< msgRecvQueue.size()<<"个元素"<< endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
2.unique_lock的第二个参数
2.1 std::adopt_lock
表示互斥量已经被lock了,在前面必须有互斥量已经被lock的代码,否则会报异常。
用adopt_lock的前提是,这个线程之前已经被自己lock住了互斥量
std::adopt_lock标记的效果就是“假设调用方线程已经拥有了互斥的所有权(已经lock成功了)”,通知lock_guard不需要在构造函数中lock这个互斥量了。unique_lock也可以带这个标记,含义相同。就是不需要在unique_lock构造函数中再次lock这个互斥量。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; ++i)
{
mymutex.lock();
std::unique_lock<std::mutex> sbguard1(mymutex,std::adopt_lock);
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
//std::chrono::milliseconds dura(2000);//2000毫秒=2秒
//std::this_thread::sleep_for(dura);//休息一定时长
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 1000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result <<",这是第"<<i<<"次取操作"<<",此时队列中还剩下"<< msgRecvQueue.size()<<"个元素"<< endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
2.1 std::try_to_lock
尝试用mutex的lock方法去锁定这个mutex,如果没有锁定成功,线程也会立即返回,并不会阻塞直到互斥量被释放。
用try_to_lock的前提是,这个线程之前没有自己lock住互斥量
就好像有人在用公厕,你试着去开门,门没打开你不会一直等在那里,而是去干别的事了
我们改造下程序,让其先执行outMsgRecvQueue线程,在执行inMsgRecvQueue线程。
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
在每一次执行outMsgRecvQueue线程后,都会令其休息一段时间(2000ms=2s)。
std::chrono::milliseconds dura(20);//2000毫秒=2秒
std::this_thread::sleep_for(dura);//休息一定时长
由于outMsgRecvQueue执行的时候先上锁,再等待2s,再读取并删除数据,再释放锁。这时候inMsgRecvQueue才能给互斥量上锁。在进行写数据。所以每次inMsgRecvQueue都会等待outMsgRecvQueue至少2s执行完后获得锁后才执行。所以inMsgRecvQueue会被outMsgRecvQueue阻塞。因此让inMsgRecvQueue判断互斥量状态,如果可以上锁,那么就锁住写数据;不能上锁,就干别的事。
std::unique_lock<std::mutex> sbguard1(mymutex, std::try_to_lock);//尝试去锁住互斥量,没锁住的话会立即返回
if (sbguard1.owns_lock())
{
//拿到了锁,执行这段
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
else
{
//没拿到锁,执行这段
cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
}
但是实际情况inMsgRecvQueue很难拿到锁,因为outMsgRecvQueue休息时间太长了。我们将outMsgRecvQueue休息时间缩短到20ms,并增加了inMsgRecvQueue的尝试次数。inMsgRecvQueue在第2590次的时候抢到了锁插入成功。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex, std::try_to_lock);//尝试去锁住互斥量,没锁住的话会立即返回
if (sbguard1.owns_lock())
{
//拿到了锁,执行这段
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
else
{
//没拿到锁,执行这段
cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
}
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
std::chrono::milliseconds dura(20);
std::this_thread::sleep_for(dura);//休息一定时长
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
2.1 std::defer_lock
初始化一个没有加锁的mutex,以便使用unique_lock的成员函数
用defer_lock的前提是,这个线程之前没有自己lock住互斥量
3.unique_lock的成员函数
3.1 lock()
unique_lock也有自己的成员函数:
std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();//不用自己unlock
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
完整代码
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();//不用自己unlock
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
3.2 unlock()
因为可能有一些非共享代码要处理,可能需要提前解锁,所以unique_lock也支持自定义位置解锁。这样就不用等到unique_lock析构的时候自动解锁了。
std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();
sbguard1.unlock();
//。。。。处理非共享数据
sbguard1.lock();//处理完非共享数据后再解锁
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
//析构时自动解锁
完整代码:
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
sbguard1.lock();
sbguard1.unlock();
//。。。。处理非共享数据
sbguard1.lock();//处理完非共享数据后再解锁
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
//析构时自动解锁
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
3.3 try_lock()
尝试给互斥量加锁,如果拿不到锁,则false,反之为true。这个不阻塞。
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex, std::defer_lock);//没有加锁的mymutex
if (sbguard1.try_lock())
{
//拿到了锁,执行这段
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
else
{
//没拿到锁,执行这段
cout << "\ninMsgRecvQueue()执行,但是没有拿到锁" << i << endl;
}
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
std::chrono::milliseconds dura(20);
std::this_thread::sleep_for(dura);//休息一定时长
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
inMsgRecvQueue在第5680次的时候抢到了锁插入成功
3.4 release()
返回它所管理的mutex对象指针并释放所有权。也就是说,unique_lock和mutex不再有关系。如果原来mutex处于加锁状态,需要自己接管过来并解锁
std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1,并给mymutex加锁
std::mutex *ptx = sbguard1.release();//解除unique_lock与mymutex关系,但此时mymutex仍然处于加锁状态
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
ptx->unlock();//处理完共享数据后需要接管过来并解锁
完整代码:
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1,并给mymutex加锁
std::mutex *ptx = sbguard1.release();//解除unique_lock与mymutex关系,但此时mymutex仍然处于加锁状态
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
ptx->unlock();//处理完共享数据后需要接管过来并解锁
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
lock的代码越少,执行越快,程序运行效率越高。锁住的代码叫做锁的粒度,一般用粗细表示。锁的代码少叫做细粒度,多就是粗粒度。选择合适粒度的代码进行保护。
4.unique_lock所有权的传递
所有权:sbguard1拥有mymutex的所有权,sbguard1可以把自己对mymutex的所有权转移给其他的unique_lock对象。所有权可以转移但是不可复制
所有权转移:方法1 移动语义
std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1
std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));//移动语义,现在相当于sbguard2和mymutex绑定到一起了
所有权转移:方法2 函数返回
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tmpguard(mymutex);
return tmpguard;//从函数返回局部的unique_lock对象是可以的。tmpguard会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
}
std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();
完整代码:
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include<mutex>
using namespace std;
//用成员函数作为线程函数
class GameSever {
public:
//所有权转移:方法2
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tmpguard(mymutex);
return tmpguard;//从函数返回局部的unique_lock对象是可以的。tmpguard会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
}
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
//所有权转移:方法1
//std::unique_lock<std::mutex> sbguard1(mymutex);//绑定unique_lock与mymutex关系为sbguard1
//std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));//移动语义,现在相当于sbguard2和mymutex绑定到一起了
std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();
cout << "\ninMsgRecvQueue()执行,插入元素为" << i << endl;
msgRecvQueue.push_back(i);
}
return;
}
int outLULProc()
{
std::unique_lock<std::mutex> sbguard1(mymutex);
if (!msgRecvQueue.empty())
{
int cmd = msgRecvQueue.front();
msgRecvQueue.pop_front();
return cmd;
}
return -1;
}
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
int result = outLULProc();
if (result != -1)
{
mymutex.lock();
cout << "\noutMsgRecvQueue已执行,取出一个元素" << result << ",这是第" << i << "次取操作" << ",此时队列中还剩下" << msgRecvQueue.size() << "个元素" << endl;
//....这里考虑对读取的数据result进行处理
mymutex.unlock();
}
else//消息队列为空
{
mymutex.lock();
cout << "\noutMsgRecvQueue()已执行,但是目前消息队列为空未取出元素" << ",这是第" << i << "次取操作" << endl;
mymutex.unlock();
}
//cout << "\noutMsgRecvQueue执行完毕" << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex mymutex;
};
int main()
{
GameSever gs;
thread myOutMsgObj(&GameSever::outMsgRecvQueue, ref(gs));
thread myInMsgObj(&GameSever::inMsgRecvQueue, ref(gs));
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}```