《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型

最近在进修muduo,于是配套陈硕(大神老师,直接开头指出,之后就不写引用参考了,有兴趣的直接搜就可以了)的书一个起来学习,真的是不怕知识多,就怕知识乱。之前很多都是用到什么学什么,最多看一下相关的东西。读过书之后才发现自己的知识体系不够系统,因此边读边把有用的部分记录在此。
前两章的东西是线程安全和线程同步,之前在我的博客里陆陆续续提到很多,所以不另外做笔记。这是第三章的东西,从设计的思想开始剖析多线程模式。

进程与线程

具体的细节不做介绍,其实进程可以简单理解为一个运行的程序。线程可以简单理解为其中的一个功能。剥离出线程的概念其实还是硬件的发展,CPU从单核发展到了多核,所以传统的单线程模式无法充分利用多核的优势,所以出现了多线程的概念。

单线程服务器常用的编程模型

之前的博文已经讲过如何单线程来完成多任务,高性能的网络程序大多使用“IO多路复用+非阻塞IO”的方式来完成这个功能。作者提到了很多,我了解的就是前面博文剖析过的libevent。
其实就是reactor,可以用于读写、连接等,以非阻塞的方式提高并发度和吞吐率。因网络业务大多是IO,所以基本没什么问题。
但之前我们说过,这个模式重要的点其实在于非阻塞,事件的回调函数必须设为非阻塞。原因我们之前说过,阻塞的话其实就跟普通单线程无异。

重点-多线程服务器的常用编程模型

我也就只学习作者的“non-block IO+one loop per thread”了。

one loop per thread
程序里的每个IO线程都有一个event loop,用于处理读写和定时事件。这种模式下:线程数目固定,不会频繁创建和销毁。
event loop是线程的主循环,需要哪个干活,就把对应的事件注册进去即可。实时性要求高的单独用一个线程,数量大的独占线程并继续线程分发处理。
其实就是每个connection/acceptor都注册到某个event loop上,程序里有多个event loop,每个线程至多一个event loop。
这里面就要讲究线程安全了,我们线程会触发事件回调,会往其他loop里注册东西,那么这个loop就必须线程安全,比如多个事件同时注册时怎么办?会不会出现冲突导致注册错误?

线程池
作者指出,没有IO只有计算的线程使用eventloop就有点浪费,因此提出了一种任务队列。
《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型_第1张图片
这是一个最简单的线程池了。我用一个阻塞队列,循环从队列中取出任务处理即可。不需要进入event loop中。
结合之前libevent的学习,其实loop是一个庞大的反应堆,之所以适合IO操作,就是因为可以通过I\O来触发事件回调,从而进一步处理事情。而不需要IO操作的线程其实大可不必放入loop进行循环监视,因为他们只计算,不会IO反复触发新的事件,因此可以使用上述模式,在自己的循环里就可以解决问题。
其实上述思想也是我项目用到的生产者消费者模式雏形。我有一个生产者不断往任务队列里加数据,处理线程只要取任务计算就可以了。
其实这里面的关键就是这个队列,作者也提到了这一点。我从我的角度把它理解为两个线程的信箱,生产把信放进去,消费者空闲了就去取信然后处理。

推荐模式
C++端服务器的多线程编程模型:one event loop per thread+thread pool。
其中loop用作IO多路分发,配合非阻塞IO和定时器(这个之前libevent专门分析过),thread pool用来做计算。
作者提到的程序匹配是“阻抗匹配”,使IO和CPU都能高效运作。
这里面提到了logging,其实对服务器也是一个不可忽略的开销,之后详述这部分。

到这里,我的多线程编程的基础框架全部梳理通了,连同之前的reactor重新梳理一遍。不得不说,经典书籍就是好啊。继续!

进程间通信只用TCP

