【问题标题】:In Go, does it make sense to write non-blocking code?在 Go 中,编写非阻塞代码有意义吗?
【发布时间】:2011-09-13 19:04:31
【问题描述】:

从 node.js 的角度来看,所有代码都是非阻塞的。

在 Go 中,使用通道很容易实现非阻塞。

如果在 go 中编写一个 node.js 类型的服务器,让它成为非阻塞的有意义吗?例如,让数据库 connect() 函数返回一个通道,而不是在等待连接发生时阻塞。

对我来说,这似乎是正确的方法

但是……

【问题讨论】:

  • 非阻塞异步IO优于阻塞IO。所以是的。
  • @raynos:我不知道这是不言而喻的。例如,在标准的 php/apache 设置中,在一个进程中被阻止仅仅意味着其他进程得到了它们的滴答声。在 node.js 中,非阻塞是必要的,因为它的 javascript 是单线程的。
  • @ccyoung 在 php/apache 中为每个连接创建一个低效的线程/进程。你需要这样做,因为你阻止了。如果你不阻止你减少这个开销。顺便说一句,php 也是单线程的,apache 产生多个 php 进程。如果 Node 想要缓慢/低效,它可以选择这样做。
  • @MattJoiner 阻塞调用使线程进入睡眠状态。那简直是浪费资源。线程不应该休眠,它们应该空闲。除了阻塞IO还有什么好处?
  • @cdunn2001 idle 表示他们正在等待更多任务。阻塞/休眠/等待意味着他们正在等待单个任务完成。基本上空闲意味着它什么都不做,但它可以在任何时间任何事情

标签: node.js go blocking


【解决方案1】:

阻塞和非阻塞实际上与性能无关,它们与接口有关。 如果您有一个执行线程,那么阻塞调用会阻止您的程序在等待时执行任何有用的工作。 但是,如果您有多个执行线程,则阻塞调用并不重要,因为您可以让该线程阻塞并在另一个线程中执行有用的工作。

在 Go 中,当一个 goroutine 在 I/O 上阻塞时,它会被换成另一个。 Go 运行时使用非阻塞 I/O 系统调用来避免操作系统阻塞线程,因此可以在第一个等待 I/O 时在其上运行不同的 goroutine。

Goroutines 真的很便宜,所以不需要编写非阻塞式代码。

【讨论】:

  • 谢谢。正如@Nathan 所问的那样,您认为数据库接口的 connect() 和 query() 非阻塞是个好主意吗?感谢您的洞察力。
  • 是的,最好让你的所有函数都阻塞 goroutine。这使它们更易于使用和思考。调用者总是可以很容易地让它们自己不阻塞。
  • 你希望一个函数只阻塞依赖于该函数结果的代码——这应该是显而易见的。如果您不需要等待函数的结果,则将其粘贴在 goroutine 中。
  • 是什么让 goroutines 比任何其他用户态线程库更便宜?如果上下文切换足够频繁,单独的上下文切换仍然会导致重大的性能损失。如果您在编译时不知道堆栈的最大大小,那么使用多个线程也会产生不可忽略的内存开销。
  • @chmike 切换 goroutine 的成本很小,类似于在事件循环中切换事件的成本。
【解决方案2】:

编写阻塞函数。该语言允许您轻松地将同步调用转换为异步调用。

如果要异步调用函数,请使用 go 语句。像这样的:

c := make(chan bool)
go func() {
    blockingFunction()
    c <- true
}()

// do some other stuff here while the blocking function runs

// wait for the blocking function to finish if it hasn't already
<-c

【讨论】:

  • 感谢这个例子,但我的问题不是如何绕过阻塞,而是是否在golang中编写非阻塞本身就是一个有价值的目标。对不起,如果我没有说清楚;)
  • 我是说因为你总是可以绕过阻塞,所以你应该总是编写阻塞函数。
  • 我可以尊重这一点。关于 db 接口,您不必担心 Connect() 和 Query() 阻塞,如果对应用程序很重要,则让用户绕过。或者可能出于礼貌,该软件包可以提供 ConnectNB() 和 QueryNB()。然后,该软件包还需要提供 LastErr()。
