orzasio,OrzAsio中的多线程编程思想【转载】

借着OrzAsio文档示例完整发布的时机,同时考虑到论坛中多线程编程的资料较少,我将OrzAsio中关于多线程编程的部分单独发一帖,做一个共享。
(bytheway,现在的个人计算机越来越趋向多核架构,所以并行编程是一个能够极大提高程序性能的方式。我自己计算机的CPU是4核的,在玩求生之路的时候,关闭多核渲染比起打开来确实感觉不一样,尤其在僵尸大量出现的时候)
OrzAsio主要面向的是网络游戏的服务器需求,所以这里讨论的服务器都指网络游戏的服务器。
服务期的性能瓶颈主要在吞吐量上,就是单位时间内能够处理的数据量,越大则服务器的性能越好;而仅对处理一个数据的角度讲,服务器并不需要非常优秀和快速的处理单个数据的能力,也就是说并发性是服务器性能的关键。那么如何提高并发性?用更快的算法?一个一个处理的情况下,缩短每个处理的单位时间,确实是一种方法,但指标不治本,根本的办法是使用多线程
一提到多线程,很多人就开始头大,因为“互斥”、“死锁”、“线程安全”等问题就一下子涌入到脑中,让人无从下手。我们先看一个生活中的具体例子:
一个地铁站的进站口就可以看作一个服务器的数据,进入的乘客可以看作线程,乘客通过进站口相当于线程访问数据。我们如何设计乘客通过进站口的方式呢?
方式1让通道同一时间能多个人通过。在过去的卖票和人工检票的时代,这种方式虽然较能够保证吞吐量,但检票和乘客的行走都有相当的不便,而且时而会有逃票、漏票或者重复检票的错误发生;
方式2让通道同一时间只能一人通过。检票和乘客行走都能保证了,但吞吐量却被限制了,人一多,这种办法肯定不行;
方式3将进站口分为多个同一时间只能一人通过的子通道。这种方式只要同时通过的乘客数量不超过子通道的数量,则就不会有乘客等待的事情发生,而且同时检票和行走问题也能完美解决。这种方式也是现今正在实行的刷卡方式。但唯一的缺点就是,如果同一时间的乘客数量超过子通道的数量,则还是会产生等待;
方式4将进站口分为多个同一时间只能一人通过的子通道,但在地铁站关闭的时间内根据最近乘客的流量调整子通道的数量。和方式3几乎一样,只是增加了视乘客流量来调整子通道数量的做法。这种方式虽然不能够做到实时调整,但只要在地铁站运营的大部分时间内让同一时间通过的乘客数量小于等于子通道的数量,就可以了。虽然可能在某些上下班高峰时间段还有乘客等待的情况发生,但那属于峰值,只要调整的合理,则发生频率不会高。这样也就能从总体上满足了整个地铁站的吞吐量需求。
上述地铁站的流量例子,我们可以将其映射到服务器程序的设计上:
地铁站例子中的通道相当于线程需要访问的数据,而每个乘客都相当于一个独立的线程。
方式1相当于“多线程并发处理数据”,虽然吞吐量能够保证,但因为没有线程安全的保证(多个线程同时访问同一个数据),故一定会发生多个线程访问冲突的错误;
方式2相当于“多个线程,但同一时间只有一个线程在工作”,通过互斥锁,多个线程访问数据的冲突问题解决了,但这样的方式相当于单线程模式,这对吞吐量的改进没有任何帮助;
方式3相当于“多个线程,同时访问被分组后的数据,每个分组的数据都能够保证线程安全”,通过事先将数据分组,然后限制每组中线程访问的条件(同一时间只能由一个线程访问同一组中的数据),来在一定条件下保证吞吐量和线程安全。
方式4相当于“多个线程,同时访问被分组后的数据,每个分组的数据都能够保证线程安全;同时在程序关闭时期根据线程访问的频度调整分组的数量”。
OrzAsio选择的线程模型就是方式4。
OrzAsio可以允许用户根据自己程序的负载通过配置文件调整其内部运行的线程数量(这里每个线程相当于上述地铁站例子的通道),每个线程在空闲的时候会自动处于sleep状态,而当有任务分配给它的时候,又能立刻恢复运行,所以OrzAsio能够做到根据网络和逻辑负载来动态灵活的使用系统的资源,而且当运行OrzAsio的服务器cpu(核)数量增加时,用户只需要简单的通过配置文件增加OrzAsio的内部线程数量,就能够最大化发挥多cpu(核)的性能。
根据方式4的思路,我们设计出了在OrzAsio中被广泛应用的一种容器结构:HashMap。(源代码在[根目录]/orz/Toolkit_Plus/Toolkit/HashMap.hpp)
这个HashMap使用哈希算法来分组数据,每个分组中使用std::map来作为储存容器,然后在每个组中增加独立的互斥锁,同时哈希算法是wait-free的,这样能够保证多个线程(不超过这个HashMap的分组数量,超过的话可能要有线程等待)并发访问不同分组的数据,则不会因为互斥而等待,而是wait-free的,即保证了线程安全,又大大提高了多线程下大量数据查找的效率。同时,这个HashMap的分组数量,可以让用户自己调整,这就做到了根据不同的负载,调整HashMap的吞吐量。
使用这个HashMap我们可以无后顾之忧的在多线程下高效、安全的存储、查找和访问数据了。
事实上,OrzAsio中使用的很多数据结构都是使用这样的思想来设计的,如果用户自己将来使用OrzAsio开发网络程序,我们也推荐使用这样的方式来编程,这样与OrzAsio能够更好的相结合。
这种编程思想总结为:
#1 使用某种方式(例如哈希)对数据分组;
#2 每个分组独自使用互斥锁,保证同一时间最多只有一个线程可以访问组内的数据;
#3 当有线程来访问数据的时候,根据分组方式中的分组算法选择出对应的组,来进行具体的操作;
#4 分组算法本身最好是wait-free的;(例如key % maxGroup == groupid)。
以下是使用这种思想设计的几种数据结构,便于用户参考:
#1 Agency,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/Agency .hpp
#2 DeadlineTimer,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/ DeadlineTimer .hpp
#3 FastIdrCluster,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/ FastIdrCluster .hpp
#4 HashMap和HashShrMap,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/ HashMap .hpp
#5 PeriodicTimer,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/ PeriodicTimer .hpp
#6 ResCluster,源代码在[根目录]/orz/Toolkit_Plus/Toolkit/ ResCluster .hpp
#7 OrzAsioLogger,源代码在[根目录]/orz/Toolkit_Plus/Log/ OrzAsioLogger .h,[根目录]/Tookit_Plus/src/Log/OrzAsioLogger.cpp
MTPluginMgrImpl、MTDynLibMgrImpl都是具体应用HashMap的例子,供用户参考。
OrzMySQL应用这种思想,设计了高效和线程安全的多连接的Mysql访问方案。
Tags:  多线程编程技术 vc多线程编程 什么是多线程编程 多线程编程 orzasio

延伸阅读

最新评论

发表评论