1

我们有一个用 Go 编写的监控代理,它使用许多 goroutine 从 WMI 收集系统指标。我们最近发现,当 go 二进制文件在 Server 2016 或 Windows 10(也可能在其他使用 WMF 5.1 的操作系统上)运行时,该程序正在泄漏内存。在创建了一个最小的测试用例来重现该问题之后,似乎只有在您对 ole.CoInitializeEx 方法进行大量调用时才会发生泄漏(WMF 5.1 中可能发生了一些变化,但我们无法使用 python comtypes 包重现该问题在同一系统上)。

我们在我们的应用程序中将 COINIT_MULTITHREADED 用于多线程单元(MTA),我的问题是:因为我们从各种 goroutine 发出 OLE/WbemScripting 调用,我们需要在启动时调用 ole.CoInitializeEx 一次还是在每个 goroutine 中调用一次?我们的查询代码已经使用runtime.LockOSThread来防止调度程序在不同的 OS 线程上运行该方法,但是 MSDN 上关于CoInitializeEx的注释似乎表明它必须在每个线程上至少调用一次。我不知道有什么方法可以确保新的 goroutine 在已经初始化的 OS 线程上运行,因此多次调用 CoInitializeEx 似乎是正确的方法(并且在过去几年中运行良好)。

我们已经重构了代码以在专用的后台工作程序上执行所有 WMI 调用,但我很想知道我们的原始代码是否应该在启动时只使用一个 CoInitializeEx 而不是每个 goroutine 一次。

4

1 回答 1

1

AFAIK,由于 Win32 API 仅根据本机操作系统线程定义,因此调用CoInitialize[Ex]()仅影响它完成的线程。

由于 Go 运行时使用 goroutines 到 OS 线程的自由 M×N 调度,并且这些线程在运行时以对 goroutines 完全透明的方式根据需要创建/删除,因此确保CoInitialize[Ex]()调用具有任何持久影响的唯一方法执行它的 goroutine 是首先将该 goroutine 绑定到其当前 OS 线程,方法是调用runtime.LockOSThread()并为每个打算执行 COM 调用的 goroutine 执行此操作。

请注意,这基本上在 goroutine 和 OS 线程之间创建了一个 1×1 映射,这违背了 goroutine 一开始的大部分目的。因此,假设您可能想要考虑只使用一个 goroutine 调用 COM 并侦听通道上的请求,或者将一个此类工作 goroutine 池隐藏在另一个将客户端的请求分派给工作人员的池中。

关于COINIT_MULTITHREADED. _

引用文档

多线程(也称为自由线程)允许对该线程创建的对象的方法的调用在任何线程上运行。调用没有序列化——许多调用可能发生在同一个方法或同一个对象或同时发生。多线程对象并发提供最高的性能,并充分利用多处理器硬件进行跨线程、跨进程和跨机器调用,因为对对象的调用不会以任何方式进行序列化。然而,这意味着对象的代码必须强制执行其自己的并发模型,通常通过使用同步原语,例如临界区、信号量或互斥锁。此外,由于对象不控制访问它的线程的生命周期,

所以基本上 COM 线程模型与线程本身的初始化无关,而是与 COM 子系统如何允许调用您在 COM 初始化线程上创建的 COM 对象的方法有关。

IIUC,如果您将 COM 初始化一个线程COINIT_MULTITHREADED并在其上创建一些 COM 对象,然后将其引用传递给该对象的某个外部客户端,以便它能够调用该对象的方法,则这些方法可以由操作系统调用在您的进程中的任何线程上。

我真的不知道这应该如何与 Go 运行时交互,所以我会从小处着手,并使用带有 STA 模型的单线程,然后如果需要,可能会尝试使其更复杂。

另一方面,如果您只实例化外部 COM 对象而不将它们的描述符传输到外部(看起来就是这种情况),那么线程模型应该不相关。也就是说,除非 WUA API 中的某些代码会在您已实例化的 COM 对象上调用某些“类事件”方法。

于 2017-03-02T06:58:58.337 回答