1. 创建和等待多个线程
- 示例代码
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
//子线程起始函数
void myprint(int inum)
{
cout << inum << "号线程开始执行" << endl;
cout << inum << "号线程执行完毕" << endl;
return;
}
int main()
{
vector <thread> myThreads;
//创建10个线程,线程入口同一使用myprint
for (int i = 0; i < 10; i++)
myThreads.push_back(thread(myprint, i));
for (auto iter = myThreads.begin(); iter != myThreads.end(); ++iter)
iter->join();
cout << "主函数" << endl;
return 0;
}
/* -----运行结果----------
01号线程开始执行号线程开始执行
0号线程执行完毕
2号线程开始执行
3号线程开始执行
3号线程执行完毕
17号线程开始执行5号线程开始执行
59号线程开始执行
6号线程开始执行
号线程执行完毕
7号线程执行完毕
4号线程开始执行
4号线程执行完毕
6号线程执行完毕
号线程执行完毕
2号线程执行完毕
9号线程执行完毕
8号线程开始执行
8号线程执行完毕
主函数
*/
- 多个线程可以使用统一的线程入口函数
- 多个线程的执行顺序是乱的,这和操作系统的调度机制有关
- 利用
join
,保证主线程等所有子线程结束后才结束 - 推荐使用
vector
管理多个线程,看起来像对象数组
2. 数据共享问题分析
(1)只读数据
- 示例代码
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
vector <int> g_v = { 1,2,3 }; //共享数据,只读
//子线程起始函数
void myprint(int inum)
{
cout << "id为" << std::this_thread::get_id() << "的进程打印共享数据g_v:" << g_v[0] << g_v[1] << g_v[2] << endl;
return;
}
int main()
{
vector <thread> myThreads;
//创建10个线程,线程入口同一使用myprint
for (int i = 0; i < 10; i++)
myThreads.push_back(thread(myprint, i));
for (auto iter = myThreads.begin(); iter != myThreads.end(); ++iter)
iter->join();
cout << "主函数" << endl;
return 0;
}
/*
id为31856的进程打印共享数据g_v:123id为28516id为id为30204id为31384的进程打印共享数据g_v:123
id为23316的进程打印共享数据g_v:123
12592的进程打印共享数据g_v:123id为26636的进程打印共享数据g_v:123
的进程打印共享数据g_v:123
id为18276的进程打印共享数据g_v:123
的进程打印共享数据g_v:123
id为25628的进程打印共享数据g_v:123
id为9048的进程打印共享数据g_v:123
主函数
*/
- 可见,虽然数据顺序是乱的,但是程序执行是稳定的,不会出现线程间相互影响导致程序崩溃的情况
- 小结:只读的数据,是安全稳定的,不需要做特殊的处理,各个进程直接读就行了
(2)有读有写
-
对于一些进程读,一些进程写的程序,可能会发生各种进程同步问题
- 生产者 - 消费者问题
- 读者 - 写者问题
- 哲学家就餐问题
-
小结:有些进程读共享数据,另一些进程写共享数据的程序,如果不进行进程同步处理,程序一定会出现无法预测的情况,甚至崩溃
3. 保护共享数据
(1)背景
- 编写一个网络游戏服务器,有两个自己创建的子线程
- 一个用来接受玩家命令(数字表示),并把命令存入一个队列中
- 另一个线程从队列中取命令,解析并执行操作
(2)不考虑进程同步问题的代码
- 示例
#include "pch.h"
#include <iostream>
#include <thread>
#include <vector>
#include <list>
using namespace std;
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();
//根据command进行各种处理
//.....
}
else
cout << "inMsgRecvQueue()执行,消息队列为空" << i << endl;
}
}
private:
list <int> msgRecvQueue; //用来存玩家命令的队列
};
int main()
{
//用类的成员函数作子线程入口的形式,实现两个线程
//这里一定要用传引用,否则子线程是建立在myobj的副本上的
A myobj;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
- 这个程序,两个线程同时操作
msgRecvQueue
队列,因为没有进程同步,很可能崩溃- 这里对list的操作不是原子操作,可能出现
pop_front
或者push_back
执行到一半就转走执行另一个线程,导致程序崩溃
- 这里对list的操作不是原子操作,可能出现
- 解决同步问题的思路
- 写进程写的时候,禁止读
- 读进程读的时候,禁止写
- 下一篇文章介绍的互斥量可以解决这个问题