1、线程学习说明
本文主要记录了线程学习的笔记,主要以示例的形式讲解每个接口的使用以及优缺点。线程的理论知识还没有系统的整理,所以后面以具体代码为主,会在每个代码段介绍对应使用的函数。
文章主要是自己学习线程后,对线程学习笔记的整理。
2、线程与线程锁
在使用线程的过程中,如果存在数据的读写,不可避免的要使用到线程锁。使用线程锁的目的是为了保护共享内存,防止在一个线程写数据的时候另外一个数据在读这一个数据,这就会导致数据出现异常。最好的方式是将这一块的数据锁起来,在写这个数据的时候其他线程不能进入,等写操作完成;释放权限给读操作。
常用的线程锁有互斥量(后文主要介绍互斥量)、临界锁、自旋锁、条件变量等;
互斥量的使用:(mutex)
【1】lock() unlock() 需要成对使用,加锁后一定要解锁
【2】std::lock_guard类模板
示例主要介绍了线程与互斥量的使用,
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class dataShare
{
public:
// 存数据
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; i++)
{
m_Mutex.lock();
m_RecvQueue.push_back(i);
m_Mutex.unlock();
// m_Mutex.unlock(); // 代码中一定要避免出现多次unlock(),会报错
// doSomething
// ...
}
}
private:
std::list<int> m_RecvQueue;
//互斥量
std::mutex m_Mutex;
};
int main()
{
dataShare datas;
std::thread inMsgObj(&dataShare::inMsgRecvQueue, &datas);
inMsgObj.join();
system("pause");
return 0;
}
3、互斥锁注意事项
【1】互斥量是一个类对象,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁成功(成功标志是lock()函数返回)如果没锁成功,流程就会一直卡在lock()这里不断尝试
【2】互斥量使用时需要谨慎,范围保护太大,影响代码运行质量;保护范围太少,达不到保护的作用;
【3】为了防止忘记unlock(),引入std::lock_guard的类模板,类似于智能指针(unique_ptr<>);
为了较好的理解std::lock_guard的类模板的使用,下面给出lock_guard()的使用示例,只需要对上一段代码inMsgRecvQueue函数进行微调即可:
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; i++)
{
/*
1.使用lock_guard()后就不需要unlock释放线程
2.在lock_guard()使用时添加作用域,防止线程作用范围太广
*/
{
std::lock_guard<std::mutex> lockGuard(m_Mutex);
m_RecvQueue.push_back(i);
}
// doSomething
// ...
}
}
4、死锁
两个线程A、B,两个锁(lockA,unlockA)、(lockB,unlockB),其中线程A使用(lockA,ublockB),线程B使用(lockB,ublockA),此时就成了死锁
描述的比较简单,可以自己思考一下,代码中也可以试试,死锁解决比较简单,定位问题会比较难,所以代码中需要注意;
下面示例可能看着有点傻,但是在实际项目中,框架比较复杂的前提下还是会出现这类问题,在此主要还是介绍一下死锁;
class dataShare
{
public:
// 存数据
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; i++)
{
m_Mutex.lockA();
m_Mutex.lockB();
m_RecvQueue.push_back(i);
mutexA.unlock();
mutexB.unlock();
// doSomething
// ...
}
}
// 取数据
void outMsgRecvQueue()
{
for (int i = 0; i < 1000; i++)
{
m_Mutex.lockB();
m_Mutex.lockA();
if(!m_RecvQueue.empty())
{
cout<< m_RecvQueue.front() << endl; // 返回第一个元素,但是不检查元素是否存在;
m_RecvQueue.pop_front(); // 移除第一个元素但不反回
}
mutexA.unlock();
mutexB.unlock();
}
}
private:
std::list<int> m_RecvQueue;
//互斥量
std::mutex m_MutexA;
std::mutex m_MutexB;
};
避免使用死锁可以使用std::lock_guard()
下面一个示例也可以避免死锁
void inMsgRecvQueue()
{
for (int i = 0; i < 1000; i++)
{
cout << "input list i:" << i << endl;
// 相当于每个互斥量都调用了lock(),避免出现死锁
std::lock(mutexA, mutexB);
// 使用lock_guard的类模板,可以避免unlock()
std::lock_guard<std::mutex> guard1(mutexA, std::adopt_lock);
std::lock_guard<std::mutex> guard1(mutexB, std::adopt_lock);
m_RecvQueue.push_back(i);
mutexA.unlock();
mutexB.unlock();
// doSomething
// ...
}
}