• 17.13 补充知识、线程池浅谈、数量谈、总结


    一:补充一些知识点

    <1>虚假唤醒

    class MA
    {
    public:
    	int i = 0;
    	std::unique_lock<std::mutex> rtn_unique_gurad()
    	{
    		std::unique_lock <std::mutex> tmpgurad(my_mutex_1);
    		return tmpgurad;  //从函数返回一个局部的unique_lock对象是可以的,三章十四届讲过移动构造函数
    		//返回这种局部对象会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
    	}
    
    	//把收到的消息(玩家命令)放入到一个队列的线程
    	void inMsgRecvQueue()
    	{
    		for (int i = 0; i < 100000; ++i)
    		{
    			cout << "inMsgRecvQueue()执行,插入一个元素:  " << i << endl;
    			std::unique_lock<std::mutex> sbguard_1 = rtn_unique_gurad();
    			msgRecvQueue.push_back(i);  //假设这个数字i就是我们收到的命令,直接弄到消息队列中
    
    			//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待唤醒;
    			//那么此时这个notify_one()这个调用也许就没效果。
    			my_cond.notify_one();  //我们尝试把wait()线程唤醒,执行完这行,那么outMsgRecvQueue()里边的wait()就会被唤醒
    		}
    		cout << "i = " << i << endl;
    	}
    
    	//把数据从消息队列中取出的线程
    	void outMsgRecvQueue()
    	{
    		int command = 0;
    		while (true)
    		{
    			//wait()用来等待一个东西
    			//如果第二个参数lambda表达式返回值是true,那wait()直接返回;
    			//如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并堵塞到本行
    			//那堵塞到什么时候为止呢?堵塞到其他某个线程调用notify_one()成员函数为止;
    			//如果wait()没有第二个参数:my_cond.wait(sbguard1); 那么就跟第二个参数lambda表达式返回false效果一样,堵塞本行
    			//wait()将解锁互斥量,并阻塞到本行,阻塞到其他某个线程调用欧冠notify_one()成员函数为止;
    
    			//当其他线程用notify_one()将本wait()(原来是睡着/阻塞)的状态唤醒后,wait就开始恢复干活了,恢复后wait干什么活?
    			//a> wait()不断的尝试重新获取互斥量锁,如果获取不到,那么流程就卡在wait()这这里等着获取,如果获取到了锁(等于加了锁),那么wait()就继续执行b;
    			//b> 
    			//b.1> 如果wait()有第二个参数(lambda),就判断这个lambda表达式,如果lambda表达式为false,那wait()又对互斥量解锁,然后又休眠这里等待再次被notify_one唤醒;
    			//b.2> 如果lambda表达式为true, 则wait返回,流程走下来(此时互斥锁被锁着);
    			//b.3> 如果wait()没有第二个参数,则wait()返回,流程走下来。
    			std::unique_lock<std::mutex> sbguard_1(my_mutex_1);
    			my_cond.wait(sbguard_1, [this] {  //一个lambda就是一个可调用对象(函数)
    				if (!msgRecvQueue.empty())
    					return true;
    				return false;
    				});
    
    			//流程只要能走到这里来,这个互斥锁一定是锁着的,同时msgRecvQueue至少是有一条数据的
    			command = msgRecvQueue.front();
    			msgRecvQueue.pop_front();
    			sbguard_1.unlock();  //因为unique_lock的灵活性,所以我们可以随时的unlock解锁,以免锁住太长时间。
    			cout << "outMsgRecvQueue()执行,取出一个元素 " << command << " " << std::this_thread::get_id() << endl;
    		}  //end while
    		cout << "end" << endl;
    	}
    private:
    	list<int> msgRecvQueue;  //容器(消息队列),专门用于代表玩家发送的命令
    	mutex my_mutex_1;  //创建了一个互斥量  (一把锁头)
    	std::condition_variable my_cond;  //生成一个条件变量对象
    };
    
    int main()
    {
    	MA myobj;
    	std::thread myOutMsgObj(&MA::outMsgRecvQueue, &myobj);  //第二个参数是引用,才能保证线程里用的是同一个对象
    	std::thread myInMsgObj(&MA::inMsgRecvQueue, &myobj);
    	myOutMsgObj.join();
    	myInMsgObj.join();
    	cout << "main主函数执行结束" << endl;  //最后执行这句,整个进程退出
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    虚假唤醒:wait中要有第二个参数(lambda)并且这个lambda中要正确判断要处理的公共数据是否存在;
    wait()、notify_one()、notify_all()。

    <2>atomic

    class MA
    {
    public:
    	MA()
    	{
    		atm = 0;
    	}
    
    public:
    	void inMsgRecv()
    	{
    		for (int i = 0; i < 10000000; ++i)
    		{
    			//atm += 1;
    			atm = atm + 1;  //读atm是个原子操作,但是整个这一行代码并不是个原子操作
    			//auto atm2 = atm;  //这种定义时初始化操作不允许,显示“尝试引用已删除的函数”编译器内部肯定把拷贝构造函数给干掉了用 =delete,因为编译器不好操作
    			//atomic<int> atm2 = atm;  //也不允许
    			//atomic<int> atm2;
    			//atm2 = atm  //尝试引用已删除的函数,拷贝赋值运算符也不让用
    			//load():以原子方式读atomic对象的值
    			atomic<int> atm2(atm.load());  //读
    			auto atm3(atm.load());
    			//store()以原子方式写入内容
    			atm2.store(12);
    			atm2 = 12;
    		}
    	}
    
    	void outMsgRecv()
    	{
    		int command = 0;
    		while (true)
    		{
    			cout << "atm = " << atm << endl;
    		}  //end while
    		cout << "end" << endl;
    	}
    private:
    	atomic<int> atm;
    };
    
    int main()
    {
    	MA myobj;
    	std::thread myOutMsgObj(&MA::outMsgRecv, &myobj);  //第二个参数是引用,才能保证线程里用的是同一个对象
    	std::thread myInMsgObj(&MA::inMsgRecv, &myobj);
    	std::thread myInMsgObj2(&MA::inMsgRecv, &myobj);
    	myOutMsgObj.join();
    	myInMsgObj.join();
    	myInMsgObj2.join();
    	cout << "main主函数执行结束" << endl;  //最后执行这句,整个进程退出
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    二:浅谈线程池

    <1>场景设想

    服务器程序 -> 客户端,每来一个客户端,就创建一个新线程为该客户提供服务。
    (1)网络游戏,2万玩家不可能给每个玩家创建个新线程,此程序写法在这种场景下不通;
    (2)程序稳定性问题:编写的代码中,偶尔创建一个线程这种代码,这种写法,就让人感到不安;

    线程池:把一堆线程弄到一起,统一管理。这种统一管理调试,循环利用线程方式,就叫线程池。

    <2>实现方式:
    在程序启动时,一次性的创建好一定数量的线程。10,8,100-200,更让人放心,觉得程序代码更稳定。

    三:线程创建数量谈

    <1>线程开的数量极限问题,2000个线程基本就是极限,再创建线程就崩溃。

    <2>线程创建数量建议
    (1)采用某些技术开发程序;api接口提供商建议你创建线程数量 = CPU数量、CPU * 2、CPU * 2 + 2,遵照专业建议和指示来;专业意见确保程序高效执行;
    (2)创建多线程完成业务;一个线程等于一条通路;100要堵塞充值,我们这里开110个线程,那是很合适的;
    (3)1800个线程,建议线程数量尽量不要超过500个,能控制在200以内。

    四:c++11多线程总结

  • 相关阅读:
    部署jar包windows服务工具
    JS:什么是时间戳?
    从不同视角理解架构
    做短视频的赶紧用起来,超好用的配音神器~
    java servlet大学生旅游网站的设计与开发源码
    Spring Cloud整合Seata实现TCC分布式事务模式案例
    电脑硬件——CPU散热器
    Practicing Version Control
    月满中秋夜|中秋和 Jina AI 一起过
    express写post接口浏览器报404
  • 原文地址:https://blog.csdn.net/zzyzxb/article/details/125467088