【解决方案3】:

在 Go 中,系统调用是使用操作系统支持的最有效的底层机制以非阻塞方式实现的(例如 epoll)。如果您在等待调用结果时没有其他代码要运行,那么它会阻塞线程(因为没有更好的事情可做),但是如果您有备用的 goroutines 处于活动状态,那么它们将改为运行。

回调(正如您习惯在 js 中使用的那样)允许基本相同的底层机制,但可以说程序员需要更多的心理体操。

在 Go 中,在函数调用之后立即指定要在函数调用之后运行的代码,而不是定义为回调。您希望与执行路径并行运行的代码应包装在 goroutine 中,并通过通道进行通信。

【讨论】:

    【解决方案4】:

    对于典型的网络服务器类型的应用程序,我建议不要让一切都异步。有几个原因。

    • 串行阻塞代码比异步代码更容易推理(更容易发现错误)

    • golang 错误处理基于 defer()、panic() 和 recover(),使用 100% 异步代码可能无法满足您的需求

    • 如果你不小心,Goroutines 可能会泄漏[one discussion]。您的异步行为越多,追踪这些类型的问题就越困难,而且它们出现的可能性就越大。

    一种策略是将异步性集中在高水平上,而让其他一切都阻塞。因此,您可能有一个在逻辑上不同于“请求处理程序”blob 的“数据库处理程序”blob。它们都在单独的 goroutine 中运行并使用通道进行通信。但是在“数据库处理程序”中,建立数据库连接和执行每个查询的调用都是阻塞的。

    您不必选择 100% 异步或 0% 异步。

    【讨论】:

    • 谢谢。这和@jessta 增加了很多。例如,对于 db 接口,我认为 connect() 和 query() 最好具有非阻塞。这是您所说的“数据库处理程序”,还是指例如发布到数据库的函数?另外,感谢有关泄漏的链接。
    • @cc_young connect() 和 query() 在我的示例中将被阻塞并由数据库处理程序调用。数据库处理程序将有一个通道接口,其中包含诸如“更新用户昵称”、“获取朋友列表”之类的操作。要获取朋友列表,您可以在“朋友列表请求”通道上发送用户名和响应通道作为请求。数据库处理程序获取请求,首先检查本地缓存,如果缓存则立即返回。否则,它将对数据库(块)执行查询。获得结果后,将其发送到响应通道。
    • 经过几天的努力,您得出的结论是 100% 正确。
    • 说golang错误处理是基于defer()、panic()和recover()是不正确的说法。 defer() 有很多用途。 panic() 很少使用,应尽可能避免使用。 recover() 是对语言的事后补充。在 Golang 中,错误处理基于函数内部仔细的错误管理,以及错误变量的惯用返回,让调用者决定要做什么(忽略它、修复它或将它报告给它自己的调用者)。
    【解决方案5】:

    阻塞接口总是比非阻塞接口更简单更好。 Go 的美妙之处在于它允许您以简单且易于推理的阻塞风格编写并发(和并行)代码。

    非阻塞编程的流行是由于人们使用的语言(特别是 JavaScript)的缺陷,而不是因为非阻塞编程本质上更好。

    【讨论】:

    • 我怀疑时尚背后也存在一定程度的缺乏经验。 99% 的异步炒作实际上是计算机科学早期辩论的重演,当时人们普遍认为阻塞单线程或重(如计算成本)线程是不可取的。然而,在 80 年代和 90 年代,协同例程和绿色线程的兴起,特别是在函数式语言中,改变了这一点,因为 idle() 的成本从计算成本转移到更多的内存成本(其中,我'将添加,异步不修复)
    猜你喜欢
    • 2011-08-05
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    • 1970-01-01
    • 2016-01-18
    • 2015-11-25
    相关资源
    最近更新 更多