一.创建和等待多个线程
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <thread>
using namespace std;
//线程入口函数
void myPrint(int num)
{
cout << "myPrint线程开始执行...num=" << num << endl;
//...
//...
cout << "myPrint线程结束执行...num=" << num << endl;
return;
}
int main()
{
vector<thread> myThreads;
//创建10个线程,线程入口函数都使用myPrint
for (int i = 0; i < 10; i++)
{
myThreads.push_back(thread(myPrint, i)); //创建10个线程并开始执行
}
for (vector<thread>::iterator iter = myThreads.begin(); iter != myThreads.end(); iter++)
{
iter->join(); //等待10个线程都返回
}
cout << "主线程..." << endl; //最后执行这一句,整个进程退出
return 0;
}
//a):多个线程执行顺序是乱的,跟操作系统内部对线程的运行调度机制有关;
//b):主线程等待所有子线程运行结束,最后主线程结束,推荐这种写法(join()),程序更容易稳定;
//c):thread对象放入容器管理,类似一个thread对象数组,创建大量线程进行管理比较方便
下图为上面程序的执行结果

二.数据共享问题分析
2.1)只读数据
vector<int> shareVal = { 1,2,3 }; //共享数据
//线程入口函数
void myPrint(int num)
{
cout << "线程ID为:" << std::this_thread::get_id() <<
"输出shareVal:" << shareVal[0] << shareVal[1] << shareVal[2] << endl;
return;
}
int main(int argc, char** argv)
{
vector<thread> myThreads;
for (int i = 0; i < 10; i++)
{
myThreads.push_back(thread(myPrint, i)); //创建10个线程并开始执行
}
for (vector<thread>::iterator iter = myThreads.begin();iter!=myThreads.end();iter++)
{
iter->join(); //等待10个线程都返回
}
cout << "主线程..." << endl; //最后执行这一句,整个进程退出
return 0;
}
下图为程序执行结果,可以看到程序输出较乱以外,执行多次也不会发生报错、异常。故只读的数据是安全稳定的,不需要特别处理

2.2)有读有写:如2个线程写,8个线程读,如果没有特别处理,程序肯定崩溃(读写的任务切换导致程序崩溃)
最简单处理:读的时候不能写,写的时候不能读。2个线程之间不能同时写,8个线程也不能同时读(可能读的时候会写)。
2.3)其他案例
如订车票问题:某窗口在给某人订某张座位的票时,另一个窗口需要给另一个人订票,是不能对这个座位同时进行预定(他此时查询到此座位应为不能预订状态)
三:共享数据的保护示例代码
假设做一个简易的网络游戏服务器:两个自己创建的线程,一个线程收集玩家命令(用数字代表,相当于写操作),并将命令写到一个队列中,另一个线程从队列中取出玩家发送来的命令(相当于读操作),解析,然后执行玩家需要的动作
list:频繁的按顺序插入和删除数据时效率高。
vector:随机的插入和删除效率高
用成员函数作为线程函数的方法来写线程
#include <list>
class A
{
public:
//线程:把收到的消息(玩家命令)放入一个队列
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue(),插入一个元素" << i << endl;
msgRecvQueue.push_back(i); //假设i就是收到的命令,存入消息队列
}
}
//线程:把收到的命令取出
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
if (!msgRecvQueue.empty())
{
//消息不为空
int command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素,但不返回
//处理取出的数据
//.............
}
else
{
//消息队列为空
cout << "outMsgRecvQueue(),但目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue; //存放玩家发送的命令队列(共享数据)
};
int main(int argc, char** argv)
{
A obj;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &obj);
//第二个参数为引用,保证子线程操作的是主线程中的obj,不会复制(可看上一篇,第二篇博客)
std::thread myInMsgObj(&A::inMsgRecvQueue, &obj); //第二个参数未引用
myOutMsgObj.join(); //阻塞主线程并等待子线程执行完毕
myInMsgObj.join();
return 0;
}
下图为某次程序执行结果,发现程序运行途中会出现崩溃,原因就是不断地往容器中写数据,同时不停地读数据,两个线程随意运行,一定会出问题。

假设写线程正在执行,而读线程又去读数据并删除数据,程序乱套,报出异常。
解决思路:在执行写数据线程的时候,让读数据线程等着。相反的情况就是,在执行读线程的时候,写数据线程等待。读完后再往这块共享内存中写数据。
总结:抛出了这个问题,并提出解决问题的想法,引出互斥量的概念,请看后续文章说明
网友评论