专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
程序员小灰  ·  强烈建议尽快搞个软考证!(重大利好) ·  4 天前  
OSC开源社区  ·  AI时代的产品经理:进化还是淘汰? ·  5 天前  
程序猿  ·  这不是找开发人员,这是找超人 ·  6 天前  
OSC开源社区  ·  淘宝程序员是不是没活硬整? ·  1 周前  
程序猿  ·  甲骨文正式发布VirtualBox ... ·  1 周前  
51好读  ›  专栏  ›  OSC开源社区

Go即将支持特定代码在主线程运行

OSC开源社区  · 公众号  · 程序员  · 2024-10-15 16:13

正文

↓推荐关注↓


大家好,我是煎鱼。

国庆的时候看到了一个较少见的提案,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()
}

请注意额外的 goroutineapp.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.Yield()
}

在主线程会打印 hello,world

一些争议

  1. 使用场景过于局限:这个新功能主要是为 GUI 程序和 Windows 服务等少数场景设计的,而大多数 Go 程序并不需要依赖主线程,因此社区有的开发者质疑这个改动的普遍适用性。
  2. init 函数中的潜在问题:有人担心在 init 函数中调用该 API 会导致线程切换,这会增加额外的复杂性,可能引发意外的错误。

总结

这个新提案所输出的新 mainthread 包,本身是一个比较小众的场景。如果有相关业务场景的同学可以关注下。

暂时没有用到的,可以通过本篇文章知道有这么一种方式方法就可以了。以备后用。

END