CoInitializeEx(COINIT_MULTITHREADED)和使用WMI的Goroutines

时间:2017-03-02 02:26:06

标签: go com ole goroutine mta

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

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

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

1 个答案:

答案 0 :(得分:1)

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

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

请注意,这基本上会在goroutines和OS线程之间创建一个1×1映射,这会破坏goroutines开始的大部分目的。因此,据说你可能想要考虑只有一个goroutine调用COM并在通道上监听请求,或者有 一群工人goroutines隐藏了另一个工人goroutines,将客户的请求发送给工人。

关于runtime.LockOSThread()

更新

引用the docs

  

多线程(也称为自由线程)允许调用方法   此线程创建的对象在任何线程上运行。没有   调用的序列化 - 许多调用可能发生在同一个方法或   到同一个对象或同时。多线程对象   并发性提供最高性能并且是最好的   多处理器硬件的优势,适用于跨线程,跨进程,   和跨机器调用,因为对对象的调用不是序列化的   以任何方式。但是,这意味着对象的代码必须   通常通过使用来强制执行自己的并发模型   同步原语,例如关键部分,信号量或   互斥。另外,因为对象不能控制生命周期   正在访问它的线程,没有特定于线程的状态   存储在对象中(在线程本地存储中)。

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

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

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

另一方面,如果您只实例化外部COM对象而不是 将他们的描述符转移到外面(似乎就是这种情况), 线程模型不应该相关。也就是说,只有一些 WUA API中的代码会在COM对象上调用一些“类似事件”的方法 实例化了。