怎样用C++11创建多线程,以及当共享数据时关于互斥量和死锁的问题
使用容器的方法创建和管理多线程
把thresd的对象放到容器中,看起来像thread对象数组,使用这种方法对于我们一次创建大量线程并对其进行管理很方便
创建一个简单的多线程:
#include<iostream>
#include<thread>
#include<vector>
using namespace std;
void show(int i)
{
cout << "子线程开始执行,线程编号为:" << i << " id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
//.....
}
int main()
{
vector<thread> test;
for (int i = 0; i < 10; i++)
{
test.push_back(thread(show, i));
}
for (auto j = test.begin(); j != test.end(); ++j)
{
j->join();
}
cout << "主进程开始执行" << " id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
system("pause");
}
由结果可以知道:多个线程执行顺序是乱的,跟操作系统操作系统内部对线程的运行调度机制有关。
上面便是一种简单的创建多线程的方法。
共享数据:
分为两种
- 只读数据:是安全稳定的,不需要什么特别的手段,直接读就可以。
- 有读有写:2个线程写,8个线程读。如果不特殊处理,程序就会崩溃
处理方法,读的时候不能写,写的时候不能读。2个线程不能同时写,8个线程不能同时读
eg:火车票购票时,总共有10个购票窗口,1号窗口和2号窗口要购买同一张票,如果程序不加以处理,就会产生很严重的问题。
一,只读数据:
#include<iostream>
#include<thread>
#include<vector>
using namespace std;
vector<int> num = { 1, 2, 3 };
void show(int i)
{
cout << "子线程开始执行,线程编号为:" << i <<" 数据:"<< num[0] << " " << num[1] << " " << num[2] << " " << endl;
//.....
}
int main()
{
vector<thread> test;
for (int i = 0; i < 10; i++)
{
test.push_back(thread(show, i));
}
for (auto j = test.begin(); j != test.end(); ++j)
{
j->join();
}
cout << "主进程开始执行" << " id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
system("pause");
}

