咱們在設計一個服務器的軟件架構的時候,一般會考慮幾種架構:多進程,多線程,非阻塞/異步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.
我想這是一種風格的選擇,優劣並非絕對的。