Go即将支持特定代码在主线程运行
The following article is from 脑子进煎鱼了 Author 陈煎鱼
↓推荐关注↓
大家好,我是煎鱼。
国庆的时候看到了一个较少见的提案,Go 竟然会提供主线程 API 的控制(说好的不对外...),还是挺有意思的。
本提案已经 Accepted,会进入 Go 主版本特性。今天分享给大家,一起围观学习一下!
背景
某些 API(尤其是 macOS 的 AppKit 和 iOS 的 UIKit)需要对启动线程进行独占控制。Go 已经支持此类 API,如果在 init
函数中调用了 runtime.LockOSThread
,就会指定主函数在主线程上运行。
但是,现有的设计不允许非主线程包控制主线程,因此任何对主线程 API 的使用都会影响到用户。(本次新提案将对此做出调整)
例如,这是 Gio 当前用于创建和驱动 GUI 窗口的 API:
import "gioui.org/app"
func main() {
go func() {
w := new(app.Window)
for {
e := w.NextEvent()
// Respond to e, usually by redrawing window content.
}
}()
app.Main()
}
请注意额外的 goroutine
和 app.Main
调用。Gio 会在其内部的 init 函数调用原有的 runtime.LockOSThread
来满足原有设计上必须为主线程的要求。
新提案:mainthread 包
包介绍
这个新的 mainthread 包主要用于确保特定代码可以在主线程上运行,也可以满足支持在非主线程包控制主线程,通常用于那些依赖于主线程的 C 库或 GUI 库。
这个包为 Go 程序提供了在主线程上调度函数的能力,同时也提供了一些机制来管理主线程的运行顺序。
以下是新的 mainthread 包的函数前面,非常短小精悍含。
方法如下:
package mainthread // imported as "runtime/mainthread"
func Do(f func())
func Yield()
func Waiting() <-chan struct{}
Do: mainthread
包的核心函数,用于在主线程上运行指定的函数 f。当这个函数运行时,主线程不会执行其他代码,直到 f
完成或调用Yield
。这意味着你可以确保某些代码只在主线程上运行,并避免并发问题。如果 f
是长时间运行的函数,它可以调用Yield
来暂时让出主线程,使其他调用 Do 的函数有机会运行。Yield: Yield
方法用于让出主线程,允许其他被阻塞的Do
调用有机会运行。Yield
之后,当前调用会等待重新获取主线程的控制权。这个方法只能在主线程上调用,即在 Do
函数内调用。如果在其他线程中调用Yield
,程序会发生panic
。Waiting: Waiting
返回一个通道(channel),通道会在有调用 Do 阻塞等待主线程时发出消息。多个阻塞的 Do 调用可能会合并成一个消息。通过监听 Waiting
返回的通道,程序可以确保适时调用Yield
来释放主线程。例如,可以在一个goroutine
中监听Waiting
通道,当检测到有阻塞时,通过事件循环发送Yield
信号,以便调用Yield
让出主线程。
代码例子
以下是伪代码的例子:
package main
import (
"runtime"
"runtime/mainthread"
)
func init() {
runtime.LockOSThread()
}
func main() {
go func() {
mainthread.Do(func() {
print("hello,")
mainthread.Do(func() {
print("world")
})
})
}()
<-mainthread.Waiting()
mainthread.Yield()
}
在主线程会打印 hello,world
。
一些争议
使用场景过于局限:这个新功能主要是为 GUI 程序和 Windows 服务等少数场景设计的,而大多数 Go 程序并不需要依赖主线程,因此社区有的开发者质疑这个改动的普遍适用性。 init 函数中的潜在问题:有人担心在 init
函数中调用该 API 会导致线程切换,这会增加额外的复杂性,可能引发意外的错误。
总结
这个新提案所输出的新 mainthread 包,本身是一个比较小众的场景。如果有相关业务场景的同学可以关注下。
暂时没有用到的,可以通过本篇文章知道有这么一种方式方法就可以了。以备后用。
END