咱们在设计一个服务器的软件架构的时候,一般会考虑几种架构:多进程,多线程,非阻塞/异步IO(callback) 以及Coroutine模型。javascript
多进程
这种模型在linux下面的服务程序普遍采用,好比大名鼎鼎的apache。主进程负责监听和管理链接,而具体的业务处理都会交给子进程来处理。这里有一篇我之前写的文章具体的解释这种架构的实现。html
这种架构的最大的好处是隔离性,子进程万一crash并不会影响到父进程。缺点就是对系统的负担太重,想像一下若是有上万的链接,会须要多少进程来处理。因此这种模型比较合适那种不须要太多并发量的服务器程序。另外,进程间的通信效率也是一个瓶颈之一,大部分会采用share memory等技术来减低通信开销。java
多线程
这种模型在windows下面比较常见。它使用一个线程来处理一个client。他的好处是编程简单,最重要的是你会有一个清晰连续顺序的work flow。简单意味着不容易出错。node
这种模型的问题就是太多的线程会减低软件的运行效率。python
多进程和多线程都有资源耗费比较大的问题,因此在高并发量的服务器端使用并很少。这里咱们重点来研究一下两种架构,基于callback和coroutine的架构。linux
Callback- 非阻塞/异步IO
这种架构的特色是使用非阻塞的IO,这样服务器就能够持续运转,而不须要等待,可使用不多的线程,即便只有一个也能够。须要按期的任务能够采起定时器来触发。把这种架构发挥到极致的就是node.js,一个用javascript来写服务器端程序的框架。在node.js中,全部的io都是non-block的,能够设置回调。c++
举个例子来讲明一下。
传统的写法:apache
var file = open(‘my.txt’); var data = file.read(); //block sleep(1); print(data); //block
node.js的写法:编程
fs.open(‘my.txt’,function(err,data){ setTimeout(1000,function(){ console.log(data); } }); //non-block
这种架构的好处是performance会比较好,缺点是编程复杂,把之前连续的流程切成了不少片断。另外也不能充分发挥多核的能力。windows
Coroutine-协程
coroutine本质上是一种轻量级的thread,它的开销会比使用thread少不少。多个coroutine能够按照次序在一个thread里面执行,一个coroutine若是处于block状态,能够交出执行权,让其余的coroutine继续执行。使用coroutine能够以清晰的编程模型实现状态机。让咱们看看Lua语言的coroutine的例子。
> function foo() >> print("foo", 1) >> coroutine.yield() >> print("foo", 2) >> end > co = coroutine.create(foo) -- create a coroutine with foo as the entry > = coroutine.status(co) suspended > = coroutine.resume(co) <--第一次resume foo 1 > = coroutine.resume(co) <--第二次resume foo 2 > = coroutine.status(co) dead
Google go语言也对coroutine使用了语言级别的支持,使用关键字go来启动一个coroutine(从这个关键字能够看出Go语言对coroutine的重视),结合chan(相似于message queue的概念)来实现coroutine的通信,实现了Go的理念 ”Do not communicate by sharing memory; instead, share memory by communicating.”。一个例子:
func ComputeAValue(c chan float64) { // Do the computation. x := 2.3423 / 534.23423; c <- x; } func UseAGoroutine() { channel := make(chan float64); go ComputeAValue(channel); // do something else for a while value := <- channel; fmt.Printf("Result was: %v", value); }
coroutine的优势是编程简单,流程清晰。可是内存消耗会比较大,毕竟每一个coroutine要保留当前stack的一些内容,以便于恢复执行。
Callback vs Coroutine
在业务流程实现上,coroutine确实是更理想的实现,基于callback的风格,代码确实不是那么清晰,你可能会写出这样的代码。
//pseudo code socket.read(function(data){ if(data==’1’) db.query(data,function(res){ socket.write(res,function(){ socket.read(function(data){ }); }); }); else doSomethingelse(...); });
固然你可使用独立的function函数来代替匿名的函数得到更好的可读性。若是使用coroutine就得到比较清晰的模型。
//pseudo code coroutine handle(client){ var data = read(client); //coroutine will yield when read, resume when complete if(data==’1’){ res = db.query(data); … } else{ doSomething(); } }
可是现实世界中,coroutine到目前为止并无真正流行起来,第一,是由于支持的语言并非不少,比较新的语言(python/lua/go/erlang)才支持,可是老一些的语言(java/c/c++)并无语言级别的支持。第二个缘由是由于coroutine的使用可能让一些糟糕的代码占用过多的内存,并且比较难于排查。另外在实现一个工做流的构架中,流的暂停和恢复的时机都是未知的,系统的状态并不能放在内存中存放,都必须序列化,因此coroutine自己要提供序列化的机制,才能够实现稳定的系统。我想这些就是coroutine叫好不叫座的缘由。
尽管有不少人要求node.js实现coroutine,Node.js的做者Ryan dahl在twitter上说: ”i’m not adding coroutines. stop asking”.至于他的理由,他在google group上提到:
Yes, there have been discussions. I think, I won’t be adding
coroutines. I don’t like the added machinery and I feel that if I add
them, then people will actually use them, and then they’ll benchmark
their programs and think that node is slow and/or hogging memory. I’d
rather force people into designing their programs efficiently with
callbacks.
我想这是一种风格的选择,优劣并非绝对的。