zoukankan      html  css  js  c++  java
  • C++并发与多线程学习笔记--多线程数据共享问题

    • 创建和等待多个线程
    • 数据和共享问题分析
      • 只读的数据
      • 有读有写
      • 其他案例
    • 共享数据的保护案例代码

    创建和等待多个线程

    服务端后台开发就需要多个线程执行不同的任务。不同的线程执行不同任务,并返回执行结果。很多个线程都用同一个线程入口:

    void myprint(int num)
    {
    	cout << "线程开始执行了: " << num << endl;
    	cout << "My print id: "<<this_thread::get_id() << endl;
    	cout << "线程结束执行了" << endl;
    	return;
    }
    
    
    int main()
    {
    	vector<thread> workers;
    	//创建10个线程,线程的入口统一使用 void myprint(int num)
    	for (int i = 0; i < 10; i++) {
    		workers.push_back(thread(myprint, i)); //创建并开始执行线程
    	}
    	for (auto iter = workers.begin(); iter != workers.end(); ++iter) {
    		iter->join();
    	}
    
    	cout << "Main Thread End!!!" << endl;
    	
    	return 0;
    }
    

     小结:

    1)多个线程的执行顺序是乱的,跟操作系统内部的运行机制有关。

    2)主线程等待所有子线程运行结束,最后主线程才结束。

    3)用join写出来的程序才比较稳定,更容易写出稳定的程序。

    4)专门用迭代器创建多个线程的写法,创建大量的线程进行管理,很方便。==>线程池的思路。

    数据和共享问题分析

    只读的数据

        共享数据

    vector<int> g_v = { 1,2,3 }; //共享数据
    

     修改打印函数

    void myprint(int num)
    {
    
    cout << "线程的ID为 " << this_thread::get_id() << 
    "打印 g_v的值为" << g_v[0] << g_v[1] << g_v[2] << endl; return; }

     虽然顺序不定,但是每次都成功读出了数组中的值。==>只读数据是安全稳定的,直接可以读。

    有读有写

    有读有些的线程,一旦代码写不好的时候,容易出问题,崩溃or报错。

    两个线程往容器里面写,八个线程往容器里读,如果没有特别的处理,程序肯定崩溃。最简单的不崩溃处理,读的时候不能写,写的时候不能读。==>互斥锁的思路??

    1)任务切换有各种诡异的事情发生,如程序崩溃。

    std::mutex locker;
    

      

    void myprint(int num)
    {
    	locker.lock();
    	cout << "线程的ID为 " << this_thread::get_id() <<
    		"打印 g_v的值为" << g_v[0] << g_v[1] << g_v[2] << endl;
    	locker.unlock();
    	return;
    }
    

      

    其他案例

    例1:假设定火车票,10个售票窗口。

    1,2窗口同时卖票,如果座位已经有人订了,那么直接返回,告诉顾客已经有人坐了,否则订票。

    1号窗口和2号窗口共享这些数据。

    共享数据的保护案例代码

     实际工作中的范例:网络游戏服务器开发,网络游戏服务器,这个服务器,最简单的有两个自己创建的线程:(实际中可以用线程池来做):

    1) 用来收玩家的命令并把命令数据写到一个队列中,这个线程专门负责通过网络收数据。

    2) 线程重队列中取出玩家发送的命令,解析,执行玩家的动作---抽卡!!!

    3) 用数字表示玩家的动作

    4) vector, list, list和vector的内部实现手段不一样,在底层虽然都是push_back(),但是在插入元素的时候,vector需要复制内存到新的空间,并且有[]操作符的重载,而list是数据结构中的双向链表,因此没有[],在频繁插入和删除的时候使用List,容器对于随机的插入和删除效率高。

     使用List的时候

    #include<list>
    

     用成员函数作为线程函数的方法写线程。实际中,一般都把变量写在类中,符合面向对象的程序设计思想。

    class ProcessRequest {
    public:
    	//把命令加入到一个队列
    	void inMsgRecvQueue() {
    		for (int i = 0; i < 100000; ++i) {
    			cout << "插入一个元素" << endl;
    			m_msgRecvQueue.push_back(i); //假设这个队列表示玩家的命令
    		
    		} //占用时间片
    	}
    	//把命令移出一个队列
    		void outMsgRecvQueue() {
    
    			for (int i = 0; i < 100000; ++i) {
    				if (!m_msgRecvQueue.empty()) {
    					//消息不为空
    					int command = m_msgRecvQueue.front();
    					//尝试返回第一个元素,取出元素
    					m_msgRecvQueue.pop_front();
    				}
    				else
    				{
    					cout << "outMsgRecvQueue() 还执行,但是消息队列为空"<<i<< endl;
    					//消息队列为空
    				}
    				
    			} //占用时间片
    			cout << "end!!!!!" << endl;
    		}
    	
    private:
    	std::list<int> m_msgRecvQueue; //容器,用于表示玩家的发送过来命令
    
    };
    

     主函数

    	ProcessRequest obj;
    	std::thread outWorker(&ProcessRequest::outMsgRecvQueue, &obj);
    	//第二参数是引用才是同一个对象,不能用detach(),否则不稳定
    	std::thread inWorker(&ProcessRequest::inMsgRecvQueue, &obj);
    	//两个线程创建完成之后,要保证对象是有意义的,用join
    	outWorker.join();
    	inWorker.join();
    

     数据共享的理论,有读有写,不断地读和写,共享的消息队列,如果完全不控制,一定会出错。共享数据与锁,某个线程在操作的时候,其他线程需要等待。写的时候锁住,读的时候锁住。

    1)互斥量。多线程一定会有互斥量,下回分解。。。

    其他知识

    vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。

    list就是数据结构中的双向链表(根据sgi stl源代码),因此它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。

    deque是一个double-ended queue,它的具体实现不太清楚,但知道它具有以下两个特点:
    它支持[]操作符,也就是支持随即存取,并且和vector的效率相差无几,它支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多。

    因此在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面
    的原则:
      1、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
      2、如果你需要大量的插入和删除,而不关心随即存取,则应使用list
      3、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。

    参考文献

    https://study.163.com/course/courseLearn.htm?courseId=1006067356#/learn/video?lessonId=1053470368&courseId=1006067356

    https://blog.csdn.net/bmjhappy/article/details/82228080

  • 相关阅读:
    数据结构与算法(一)--数组
    Lucene学习
    java虚拟机面试题(JVM)
    Java开发面试题归类( 题目篇)
    java虚拟机学习(六)
    java虚拟机学习(五)--垃圾收集器总结
    21_异常_第21天(异常、企业面试题,思维导图下载)
    20_集合_第20天(Map、可变参数、Collections)
    19_集合_第19天(List、Set)
    18_集合框架_第18天(集合、Iterator迭代器、增强for循环 、泛型)
  • 原文地址:https://www.cnblogs.com/rynerlute/p/11802715.html
Copyright © 2011-2022 走看看