美文网首页
多线程学习(三)

多线程学习(三)

作者: lxr_ | 来源:发表于2021-11-10 14:03 被阅读0次

一.创建和等待多个线程

#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;
}

下图为某次程序执行结果,发现程序运行途中会出现崩溃,原因就是不断地往容器中写数据,同时不停地读数据,两个线程随意运行,一定会出问题。


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

相关文章

网友评论

      本文标题:多线程学习(三)

      本文链接:https://www.haomeiwen.com/subject/jyvbzltx.html