Hello World
Spiga

为什么我认为goroutine和channel是把别的平台上类库的功能内置在语言里

2013-04-09 13:52 by 老赵, 10142 visits

这几天看了《Go语言编程》这本书,感觉一般,具体可见这篇书评。书评里面我提到“Go语言的goroutine和channel其实是把别的语言/平台上类库的功能内置到语言里”,这句话当然单单这么说出来是没什么价值的,于是我也就趁热把它说得再详细一些。我的看法简而言之是:由goroutine和channel所带来的主要编程范式、设计思路等等,其实基本都可以在其他一些平台中配合特定的类库来实现。

我们知道,操作系统的最小调度单元是“线程”,要执行任何一段代码,都必须落实到“线程”上。可惜线程太重,资源占用太高,频繁创建销毁会带来比较严重的性能问题,于是又诞生出线程池之类的常见使用模式。也是类似的原因,“阻塞”一个线程往往不是一个好主意,因为线程虽然暂停了,但是它所占用的资源还在。线程的暂停和继续对于调度器都会带来压力,而且线程越多,调度时的开销便越大,这其中的平衡很难把握。

正因为如此,也有人提出并实现了fibercoroutine这样的东西,所谓fiber便是一个比线程更小的代码执行单位,假如说“线程”是用来计算的“物理”资源,那么fiber就可以认为是计算的“逻辑”资源了。从理念上说,goroutine和WebWorker都是类似fiber或coroutine这样的概念(所以叫做goroutine):它们都是执行逻辑的计算单元,我们可以创建大量此类单元而不用担心占用过多资源,自有调度器来使用一个或多个线程来执行它们的逻辑。

Go语言使用go关键字来将任意一条语句放到一个coroutine上去运行。假如只是简单地执行一段逻辑,那么这和丢一段代码去线程池里执行可以说没有任何区别。但关键就在于,由于一个coroutine几乎就是个普通的对象,因此我们往往可以放心地阻塞它的逻辑,一旦阻塞调度器可以让当前线程立即去执行其他fiber上的代码。这里的阻塞往往就是通过Go语言中的channel带来的,一般来说会发生在“读”和“写”的时候:

func DoSomething(ch chan int) {
    ch <- 1
    var i = <-ch
}

上面代码中的ch就是一个用来保存int类型数据的channel。第一行代码是向其写入数据,可能在channel写满的时候阻塞。第二行则是从中获取数据,在channel为空的时候阻塞。可以看出,所谓channel其实就是一个再简单不过的容器而已。假如要类比.NET类库,则可以认为它是一个实现了ITargetBlockISourceBlock的对象(例如一个BufferBlock):

static async void DoSomething<T>(T block) where T : ISourceBlock<int>, ITargetBlock<int> {
    await block.SendAsync(1);
    var i = await block.ReceiveAsync();
}

类似Go语言中的超时等特性自然也一应俱全。当然,这里还并不能完全说是“类库”,毕竟还用到了C# 5里的async/await特性。我相信假如您对async/await有所了解的话,肯定也会听到一些它跟coroutine相关或类比的声音。它们在概念和效果上的确十分相似,当然背后的实现是有很大不同的。假如你一定要用coroutine,那还是免不了由语言或运行时提供支持。不过基于goroutine和channel的编程模式几乎完全可以由类库来实现。

在Go语言中,基于goroutine和channel的编程模式往往是这样的:

func (ch chan int) {
    for { // 死循环
        var msg = <-ch

        Process(msg)
    }
}

这样的“代码编写模式”是基于阻塞的,这需要coroutine支持。不过假如我们把需求分析到最基础的部分,它其实仅仅是:

  1. 可以创建大量队列,每个队列可以保存大量任务。
  2. 单个队列中的任务严格串行。
  3. 尽可能高效地(自然可以并行)处理系统中所有队列里的任务。

这就完全是类库能实现的功能了,各个平台上的此类成熟类库并不少见:

  1. iOS上的GCD,或者说libdispatch。
  2. Java平台上与GCD理念相同的HawtDispatch类库。
  3. 与Scala语言关系更为密切的Akka类库。
  4. .NET中的TPL Dataflow(之前提到的BufferBlock的出处)。

