众所周知,libvirt 是线程安全的。 但是,同时运行两个作用于同一资源的 goroutine(例如修改和删除 VM)会使其处于不明确的状态。 libvirt 如何决定 goroutine 的执行顺序?
这是我尝试过的代码:
package main
import (
"fmt"
"github.com/libvirt/libvirt-go"
)
func main() {
conn, err := libvirt.NewConnect("qemu:///system")
if err != nil {
fmt.Printf("Failed to connect to libvirt: %v\n", err)
return
}
defer conn.Close()
// Create a new VM
domainXML := `
<domain type='kvm'>
<name>myvm</name>
<memory unit='KiB'>1048576</memory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='path/to/disk'/>
<target dev='vda' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</disk>
</devices>
</domain>`
dom, err := createVM(conn, domainXML)
if err != nil {
fmt.Printf("Failed to create VM: %v\n", err)
return
}
go modifyVMMemory(dom, 2*1024*1024) // 2 GiB
go deleteVM(dom)
}
func createVM(conn *libvirt.Connect, domainXML string) (*libvirt.Domain, error) {
dom, err := conn.DomainCreateXML(domainXML, 0)
if err != nil {
return nil, err
}
return dom, nil
}
func modifyVMMemory(dom *libvirt.Domain, newMemory uint64) error {
err := dom.SetMaxMemory(newMemory)
if err != nil {
return err
}
fmt.Print("Modified VM")
return nil
}
func deleteVM(dom *libvirt.Domain) error {
err := dom.Destroy()
if err != nil {
return err
}
err = dom.Undefine()
if err != nil {
return err
}
fmt.Print("Deleted VM")
return nil
}
程序成功完成,因此域被破坏并可以重新创建 但再次运行会导致以下错误:
virError(Code=9, Domain=20, Message='operation failed: domain 'myvm' already exists with uuid 32c25acb-a4c5-4bfd-b2f5-f07b3d9b8eea')
您在 Goroutine 中运行
modifyVMMemory
和 deleteVM
,并且在没有检查的情况下返回错误。
errCh := make (chan error, 1)
go func () {
err := modifyVMMemory(dom, 2*1024*1024) // 2 GiB
errCh <- err
}()
err := <- errCh
if err != nil {
// handle the error
}
go func() {
err := deleteVM(dom)
errCh <- err
}()
err = <-errCh
if err != nil {
// handle the error
}
此外,tread-safe库并不一定意味着底层软件,比如qemu就是tread-safe。
众所周知,libvirt 是线程安全的。但是,同时运行两个作用于同一资源的 goroutine(例如修改和删除 VM)会使其处于不明确的状态。 libvirt 如何决定 goroutine 的执行顺序?
线程安全只是意味着当多个线程同时使用同一个连接时,代码不会出现内存损坏问题。
您将获得的语义行为仍然是不确定的。
libvirt QEMU/KVM 驱动程序在客户端应用程序和
libvirtd
(或 virtqemud
)守护进程之间使用 RPC 层。所以首先你有非确定性,其中 Goroutine 首先运行。当 libvirt-go-module
API 通过 CGo 调用 C ibvirt.so
库时,它们将被锁定到本机操作系统线程,然后该线程将在 libvirt.so
内部同步,以决定哪个首先将其 RPC 消息传输到网络上。在 libvirtd
守护进程中也有很多线程,并且 RPC 消息理论上是按 FIFO 处理的,但是,libvirtd
内部的 API 逻辑仍然会争夺锁,因此在对话/交互时会增加更多的不确定性与 QEMU。基本上,您的 SetMaxMemory
和 Destroy
API 调用可以按任意顺序运行。如果您需要保证订购,则需要在应用程序中对它们进行序列化,这样您只能在完成后调用 Destroy
SetMaxMemory
最后 IIUC 你的 Go 代码并不健壮,因为
main()
方法生成了两个 goroutine,但没有等待它们中的任何一个完成。 IOW,在任一 goroutine 完全运行之前,Go 进程很可能会退出。这可能就是为什么您收到有关 VM 已存在的错误消息的原因 - deleteVM
goroutine 在进程退出之前从未运行。