高性能服务器架构思路(四)——编码复杂度和通信
分布式编程复杂度
以前我们的代码,从上往下执行,每一行都会占用一定的 CPU 时间,这些代码的执行顺序,也是和编写的顺序基本一致,任何一行代码,都是唯一时刻的执行任务。当我们在编写分布式程序的时候,我们的代码将不再好像那些单进程、单线程的程序一样简单。我们要把同时运行的不同代码,在同一段代码中编写。就好像我们要把整个交响乐团的每个乐器的乐谱,全部写到一张纸上。为了解决这种编程的复杂度,业界发展出了多种编码形式。
在多进程的编码模型上,fork() 函数可以说一个非常典型的代表。在一段代码中,fork() 调用之后的部分,可能会被新的进程中执行。要区分当前代码的所在进程,要靠 fork() 的返回值变量。这种做法,等于把多个进程的代码都合并到一块,然后通过某些变量作为标志来划分。这样的写法,对于不同进程代码大部份相同的“同质进程”来说,还是比较方便的,最怕就是有大量的不同逻辑要用不同的进程来处理,这种情况下,我们就只能自己通过规范 fork() 附近的代码,来控制混乱的局面。比较典型的是把 fork() 附近的代码弄成一个类似分发器(dispatcher)的形式,把不同功能的代码放到不同的函数中,以 fork 之前的标记变量来决定如何调用。

动态多进程的代码模式
在我们使用多线程的 API 时,情况就会好很多,我们可以用一个函数指针,或者一个带回调方法的对象,作为线程执行的主体,并且以句柄或者对象的形式来控制这些线程。作为开发人员,我们只要掌握了对线程的启动、停止等有限的几个 API,就能很好的对并行的多线程进行控制。这对比多进程的 fork() 来说,从代码上看会更直观,只是我们必须要分清楚调用一个函数,和新建一个线程去调用一个函数,之间的差别:新建线程去调用函数,这个操作会很快的结束,并不会依序去执行那个函数,而是代表着,那个函数中的代码,可能和线程调用之后的代码,交替的执行。
由于多线程把“并行的任务”作为一个明确的编程概念定义了出来,以句柄、对象的形式封装好,那么我们自然会希望对多线程能更多复杂而细致的控制。因此出现了很多多线程相关的工具。比较典型的编程工具有线程池、线程安全容器、锁这三类。线程池提供给我们以“池”的形态,自动管理线程的能力:我们不需要自己去考虑怎么建立线程、回收线程,而是给线程池一个策略,然后输入需要执行的任务函数,线程池就会自动操作,比如它会维持一个同时运行线程数量,或者保持一定的空闲线程以节省创建、销毁线程的消耗。在多线程操作中,不像多进程在内存上完全是区分开的,所以可以访问同一份内存,也就是对堆里面的同一个变量进行读写,这就可能产生程序员所预计不到的情况(因为我们写程序只考虑代码是顺序执行的)。还有一些对象容器,比如哈希表和队列,如果
原创不易,完成人机校验,阅读全文