一.创建和等待多个线程
- 多个线程执行的顺序是乱的,与操作系统内部对线程的调度机制有关。
- 主线程会等待所有的线程运行结束,最后主线程结束。
- 把thread对象放入容器里面管理,这对一次创建大量的线程并对线程进行管理很方便。
#include <bits/stdc++.h>
#include <thread>
using namespace std;
void myPrint(int num){
cout<<"线程编号:"<<num<<endl;
cout<<"线程结束,编号是:"<<num<<endl;
return;
}
int main(){
vector<thread> myThreads;
for(int i = 0; i < 10; i++){
myThreads.push_back(thread(myPrint,i)); //创建并开始执行线程
}
for(auto it = myThreads.begin(); it != myThreads.end(); it++){
it->join(); //等待10个线程都返回
}
cout<<"I love China!"<<endl;
return 0;
}
二.数据共享问题分析
1.只读的数据
是安全稳定的,不需要特别的处理手段;直接读就可以。
2.有读有写
读者写者问题。需要进行特殊的处理。因为写一个数据需要好几个步骤,有可能在写的过程中操作系统把当前时间片调度给了其他线程,就会导致程序出现不可预知的问题。
三.共享数据的保护案例代码
网络游戏服务器,有两个自己创建的线程,一个线程收集玩家命令(用数字代表玩家发的命令),并把命令写到一个队列中;另外一个线程从队列中取出玩家发送过来的命令,执行玩家的动作。
#include <bits/stdc++.h>
#include <thread>
using namespace std;
//成员函数作为线程的入口函数
class A{
public:
//把收到的玩家命令放到队列的一个线程 写
void inMsgRecvQueue(){
for(int i = 0; i < 100000; i++){ //模拟了100000次
cout<<"inMsgRecvQueue执行,插入一个元素:"<<i<<endl;
msgRecvQueue.push_back(i);
}
}
//处理玩家命令的线程 读
void outMsgRecvQueue(){
for(int i = 0; i < 100000; i++){ //模拟了100000次
if(!msgRecvQueue.empty()){ //消息队列不为空
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//处理数据
}else{
cout<<"outMsgRecvQueue执行,消息队列为空"<<i<<endl;
}
}
cout<<"end"<<endl;
}
private:
list<int> msgRecvQueue; //玩家发送的命令
};
int main(){
A obj;
thread myOut(&A::outMsgRecvQueue,ref(obj));//第二个参数是引用,才能保证线程里用的是同一个对象
thread myIn(&A::inMsgRecvQueue,ref(obj));//第二个参数是引用,才能保证线程里用的是同一个对象
myIn.join();
myOut.join();
return 0;
}
这个代码执行会出问题。
读线程和写线程同时开始运行,当写的时候,执行到msgRecvQueue.push_back(i)的时候,因为这个操作不是原子操作,运行到一半的时候,调度到读线程,执行msgRecvQueue.pop_front(),因为不是原子操作,当运行到一半的时候,又调度了到其他线程。这就会出现异常,程序不稳定。
版权声明:本文为jinjia649472189原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。