• (转)风格之争:Coroutine模型 vs 非阻塞/异步IO(callback)


    我们在设计一个服务器的软件架构的时候,通常会考虑几种架构:多进程,多线程,非阻塞/异步IO(callback) 以及Coroutine模型。

    多进程
    这种模型在linux下面的服务程序广泛采用,比如大名鼎鼎的apache。主进程负责监听和管理连接,而具体的业务处理都会交给子进程来处理。这里有一篇我以前写的文章具体的解释这种架构的实现。

    这种架构的最大的好处是隔离性,子进程万一crash并不会影响到父进程。缺点就是对系统的负担过重,想像一下如果有上万的连接,会需要多少进程来处理。所以这种模型比较合适那种不需要太多并发量的服务器程序。另外,进程间的通讯效率也是一个瓶颈之一,大部分会采用share memory等技术来减低通讯开销。

    多线程
    这种模型在windows下面比较常见。它使用一个线程来处理一个client。他的好处是编程简单,最重要的是你会有一个清晰连续顺序的work flow。简单意味着不容易出错。

    这种模型的问题就是太多的线程会减低软件的运行效率。

    多进程和多线程都有资源耗费比较大的问题,所以在高并发量的服务器端使用并不多。这里我们重点来研究一下两种架构,基于callback和coroutine的架构。

    Callback- 非阻塞/异步IO
    这种架构的特点是使用非阻塞的IO,这样服务器就可以持续运转,而不需要等待,可以使用很少的线程,即使只有一个也可以。需要定期的任务可以采取定时器来触发。把这种架构发挥到极致的就是node.js,一个用javascript来写服务器端程序的框架。在node.js中,所有的io都是non-block的,可以设置回调。

    举个例子来说明一下。
    传统的写法:

     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会比较好,缺点是编程复杂,把以前连续的流程切成了很多片段。另外也不能充分发挥多核的能力。

    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.

    我想这是一种风格的选择,优劣并不是绝对的。

  • 相关阅读:
    [DICOM]SCU和SCP
    [CentOS7]安装Python3
    解决mysqlCannot truncate a table referenced in a foreign key constraint
    对称加密和非对称加密,自签证书,ssl证书
    Qt QRadioButton 选中/取消选中
    Qt QPixmap设置图片透明度
    Qt 建立文件夹、判断文件夹是否存在、文件夹是否为空、清空文件夹
    Qt 删除某文件目录下的空文件夹
    python 3.6 生成器
    tensorflow tfserving 部署记录
  • 原文地址:https://www.cnblogs.com/cdyboke/p/7765443.html
Copyright © 2020-2023  润新知