这些类库与Go语言中基于goroutine和channel的开发方式有着相似的基础,也完全有能力使用同样的方式来架构系统。基于这些类库,我们只需要提交大量的任务,至于这些任务什么时候被执行则是内部实现所关心的问题,类库自身将会把这些任务调度到物理线程上执行,用一种最高效,代价最低的方式。当然,我们也可以对这些队列进行一些配置,这甚至比Go或Erlang中直接由语言运行时来提供的调度支持有更细致的控制粒度。

我在工作中用过HawtDispatch和TPL Dataflow,也深刻体会到它们的价值。尤其是后者,我用TPL Dataflow实现的业务更为复杂,简直可以说大大改善了我的工作品质,拿它来模仿之前的编程模式则可以是这样的:

var block = new ActionBlock<int>(Process);

往这个block对象里塞入的任何对象都会使用Process方法进行处理。当然TPL Dataflow的功能不止如此,它有着大量的高级功能,例如TransformBlock可以在保证顺序的情况下进行一对多的数据转换,十分好用。具体内容可以参考这篇说明

当然,像Go与Erlang这种对coroutine和并发直接提供支持的语言还可以有其他一些做法,例如Go可以做到先从channel A中获取数据,然后在一个逻辑分支中再从channel B中获取数据。这对于只提供任务队列的类库来说做起来就麻烦一些了(对于C#和Scala这类语言来说依然不成问题),不过在我的经验里这个限制似乎并不会成为严重的阻碍,我们依然可以实现相同消息架构。

说起Erlang,其实在我看来它比Go的channel要好用不少。原因在于Erlang是动态类型语言,它的receive操作可以用来匹配当前队列(在Erlang里叫做mailbox)中不同模式的元组,筛选出符合特定模式的消息。与此相反,Go是静态类型语言,它总是从一个channel中依次获取类型相同的元素,这就完全类似于Java或C#中的泛型集合了。当然,这也不会是个影响系统设计的大问题。

说实话,我觉得这篇文章描述过多,但缺乏案例。其实我本来想通过改写《Go语言编程》中的范例来说明问题,但后来发现书中关于channel和goroutine的例子实在太简单了,没法体现出一个这个特性所带来“架构设计”。所以,示例什么的找机会再说吧。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

58 条回复

  1. astaxie
    114.80.133.*
    链接

    astaxie 2013-04-09 14:39:31

    其实任何语言都可以实现类似goroutine和channel的方式,因为这仅仅是CSP的一种理念,所以你会觉得其实OC/Java/C#等都实现了类似的类库,但是go里面的这个操作你会觉得编写并发相当的简单,只需要go 函数就可以并发了,然后不同的通道通过channel来交互数据,至于你说的channel只能获取相同的元素,这个我觉得可以使用interface来实现动态的元素传递。

    这里有一篇文章介绍的Go的并发之美我觉得很好的描述了Go的goroutine带来架构设计:http://www.yankay.com/go-clear-concurreny/

  2. 老赵
    admin
    链接

    老赵 2013-04-09 15:21:37

    @astaxie

    你也可以在其他语言里为自己准备一个Go函数来并发,此外用interface来实现动态元素传递也是最普通的其他语言都能做到的workaround么……

    其实我说的就是,goroutine并没有带来的什么新的些架构设计。你给的那篇文章其实并没有说Go,更像是用Go来举例说明这种基于coroutine的架构设计,各种语言都能轻松实现。

  3. runner.mei
    58.246.2.*
    链接

    runner.mei 2013-04-09 16:10:15

    我非常赞同你的观点, 它就是把别的平台上类库的功能内置在语言里, 这个好处就是简单, 只给你一种方法解决问题,省得你难以选择.

    另外我对你的对channel的理解有一点不一样, channel在go中的内部实现确实就是一个queue, 但在go语言中channel它不是用来传递任务或动作的, 而主要是用于两个 goroutine 之间传递消息的.这个消息是用于两个 goroutine 为完成一个共同任务的协调信息, 而不是任务本身.

    当然, 你也可以将它作为一个任务队列使用.

  4. 老赵
    admin
    链接

    老赵 2013-04-09 16:21:50

    @runner.mei

    用来放消息还是放任务是一个概念,或者说就是个“保存对象”的作用,对象带行为那就是任务,不带行为那么就是纯消息,区别在于行为定义的位置。我举的那些类库完全也可以按照发送消息(纯数据),完全就看你怎么用了。用文章里的话就是说,不影响架构设计方式。

  5. runner.mei
    58.246.2.*
    链接

    runner.mei 2013-04-09 17:19:24

    用来放消息还是放任务是一个概念,或者说就是个“保存对象”的作用

    这个就是你我对channel理解的区别, 我认为 channel 是交流的手段. 从channel默认将队列长度设置为0可以看出这一点。

  6. 老赵
    admin
    链接

    老赵 2013-04-09 17:36:16

    @runner.mei

    对于类库来说不涉及具体的理解方式,换句话说,只要可以实现你理解的东西就行,此外还可以实现别的,只不过我们暂时不谈别的。

    类库么就是工具,就是提供这么一些功能,具体怎么用,表达什么概念,怎么参与设计,是使用者的选择。

  7. MaskRay
    59.66.131.*
    链接

    MaskRay 2013-04-09 18:59:43

    對,message-passing concurrency的兩種實現方式:channel(Go)、actor(Erlang),兩者各有千秋。

  8. days
    115.196.143.*
    链接

    days 2013-04-09 19:20:07

    最早入行的时候,总觉得无的不能,面临所有需求都会很简单的,怎么怎么做就可以了。到后来慢慢知道,这个世界不是yes和no的世界,能做和做的好是两个概念。有时候做的不好=不能做。 如果仅仅是以能做和不能做来判定,那么纸带式的二进制编程会是主导性的开发方式,不会有什么高级语言。

  9. 老赵
    admin
    链接

    老赵 2013-04-09 19:46:39

    @days

    又来这种套话,我只是说做或不能做了么?你觉得我不懂这道理还会批评Java语言么?文章都不看就来装逼好蛋疼。

  10. afutureboss
    114.249.231.*
    链接

    afutureboss 2013-04-09 20:11:31

    菜鸟,看不懂,你能不能写点水文,教教我们这些菜怎么发展

  11. tony
    122.224.121.*
    链接

    tony 2013-04-09 22:55:13

    用库可以搞。但是语言层支持,有一个好处是:调度器可以不断的优化,增加阻塞切出点。比如:channel本身,本地磁盘文件io,系统调用等等。要同步非阻塞,需要一揽子的基础设施。我觉得java做不到这个。erlang做到了,而且比目前的go做的更好。

  12. 老赵
    admin
    链接

    老赵 2013-04-09 23:19:38

    @tony

    库也可以不断优化的,你说的就相当于例如在C#里的SysCallAsync()调用从伪异步变成真异步IO,而调度策略优化更加是类库本身要实现的东西了,理论上说类库可以提供的控制会更细,所以之前也有人(好像就是邓草原)提到Scala的调度策略就没有Erlang里出现的什么问题。

    你说的同步非阻塞,这当然不是Java靠类库能做到的,我文章里说的很清楚,可以再仔细读读。

  13. douzifly
    218.17.158.*
    链接

    douzifly 2013-04-10 09:41:00

    分析的很到位~学习了。

  14. DT
    210.13.75.*
    链接

    DT 2013-04-10 13:23:09

    文章让我想到了在ObjC中一般会是用@“”字符串,而不太会用C字符串和它的字符串库函数,虽然功能上基本是等价的, 对于Golang内化并发库的好处,我的理解它更多是一种语法糖,对比示例中C#代码和Golang代码,哪个更优雅就很明显了 (虽然C#也内化了await等关键字),而一个好的语法糖就为功能的大规模应用做好准备。

  15. 老赵
    admin
    链接

    老赵 2013-04-10 18:00:10

    @DT

    还真不觉得哪个更优雅,优雅的意思只是省字符么?那么Go还真挺优雅的,既有var又有:=

  16. reus
    113.107.164.*
    链接

    reus 2013-04-10 21:21:21

    select结构怎样实现?

  17. 老赵
    admin
    链接

    老赵 2013-04-10 22:19:10

    @reus

    似乎没法简单实现,一直没搞懂它的使用场景是?

  18. reus
    113.107.164.*
    链接

    reus 2013-04-11 13:37:56

    因为golang是强类型语言,所以一个进程需要同时接收多种类型的消息时,就要使用多个channel。select可以同时进行多个channel的读和写。和posix的select类似。但也像进程一样,轻量很多。golang确实没有什么新鲜概念,用os提供的线程管道epoll/iocp之类也能实现,但把这些机制做得廉价,可以大量使用,就是其他环境提供不了的了。

  19. 老赵
    admin
    链接

    老赵 2013-04-11 14:09:37

    @reus

    假如是解决这点,那就是相当于Merge了多根管道——这跟Erlang里同一个Mailbox放各种不同元素的场景还是不同的……

  20. 科技球
    216.239.45.*
    链接

    科技球 2013-04-12 03:03:37

    我不觉得go channel的模式“往往”是for循环然后process。这只不过是一个简单的例子罢了。

    其实你文中也提到了,我觉得一个更常见的用例是:

    从channel中取msg
    从Data Source A中取data (用channel实现的异步)
    if (条件1) {
        从Data Source B中取data (用channel实现的异步)
        Combine data from A and B
    } else {
        从Data Source C中取data (用channel实现的异步)
        Combine data from A and C
    }
    send result back to some channel
    

    固然,用所谓的Workflow和Dependency Graph也可以表达出上面的这段逻辑。只是说,这样做好不好.如果一种逻辑接近programming的control flow,是不是一个语言让它本身就可以表达这种control flow而不是用数据去表达更make sense一些。我记得类似的argument你在支持Jscex以及F# Computational Expression也用过,可是为了说明go没有那么好你却走到了这个argument的相反面。

  21. 老赵
    admin
    链接

    老赵 2013-04-12 09:50:27

    @科技球

    简单说一句,设计架构和逻辑表达是两码事。大层面上我甚至不觉得Java使用这种架构方法有什么问题,小层面上都不谈coroutine了,连Lambda不支持我都觉得不爽了。当然我其实不想说这个。

    我觉得某些Go粉(又给人贴标签了么)真心让人无语,莫名其妙就把我推到了Go的反面,我这篇文章只是在说一种“情况”,你甚至可以认为是“其他平台如何使用Go语言式设计架构”,为什么要认为我在说Go不好?我的书评里也说“用语言来强调编程方式是一种文化,Erlang就是前例”,而且有些地方既然你都知道我文中也提到了,你还认为我在说Go坏话?非要明文把Go夸到天上有地下无你们才满意么?

    有必要那么敏感脆弱么。

  22. 科技球
    76.21.115.*
    链接

    科技球 2013-04-12 11:16:56

    我不是go粉,我其实没有机会写过一行go的程序,我只是有兴趣思考各种编程语言带来编程思想上的改变。我也没有说你走到了go的相反面,我说的是你走到了语言层面支持比类库支持更好这个argument的相反面。当然你可能不是这样认为的。你在文章里说,“go语言的goroutine和channel其实是把别的语言/平台上类库的功能内置到语言里”,我可能错误理解你的意思,但这句话的语感更像是"XXX其实就是YYY,没什么大不了的"。但是你在Jscex和F# Computational Expression的观点是:“XXX虽然可以通过YYY做到,但是XXX带来的是思想上的改变和飞跃”,所以我才这样子说的,并没有想冒犯你的意思。

    就我个人观点而言,go的channel机制虽然可以通过类库实现,但仍然是编程思想上的一种飞跃,理由同Jscex和F# Computational Expression。当然你可以说是go借鉴了这些东西,只是从时间上说,这些东西至少是同时被发明出来的。另外,我个人感觉go的channel稍稍比F#和C#的async和await更make sense一些,理由我不详述了,如果有时间的话我会写写的。

  23. 老赵
    admin
    链接

    老赵 2013-04-12 11:29:58

    @科技球

    “没有什么大不了的”就是我说的敏感脆弱了,我根本没这么说,唉,更何况我都说了这也是一种语言文化,何必呢。

    此外,我也没有说过“语言支持比类库更好”这种话,我说Jscex或F# Computation Expression更好,都是建立在它们跟传统类库相比做得更好的前提上的。我对他们的支持当然会谈到思想,但更多是思想A比思想B更方便(也就是说并非“虽然XXX也可以通过YYY做到”,或者说能做到但可以做更好),不是为了思想而思想的……

  24. tokimeki
    61.57.134.*
    链接

    tokimeki 2013-04-13 14:13:05

    我想到一個比喻來看線程跟 goroutine / channel:前者像在高速公路開車,後者像在跑接力賽。

    我同意你說的在其他語言上可以用類庫來表現 goroutine / channel,不過 Go 把這個能力放到語言層級應該也沒關係吧?

  25. 爱围观
    219.128.48.*
    链接

    爱围观 2013-04-30 08:51:55

    c#挺强大的,啥功能都有。够人研究一辈子。

    go貌似是建立在单纯的协作式线程上的,语言本身限制了抢占式的线程,目的好像是为了并发啥的。

    线程池怎么模拟这个 runtime.Gosched() yields/让步?

  26. damon
    218.109.158.*
    链接

    damon 2013-05-08 23:18:19

    语言层面支持的和库支持的,还是有一些差别的.比方说gc功能,c++也可以通过类库的方式支持(简单的引用技术),但是C#直接的语言层面支持,就可以减少考虑很多.另外,语言层面支持的map和库支持的map,也是存在一些差别的.java或者c#的包技术,c++也可以通过创建文件夹的方式来做,但是与内建必须的有差别的是,c++的大量工程可能分包并没有java和c#那么通用,沟通成本也更高一些.

    另一个就是库可以被另一个库替换,这个时候就会存在一些沟通上的成本,也就是两个人都需要同时知道这个库才能更好沟通.语言层面的支持,就是说如果你熟悉这个语言,那么就可以直接进行沟通.相比较库,成本会轻量级一些.

    所以我是比较支持在语言层面做一些常见的事情的集成.不过go的写法和go的用法,我个人感觉也不是特别的好

  27. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-12 06:37:33

    如果是动态语言的coroutine可能还真的需要在语言中集成支持,因为调度单元毕竟是种抽象基础设施(不同的VM上有不同实现,共享哪些上下文资源由语言决定),语言不提供库就用不了(对于基础库/内置库/标准库由C或其他静态编译语言实现的情况就当我没说过);但是静态语言么……Windows上有fiber,泛Unix上有setjmp&longjmp/ucontext,已经说明了coroutine可以独立于语言实现。

    PS:WebWorker更像是多线程模型而非coroutine。若干个WebWorkers可以并发执行。

  28. 老赵
    admin
    链接

    老赵 2013-05-12 15:48:25

    @我傻逼我自豪

    coroutine也没说不能并发执行了。把WebWorker跟coroutine类比也是因为它们都可以开大量的对象,跟线程不直接对应。

  29. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-13 02:26:21

    @老赵

    “现代的”浏览器一般都会把WebWorker绑定到线程,至少渲染线程和WebWorker线程必须分开,否则一做大计算,渲染线程就卡住了,然后页面假死,半天点不了。当然可以用coroutine的形式实现WebWorker,但是可用性很差。从这一点上来说,WebWorker和coroutine确实不等价。

    Coroutine本来就是用户级线程的一个等价模型,用系统级线程来实现,其一是太重型,切换开销大;其二是本来单线程无锁访问各种数据的优势就不存在了(当然这个优势也是用可伸缩性换来的)。一般来说,单个系统级线程上的多个coroutine之间一定不存在并发,如果有并发就说明coroutine是用多个系统线程实现的,而这又是另外一个问题了。

  30. 老赵
    admin
    链接

    老赵 2013-05-13 13:13:01

    @我傻逼我自豪: 单个系统级线程上的多个coroutine之间一定不存在并发

    假如是你说的这点,那么WebWorker的确跟coroutine不等价,但我不觉得有这个说法,所以我觉得它们是一种东西。

  31. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-13 17:25:16

    @老赵

    其实我们之间就一个认识差异:老赵你认同coroutine可以用任何可接受的方法实现,而我认同coroutine必须用用户级线程实现(不然我认为没有意义),仅此而已。

  32. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-14 11:38:44

    https://en.wikipedia.org/wiki/Fiber(computerscience)

    根据以上链接的“Fibers and coroutines”一节,coroutine作为一个语言级基础设施,一般倾向于用fiber或用户级线程实现。

    https://en.wikipedia.org/wiki/Web_worker

    根据以上链接的“Overview”一节,WebWorkers被认为是:“……相对重型的。……WebWorkers允许浏览器线程和一个或多个后台Javascript线程并发执行。”

    上面的资料证实了我的观点,coroutine->用户级线程,WebWorkers->系统级线程,二者一般不等价。

  33. 老赵
    admin
    链接

    老赵 2013-05-15 11:04:09

    @我傻逼我自豪

    这里说的WebWorker的线程显然不是“系统级线程”,你可以开几十几百个WebWorker,他们是系统级线程么?这里“浏览器线程”或“JavaScript线程”本身就是指“用户级线程”,用户级线程之间也可以并发。Erlang里还都是“进程”呢,跟操作系统进程能同日而语么。

  34. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-15 23:35:03

    @老赵

    我发现我有时候说话论据不够充分,难以使人信服。所以我就找了一下chromium的实现:(以下源文件的根目录均为chromium官方SVN服务器中的 /trunk 路径)

    chromium/src/third_party/WebKit/Source/core/workers/WorkerThread.cpp
    

    此文件 125 行的

    bool WorkerThread::start() 函数
    

    为chromium浏览器中WebWorker线程创建的起点,调用了 createThread 函数。此函数位于

    chromium/src/third_party/WebKit/Source/wtf/Threading.cpp
    

    文件 72 行,原型如下

    ThreadIdentifier createThread(ThreadFunction entryPoint, void* data, const char* name) 
    

    调用了 createThreadInternal 函数。此函数分为 POSIX 和 Win 两个版本:

    POSIX 版本在

    chromium/src/third_party/WebKit/Source/wtf/ThreadingPthreads.cpp
    

    文件的 184 行:

    ThreadIdentifier createThreadInternal(ThreadFunction entryPoint, void* data, const char*)
    

    其中调用了 pthread_create 函数。根据网上的资料( http://stackoverflow.com/questions/8639150/is-pthread-library-actually-a-user-thread-solution ),我认为在Linux平台下,Chromium使用了系统级线程实现WebWorkers。

    再看Win。Windows 版本在

    chromium/src/third_party/WebKit/Source/wtf/ThreadingWin.cpp
    

    文件的 224 行:

    ThreadIdentifier createThreadInternal(ThreadFunction entryPoint, void* data, const char* threadName)
    

    其中调用了 _beginthreadex MSVC C语言运行时库函数。根据网上诸多教程以及前一阵子我对crt源码的阅读,我认为在Windows平台下,Chromium使用了系统级线程实现WebWorkers。

    由于我完全不了解OSX系统,所以不太清楚 pthread 在OSX系统下使用了何种线程模型。但可以肯定的是,在Linux和Windows这两大系统上,Chromium都使用了系统级线程实现WebWorkers。也就是说,“现代”浏览器倾向于使用系统级线程实现WebWorkers。

  35. 老赵
    admin
    链接

    老赵 2013-05-16 10:47:56

    @我傻逼我自豪

    标准摘录

    Workers (as these background scripts are called herein) are relatively heavy-weight, and are not intended to be used in large numbers. For example, it would be inappropriate to launch one worker for each pixel of a four megapixel image. The examples below show some appropriate uses of workers.

    好像的确是我理解错了,那么我到底是从哪里看到说WebWorker可以创建很大量的?

    多谢纠正。

  36. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-16 17:45:17

    @老赵

    可能是WebWorkers标准刚出台的时候,Chrome/firefox使用了fiber/用户级线程模型,造成了误解的缘故。

    Firefox的实现没有仔细看,代码量比Chrome要大一些……

  37. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-16 17:56:04

    @老赵

    其实即便是系统提供的fiber/用户级线程,也不能开得太多(当然动态语言的coroutine可以,估计Ruby/Lua的coroutine开几千个没问题),因为fiber/用户级线程的栈都是独立的,开得太多会大量消耗内存资源。

    这两天我做了一个Win32绑定的coroutine实现(类似于fiber),发现4KB的栈都不够调用msvcrt的printf一次,被逼用了64KB(60KB可用,4KB保护页面),可想而知如果开得太多会怎么样。

  38. 老赵
    admin
    链接

    老赵 2013-05-16 23:13:28

    @我傻逼我自豪

    看怎么实现的,理论上完全可以做到coroutine跟系统资源无关,就是最普通不过的对象,反正只要使用效果到了就行。

  39. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-17 01:06:46

    @老赵

    如果是动态语言很容易写出轻量化coroutine,因为可共享资源都是VM在控制。我写的是个系统级的版本,可以替代fiber用,所以量级重一些。

  40. bombless
    113.111.120.*
    链接

    bombless 2013-05-17 14:44:48

    你这叫做双重标准。

    你以前支持C#的时候就说语法糖好,现在批其他语言了就开始说语法糖没用了……

  41. 老赵
    admin
    链接

    老赵 2013-05-17 15:14:23

    @bombless

    你这叫脑补,我从头到尾没有说这种做法是缺点,我只是做一个客观的描述,而且我文章里都说了“这也是一种文化表现”。

    所以我说Go的使用者真可怕,不明着说“Go语言好Go语言妙Go语言顶呱呱”的都当是黑。

    更何况你还真别说,C#的可都不是普通的语法糖。

  42. 老赵
    admin
    链接

    老赵 2013-05-17 15:16:03

    @我傻逼我自豪

    跟是不是VM或是不是动态语言没关系,就看编译成什么样,可以有VM,也可以直接编译成native的,不是所有的编译都得像C语言那样直白。

  43. 我傻逼我自豪
    120.6.154.*
    链接

    我傻逼我自豪 2013-05-18 01:03:37

    @老赵

    你说得对。

  44. lili
    222.69.21.*
    链接

    lili 2013-06-17 11:15:59

    @老赵 你多大了?

  45. QLeelulu
    116.21.82.*
    链接

    QLeelulu 2013-07-20 00:07:17

    你这叫脑补,我从头到尾没有说这种做法是缺点,我只是做一个客观的描述,而且我文章里都说了“这也是一种文化表现”。 所以我说Go的使用者真可怕,不明着说“Go语言好Go语言妙Go语言顶呱呱”的都当是黑。 更何况你还真别说,C#的可都不是普通的语法糖。

    哪里的go使用者是这样的啊? 话说我认识的雨痕和360的某位攻城湿都不会这样啊, 他们天天在群里黑go。

    另外对于《Go语言编程》这本书我看过后就再不会给别人推荐了, 我现在都推荐雨痕的go笔记 https://github.com/qyuhen/book

  46. hello
    118.113.227.*
    链接

    hello 2013-08-30 16:08:11

    那这么说来,“别的平台上库嵌入到语言里”的可就多了。。。能不能说很多语言都把C的链表库之类的嵌入成list、dict等?

  47. microcai
    223.93.175.*
    链接

    microcai 2013-09-03 02:07:28

    C++ 里也有这种协程调度。Boost.Asio 里的 coroutine

    Asio 的 coroutine 其实只是 closure, 注意不要和 Boost.coroutine 混淆。 asio 的 coroutine 应该最接近于 go 的 goroutine 了。

    使用的时候需要使用 BOOSTASIOCOROREENTER 和 BOOSTASIOCOROYIELD 两个宏。

    一些小介绍可以 看看 http://microcai.org/2013/03/17/stackless-coroutine.html

  48. linustd
    127.0.0.*
    链接

    linustd 2013-10-19 22:27:20

    不是“你认为”goroutine和channel是把别的平台上类库的功能内置在语言里,

    而是,“就是”goroutine和channel是把别的平台上类库的功能内置在语言里。

    开始了解go语言的时候,我发现字典等类型,竟然被Go语言内置到语言里了,很不爽,觉得这没有原则,不是最优的。

    后来还发了一个帖子声讨,结果有人回复很简单: 既然字典使用的非常频繁,为什么不能内置到语言里呢?

    go语言应该是一个兼有设计精炼,又务实求效的语言。所以把dict/goroutine/channel都内置到语言里了。

    其实.net更无耻,直接用巨大并且不断膨胀的类库实现务实的目的,这还不如go呢。

  49. xczx
    14.153.32.*
    链接

    xczx 2013-12-04 15:02:07

    我一想到事务内存,就感到一种dandan的幽桑

  50. 链接

    Join Smith 2014-04-15 17:15:56

    我正在用c#写一个多线程的程序,c#的基于内存共享数据同步真心坑爹,需要小心地避开各种死锁,然后就到你这儿,真心不敢赞同

  51. 老赵
    admin
    链接

    老赵 2014-04-24 11:36:32

    @Join Smith

    你看懂文章在说什么了吗?

  52. 我傻逼我自豪
    74.79.58.*
    链接

    我傻逼我自豪 2014-04-27 14:49:31

    老赵好久不见了,近来好吗:)

  53. qkb_75_go
    111.204.254.*
    链接

    qkb_75_go 2014-11-22 15:55:09

    其他C/C++/JAVA/PATHY语言和平台上的类库的功能,其实不过是把机器指令汇编数内置在语言或函数里的。

    老赵你到底想说什么呢?

  54. 老赵
    admin
    链接

    老赵 2014-11-23 18:03:47

    @qkb75go

    忘了,不过现在我想说,你没看懂文章。

  55. kenneth.jing
    106.187.49.*
    链接

    kenneth.jing 2015-01-29 16:38:26

    我觉得吧,楼主并没有说怎么好怎么不好,只是说go把别的平台上类库实现或可以通过类库实现的功能内置了,这个话本身没什么问题,只是个客观描述。不过,整个go都是开源的,完全可以在src/runtime下边找到chan的实现代码,又加上本身是native运行,库和内置的界限就不是那么明显了,因为所有的“内置”功能,都和库的表现是一致的,即需要才会被链接。 从使用角度而言,go本身还没有成为一个可以和.net相比的非常强大的系统,但是因为开源、跨平台、极简的设计,让它具有各种更好的潜力。因此,我本人是go的脑残粉。

  56. kenneth.jing
    106.187.49.*
    链接

    kenneth.jing 2015-01-29 17:36:19

    https://www.youtube.com/watch?v=QDDwwePbDtw google的人自己也说了,内置并发而不是通过库实现,使得语法更简洁。

  57. 链接

    liyonghelpme 2015-09-13 02:00:50

    go 从思想上确实没有什么新颖之处,优点大概就是比较知名的完全并行化的语言实现,没有同步设计的历史负担,例如c#这货一个网络库,可以有好几种编程模型。

  58. kingluo
    183.48.84.*
    链接

    kingluo 2016-06-20 14:30:04

    不明白为什么大家讨论goroutine和channel的时候,都忘记了goroutine本身的特性,以及能线性编写代码逻辑而无需考虑异步或者回调的特性。

    goroutine跟actor有很大区别,在我看来,actor只是一种调用形式的改变而已,它没有自己的堆栈(所以不能yield或者resume),也回避了如何在actor里面做CPU密集型任务或者阻塞IO的事情,甚至连非阻塞IO也没有考虑(akka将TCP/UDP的调用转换为message receive,算是一种折中,否则当面临实际的网络编程的时候,你还是要自己去封装Java NIO)。actor只适合用来开发上层逻辑,一旦涉及大量计算和IO,你只能通过其他途径去弥补。我们现实编程里面,是绕不开大量计算或IO的,例如socket的读写。

    goroutine只是表面,背后是golang的标准库,你可以线性编写代码,包括对标准库的调用,完全不需要担心socket是否阻塞或非阻塞,异步或同步,甚至在做CPU密集型任务的时候,也能像erlang那样,在某些切入点被抢占调度,使得时间片更均匀分布在不同goroutine上。这是在其他语言不能做到的,即便你打算去用一些库去重现这种行为也很难,因为语言本身限制了扩展性;再说,你能将标准库所有的IO都转换为coroutine形式的代码吗?例如JDK。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我