怎样用C++11创建多线程,以及当共享数据时关于互斥量和死锁的问题

it2026-02-26  8

使用容器的方法创建和管理多线程

把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)