可以看出,尽管线程不是按照一定的顺序执行,但是数据都能够显示出来。
二,有读有写:
第二种方法可能如果两个线程同时产生读写操作,则会引发系统崩溃的问题,则需要通过互斥量等来解决问题。
解决方法:保护共享数据,操作时,用代码把共享数据锁住,操作数据,解锁
其他想操作共享数据的线程必须等待解锁,锁住,操作,解锁
案例:
网络游戏服务器,两个自己创建的线程,一个线程收集玩家命令(用一个数字表示),并把命令数据写到一个队列中。
另一个线程,从队列中取出玩家发来的命令,解析,然后执行玩家需要的命令
互斥量
一,互斥量(mutex)的基本概念
互斥量是个类对象,理解成一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回)
如果没锁成功,那么流程卡在lock()这里不断尝试去锁这把锁头
需要注意保护量的大小,不能过多也不能太少
二,互斥量的用法
- lock(),unlock()
步骤:先lock(),操作共享数据,unlock();
lock()和unlock()要成对使用,两者缺一不可,不允许调用不同数量的lock()和unlock(),否则程序可能崩溃 - 为了防止忘记unlock(),引入了一个std::lock_guard的类模板
lock_guard类模板:直接取代lock()和unlock();也就是说,用了lock_guard之后,就不能使用lock()和unlock()
lock_guard构造函数里执行了mutex.lock();
lock_guard析构函数里执行了mutex.unlock(); - std:lock()函数模板
能力:一次锁定多个互斥量:一次锁住两个或两个以上的互斥量(至少两个或两个以上,一个不行)
它不存在这种因为在多个线程中,因为锁的顺序问题而导致死锁的风险问题
如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回)
要么两个互斥量都锁住,要么两个都没锁住,如果只锁定了一个,另外一个没锁住,则它立刻把已经锁住的解锁
因此不用担心多个锁的顺序问题 - std::lock_guard的std::adopt_lock参数
std::adopt_lock是个结构体对象,起一个标记作用;作用就是这个互斥量已经lock(),
不需要再std::lock_guard(std::mutex)里面对对象进行再次lock()了。
主要可以与lock()或者std::lock()函数模板连用
假设现在有线程a和线程b,并且有两个锁x,y;
- lock(),unlock()使用方法
线程a:x.lock();y.lock(); ........... x.unlock();y.unlock();
线程b:x.lock();y.lock(); (x,y的顺序需要和线程a相同) ........... x.unlock();y.unlock();
- std::lock_guard的使用用法
线程a:std::lock_guard<mutex> abguard(x);
std::lock_guard<mutex> abguard(y); (因为该类模板会自动解锁,所以不用再使用unlock())
线程b:std::lock_guard<mutex> abguard(x);
std::lock_guard<mutex> abguard(y);
- std:lock()的使用方法
线程a:std::lock(x,y); ............ x.unlock();y.unlock();
线程b:std::lock(y,x); (x,y的顺序可以与线程a的不同) ............ x.unlock();y.unlock();
- std::lock_guard的std::adopt_lock参数
线程a:x.lock();y.lock(); std::lock_guard<mutex> abguard(x);
std::lock_guard<mutex> abguard(y);
线程b:std::lock(x,y); std::lock_guard<mutex> abguard(x,std::adopt_lock);
std::lock_guard<mutex> abguard(y,std::adopt_lock);
死锁问题:
举例:
比如我有两把锁(死锁这个问题,是由至少两个锁头也就是两个互斥锁才能产生):假设现在有金锁和银锁
两个线程a和线程b
- 线程a执行的时候,这个线程先锁金锁,把金锁lock()成功后,他去lock银锁
此时出现了上下文切换的情况 - 线程b执行,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会被lock()成功,线程B要去lock()金锁
此时此刻,死锁就产生了 - 线程a因为拿不到银锁头,流程走不下(所有后面有解锁金锁头的流程走不下去,所以金锁头解不开)
- 线程b因为拿不到金锁头,流程走不下(所有后面有解锁银锁头的流程走不下去,所以银锁头解不开)
- 此刻线程a和线程b就都无法执行下去,产生了死锁的情况
解决方法:
当有多个锁时,锁的顺序应该相同
因此在编写多线程的程序时,当出现多个锁时,需要注意锁的执行顺序,避免出现死锁的情乱。
代码:
#include<iostream>
#include<vector>
#include<thread>
#include<list>
#include<mutex>
using namespace std;
class A
{
public:
//把收到的玩家命令,入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex1.lock();
my_mutex2.lock();
msgRecvQueue.push_back(i); //把收到的命令,放到消息队列中
my_mutex1.unlock();
my_mutex2.unlock();
}
}
bool outMsgLUL(int &command)
{
//std::lock_guard<mutex> abguard(my_mutex);
//想要提前结束,则可以使用{},将这句话放到{}之内执行
//my_mutex1.lock();
//my_mutex2.lock();
lock(my_mutex2, my_mutex1);
std::lock_guard<mutex> abguard1(my_mutex1, std::adopt_lock);
std::lock_guard<mutex> abguard2(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty())
{
//消息不为空
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素,但不返回
//my_mutex1.unlock();
//my_mutex2.unlock();
return true;
}
//my_mutex1.unlock();
//my_mutex2.unlock();
return false;
}
//把数据从消息队列中取出的线程
void outMsgRecvRueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool YN = outMsgLUL(command);
if (YN)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
//可以考虑进行命令(数据)处理
//....
}
else
{
//消息队列为空
cout << "outMsgRecvQueue()执行,但是目前消息队列为空" << i << endl;
}
}
}
private:
list<int> msgRecvQueue;
mutex my_mutex1; //创建一个互斥量
mutex my_mutex2;
};
int main()
{
A a;
thread test1(&A::outMsgRecvRueue, &a);//第二个参数是引用才能保证
thread test2(&A::inMsgRecvQueue, &a);//第二个参数是引用才能保证
test1.join();
test2.join();
system("pause");
return 0;
}
更多推荐


所有评论(0)