Golang方法如何生成goroutine

16
据我所知,如果goroutine太忙,它将阻塞其他goroutine运行。对我来说,这意味着我的应用程序的性能和响应能力可能取决于我知道哪些库方法将控制权转移给其他goroutine(例如通常是Read()和Write())。
是否有办法确切地知道不同库方法如何将控制权转移到其他goroutine,即不会实际阻塞?
是否有办法实现调用第三方代码的新方法(包括依赖waitforsingleobject或waitformultipleobjects的异步Win32 API,例如findnextchangenotification),并与Go调度程序“友好”地交互?在这个特定的例子中,系统调用将在完成后发出信号,我需要等待它完成而不会耗尽所有其他goroutine。
在Go中,处理第三方阻塞操作的另一种“最佳实践”方式是否存在,以使它们不会耗尽其他goroutine?
我认为,Go运行时可能内部运行一个IO循环在后台线程上,以便“暂停”阻塞的goroutine操作直到它们完成IO。如果的确如此,那么我想能够基于此构建新的阻塞操作将会非常有用。
3个回答

15

Go的调度程序将暂停等待系统调用的goroutine,并在系统调用完成后唤醒它们,使您可以使用同步API处理异步调用。

阅读更多关于调度程序如何工作的信息。

但是,在决定唤醒哪个goroutine或直接从一个goroutine控制权移交给另一个goroutine方面,没有确切的方法 - 这是调度程序的工作。

您不必担心,您所关注的问题已经在Go中得到解决 - 开始编码吧!

编辑:

进一步说明:您不应该编写代码以符合(或更好地利用)Go调度程序的语义-相反,应该倒过来。可能有一些代码技巧可以稍微提高性能,但调度程序可以在任何未来的Go版本中更改-使您的代码优化无用或甚至适得其反。


谢谢您的回复。我的关注点不是如何让它工作,而是如何使其尽可能好。在我看来,必须有某种内部事件通知循环,并连接到此循环以进行自己的阻塞调用似乎是确保良好性能和调度的最佳方式? - agnsaft
我已经修改了我的答案。 - thwd

12

两种机制可以增强对此的控制:

  1. runtime.Gosched() - 将控制权交还给调度器,但是它无法帮助您已经发出的阻塞调用。

  2. runtime.LockOSThread()会为此goroutine分配一个真正的操作系统线程,没有其他线程竞争,这意味着调度器中将会有更少的竞争。来自文档的描述:

LockOSThread将调用的goroutine与其当前的操作系统线程绑定。在调用goroutine退出或调用UnlockOSThread之前,它将始终在该线程中执行,并且没有其他goroutine可以运行在该线程中。


这很有趣,不过重用现有的事件循环会更有效率吧? - agnsaft
根据这些“阻塞”goroutine的数量而定。截至Go 1.2,即使在不阻塞IO的情况下,goroutine也会让出执行权,并且新的goroutine会抢占调度程序(不确定是否是正确的术语,但你知道我的意思)。我个人将此类事情留给go调度程序处理,除非您正在调用外部C库。 - Not_a_Golfer
调用外部的C库实际上是我最初问题的使用案例。 - agnsaft
如果您不打算为许多 Goroutine 进行此操作,那么锁定操作系统线程也许并不是一个坏主意。 - Not_a_Golfer
吹毛求疵一句:您将“Gosched”错拼为“Goshced”。除此之外,您的回答对我很有帮助。谢谢。 - dimitarvp

5
通过Go 1.1,goroutines只会在阻塞调用(syscalls、通道读写、互斥锁等)时才会让出控制权。这意味着Goroutines实际上可以完全占用CPU,不允许调度程序运行。
在Go 1.2中,引入了一项更改来解决此问题:
在之前的版本中,永远循环的goroutine可能会饿死同一线程上的其他goroutine,当GOMAXPROCS提供仅一个用户线程时,这是一个严重的问题。在Go 1.2中,这个问题已经得到部分解决:调度器偶尔会在进入函数时被调用。这意味着包括非嵌入式函数调用的任何循环都可以被抢占,从而允许在同一线程上运行其他goroutine。
(来源:go1.2发布说明)
理论上,仍然有可能通过不调用非内联函数来占用Goroutines的CPU,但这种情况比从Go 1.2之前开始就不进行阻塞调用的Goroutines要少得多。

感谢您的回复。虽然很有趣并且部分回答了我的问题,但我仍想知道如何使我的自定义阻塞方法以最好的方式与调度程序配合使用?如果调度程序使用某种内部事件通知循环,则应该也可以将其用于自定义库? - agnsaft
1
如果你想强制调度程序运行,可以调用 runtime.Gosched() 方法。但除非你确实注意到了问题,否则这可能没有太大价值。 - James Henstridge
如果您正在使用Go 1.2,除非您有一些没有进行任何函数调用的循环代码,否则不应该出现任何问题。每个函数调用都会给调度程序一个机会来“抢占”当前正在运行的goroutine。编译器和运行时将确定实际采取该机会的时间,但您应该假设它在智能地做出决策方面表现良好。 - joshlf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接