目录
互斥量mutex
class A
{
public:
// 向消息队列中插入元素
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i);
my_mutex.unlock();
}
}
// 取出元素的加解锁操作
bool outMsgLULproc(int& command)
{
std::lock_guard<mutex> guard(my_mutex);
//my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//my_mutex.unlock();// 注意不同分支退出前都要unlock
return true;
}
//my_mutex.unlock();
return false;
}
// 从消息队列中取出首元素
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; i++)
{
bool result = outMsgLULproc(command);// 将加解锁与其他处理代码分开
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
// 其他处理代码...
}
else
{
cout << "outMsgRecvQueue()执行,目前消息队列为空" << endl;
}
}
}
private:
list<int> msgRecvQueue; // 消息队列
mutex my_mutex; // 互斥量
};
int main()
{
A a;
thread inThread(&A::inMsgRecvQueue, std::ref(a));
thread outThread(&A::outMsgRecvQueue, std::ref(a));
inThread.join();
outThread.join();
return 0;
}
- lock()与unlock()使用注意事项:有lock必然有unlock(),两者一一对应。注意语句不同分支return之前,要unlock()。
- lock_guard()一条语句取代了lock()与unlock();lock_guard构造函数里执行了mutex::lock(),在析构函数里执行了mutex::unlock()。
死锁
class A
{
public:
void thread1()
{
for (int i = 0; i < 100000; i++)
{
cout << "thread1()执行第" << i << "次" << endl;
my_mutex1.lock();
my_mutex2.lock();
my_mutex2.unlock();
my_mutex1.unlock();
}
}
void thread2()
{
for (int i = 0; i < 100000; i++)
{
cout << "thread2()执行第" << i << "次" << endl;
my_mutex2.lock();
my_mutex1.lock();
my_mutex1.unlock();
my_mutex2.unlock();
}
}
private:
mutex my_mutex1;
mutex my_mutex2;
};
int main()
{
A a;
thread inThread(&A::thread1, std::ref(a));
thread outThread(&A::thread2, std::ref(a));
inThread.join();
outThread.join();
return 0;
}
- 死锁产生的前提是至少要有两个互斥量(两把锁),两个互斥量在两个子线程中加锁的顺序正好相反。
- 当thread1执行时,先对my_mutex1加锁,然后当要给my_mutex2加锁时,被上下文切换了,此时thread2执行,先对my_mutex2加锁,然后要对my_mutex2加锁时由于已被thread1上锁,所以两个线程只能互相等待对方解锁,此时死锁产生。
- 一般解决方案:
- 保证两个互斥量上锁的顺序一致
- 使用std::lock()函数模板
std::lock()函数模板
- 用于处理多个互斥量,给多个互斥量同时加锁
- 不存在因为加锁的顺序问题导致死锁
- 如果互斥量中有一个没有锁住,则std::lock()会立即把已经锁住的解锁,直到所有互斥量都锁住,才继续往下运行。
std::lock(my_mutex1, my_mutex2);
my_mutex1.unlock();
my_mutex2.unlock();
- 但是此时还需要手动解锁。可以使用std::lock_guard的std::adopt_lock参数
std::adopt_lock参数
std::lock(my_mutex1, my_mutex2);
std::lock_guard<mutex> guard1(my_mutex1, std::adopt_lock);
std::lock_guard<mutex> guard2(my_mutex2, std::adopt_lock);
- std::lock_guard是个RAII(Resource Acquisition is Initialization)资源获取即初始化类。在构造函数中申请内存,析构函数中释放内存。容器、智能指针都是RAII类
- std::adopt_lock是个结构体对象,起到标记作用:表示这个互斥量已经加过锁了,不需要在std::lock_guard中再上锁
std::unique_lock()
与lock_gard()的区别
- lock_guard更加的高效,只能在析构函数中解锁
- unique_lock更加的灵活,但是内存占用更多,效率低
std::try_to_lock参数
- 使用try_to_lock的前提是不能先去锁这个互斥量
- unique_lock会尝试去锁这个互斥量,但如果没有锁定成功,会立即返回,并不会阻塞在那里
unique_lock<mutex> guard1(my_mutex, std::try_to_lock);
if (guard1.owns_lock())
{
cout << "thread1()尝试执行第" << i << "次————成功" << endl;
}
else
{
cout << "thread1()尝试执行第" << i << "次————失败" << endl;
}
std::defer_lock参数
-
使用defer_lock的前提是不能先去锁这个互斥量
-
defer_lock并没有给mutex加锁,只是初始化了一个没有加锁的mutex,将mutex和一个unique_lock绑定到一起
unique_lock<mutex> guard(my_mutex, std::defer_lock);
guard.lock();// 注意这里调用unique_lock的lock()成员函数
成员函数lock()、unlock()、try_lock()、release()
unique_lock<mutex> guard(my_mutex, std::defer_lock);
guard.lock();
// 处理共享数据代码
//...
guard.unlock();
// 临时处理非共享数据代码
//...
guard.lock();
// 处理共享数据代码
//...
if(guard.try_lock()==true)
{
// 处理共享数据代码
//...
}
else
{
cout<<"暂未拿到锁"<<endl;
}
mutex* pm = guard.release();
pm->unlock();// 注意手动解锁
- lock()加锁
- unlock()解锁,临时处理非共享数据代码
- try_lock()尝试给互斥量加锁,不会导致线程阻塞
- release()返回它所管理的mutex指针,并释放所有权。也就是,这个unique_lock和mutex不再有关系。注意区别release()和unlock()的区别。
粒度
- 互斥量锁住的代码多少称为锁的粒度。
- 如果锁住的代码少,粒度叫细,执行效率高;如果锁住的代码多,粒度叫粗,执行效率低;
所有权传递
// 方法一
unique_lock<mutex> guard1(my_mutex);
// unique_lock<mutex> guard2(guard1);// 复制所有权是非法的
unique_lock<mutex> guard2(std::move(guard1));// 移动语义,现在相当于guard2和my_mutex绑定到了一起,guard1指向空
// 方法二
unique_lock<mutex> rtn_unique_lock()
{
unique_lock<mutex> tmpguard(my_mutex);
return tmpguard;// 从函数返回一个局部的unique_lock对象是可以的。返回这种局部对象会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
}
unique_lock<mutex> guard = rtn_unique_lock();
单例设计模式共享数据
单例类
// 单例类
class MyCAS
{
private:
MyCAS() {
};// 私有化构造函数
static MyCAS* m_instance;// 静态成员变量
public:
static MyCAS* GetInstance() {
if (m_instance == NULL)
{
m_instance = new MyCAS();
}
return m_instance;
}
};
MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化
int main()
{
MyCAS* a = MyCAS::GetInstance();
MyCAS* b = MyCAS::GetInstance();
return 0;
}
多线程中创建单例类
-
建议在创建其他线程之前,在主线程中初始化单例类对象。
-
当在多线程中创建单例类时,可能会面临这个问题:当线程1在初始化单例类对象执行GetInstance()函数时,执行完if (m_instance == NULL)语句后被切换到线程2执行GetInstance()函数,由于线程1并没有执行m_instance = new MyCAS()语句,导致线程2在执行if (m_instance == NULL)时判断没有初始化m_instance,进而执行了m_instance = new MyCAS()语句,当再切换回线程1时,又执行了一遍m_instance = new MyCAS()语句,最终导致这个单例类对象被创建了两个。总之,当在多线程中创建单例类时,可能会创建多个单例类对象。
std::mutex MyCAS_mutex;// 互斥量
// 单例类
class MyCAS
{
private:
MyCAS() {
};// 私有化构造函数
static MyCAS* m_instance;// 静态成员变量
public:
static MyCAS* GetInstance()
{
if (m_instance == NULL)// 双重锁定(双重检查)
{
unique_lock<mutex> guard(MyCAS_mutex);// 加锁
if (m_instance == NULL)
{
m_instance = new MyCAS();
}
}
return m_instance;
}
};
MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化
void CreateMyCAS()
{
MyCAS* p = MyCAS::GetInstance();
return;
}
int main()
{
//MyCAS* a = MyCAS::GetInstance();
//MyCAS* b = MyCAS::GetInstance();
thread my_thread1(CreateMyCAS);
thread my_thread2(CreateMyCAS);
my_thread1.join();
my_thread2.join();
return 0;
}
- 双重锁定的作用:仅在初始化时加锁,提高效率。
std::call_once
- std::call_once能够保证函数只被调用一次,需要与一个标记结合使用
std::mutex MyCAS_mutex;
std::once_flag g_flag;
// 单例类
class MyCAS
{
private:
MyCAS() {
};// 私有化构造函数
static MyCAS* m_instance;// 静态成员变量
static void CreateInstance()// 只被调用一次1次的代码
{
m_instance = new MyCAS();
cout << "CreateInstance被执行了" << endl;
return;
}
public:
static MyCAS* GetInstance()
{
std::call_once(g_flag, CreateInstance);
return m_instance;
}
};
MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化
void CreateMyCAS()
{
MyCAS* p = MyCAS::GetInstance();
return;
}
int main()
{
//MyCAS* a = MyCAS::GetInstance();
//MyCAS* b = MyCAS::GetInstance();
thread my_thread1(CreateMyCAS);
thread my_thread2(CreateMyCAS);
my_thread1.join();
my_thread2.join();
return 0;
}