进程间通信的方式很多,作者选用了sockets,主要是TCP。原因其实还是伸缩性强,跨主机,适用于多服务器,尤其是现在的情况下,单服务器早已无法解决问题。
关于socket之前博文讲过,不详述,只摘取作者的有力观点进行记录分析。
首先TCP的双工通信,适用于服务器场景。而且TCP port是独占的,且操作系统会自动回收,省了程序员很多事。一半操作系统自动干的事都不会有很大隐患。独占式还可以防止程序重复启动,浪费资源。
TCP好处还在于可记录可重现。这在错误分析和性能分析时帮助很大。同时,TCP可以跨语言,不拘泥于客户端的语言,因此更普遍性。
TCP连接是可以随时再生的,某个进程想连接可重新建立连接。
之后是一系列的分析,总之,TCP更加适合于服务端编程的要求。

分布式系统中使用长连接

首先,分布式系统是由运行在多个机器上的多个进程组成的,进程间采用TCP长连接通信。长连接之前HTTP里有提到过,简言之,需要持久通信的进程间,就适当保持连接,不要每次交流一次就断一次,重连重启消耗很大。分布式就是很典型的频繁交流,因为我需要知道每个的进度或状态,所以TCP自然需要长连接。
TCP连接可以定位到各个之间的依赖关系。netstat -tpna | grep :port,通过address 和 foreign来确定连接关系。而且可以定位网络故障,正常情况下的netstat打印的Recv-Q&Send-Q都应该接近于0,通过观察状态可以判断死锁或阻塞等问题。
这些都是长连接可以监测到的,而短连接则很难办到,很短的时间就完成一次连接并断开,中间状态很难侦测。

多线程服务器的适用场合

我很喜欢这个服务器的通俗定义:跑在多核机器上的Linux用户态的没有用户界面的长期运行的网络应用程序。
也就说,我就站在那里,你来我去接你,你走我还在那里。你来几个人也没关系,你不来也没关系。(这么看服务器真是个好人啊!)
服务端程序主要就是处理并发连接,处理的时候自然要充分利用硬件配置。有时候是拿着小刀砍大树,有时候是开着火箭来赶路。这都是不合适的。
我们不考虑分布式的进程间,我们只从一台多核机器入手。作者指出了四种模式:
《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型_第2张图片
具体优劣已经分析。我之前在多线程博文部分已经提过这个概念,一个东西好,但不一定非要用,用不好反而是劣势。
那一个服务器什么时候应该是多线程的?
《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型_第3张图片
这是作者提到的例子。总结一下就是,当进程时间相对于任务所需时间可以忽略不计时,开进程做就好了。而进程时间较之任务时间不可忽略,而线程可以忽略时,开线程去做比较好。而原本运行时间就小的时候,那就真不如单线程了。形象的例子我举过,有兴趣可以翻阅到我的多线程学习部分细看。

单线程or多线程

一种很典型的环境就是我对CPU占用率是由要求的。比如一个不重要的程序,我们就用单线程就好了,即使跑满,也最多耗尽一个内核,而不会出现重要进程无法占用CPU的情况。
自然,还是要回到根本来看,作者举了个很好的例子。event loop我们说过是个很好的单线程的模型。但是非抢占式的是有一定问题的,a优先级高于b,a处理需1ms,b需10ms。如果b略早于a开始,a到来的时候,程序已经离开poll调用,开始处理事件b。因此a就需要等待10ms。优先级无法体现出。
单线程的缺点自然也就是多线程的优点。就像我们之前说的CPU硬件的限制也是编程需考虑的因素。那如果单核就能跑满IO的时候就无需多线程了。
那多线程主要的优势是什么?提高响应速度,多线程下,IO和计算可以重叠,无须谁等谁。虽然各自的时间不变,但平均响应性能上去了。
《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型_第4张图片
作者提供了多线程编程的条件也可以说是要求。下面有例子,不详述。
《Linux多线程服务端编程》笔记——多线程服务器的适用场合和常用编程模型_第5张图片
这是作者给的多线程的分类。说到底,多线程是大势所趋,我们并不否认单线程的简单和清晰,但多核时代,多线程无可避免也必须上去,理解清楚后其实多线程也就没有很难了,对异步思路也是个很好的训练。

最后还是总结一句吧,逻辑清晰的框架能助力你更好的编程设计,加油!!!

你可能感兴趣的:(网络,多线程,服务端编程)