(轉)風格之爭:Coroutine模型 vs 非阻塞/異步IO(callback)

咱們在設計一個服務器的軟件架構的時候,一般會考慮幾種架構:多進程,多線程,非阻塞/異步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.

我想這是一種風格的選擇,優劣並非絕對的。

相關文章
相關標籤/搜索