Golang中如何从goroutine调用方法

Golang中如何从goroutine调用方法 我有一个对象 A,它有一些方法:Get1()、Get2()、Get3()…

该对象正在被一个 goroutine 使用。

另一个 goroutine 想要调用这些 Get 方法。

应该如何安全地实现这一点?

我能想到几种处理方式。 可以添加一个互斥锁,但指导原则似乎是在可能的情况下使用通道。

我的问题是:在 Go 中,最符合语言习惯的安全实现方式是什么?

谢谢, Mike

5 回复

当从多个 goroutine 调用访问共享状态(如全局变量或可变数据结构)的方法时,请务必小心。这可能导致竞态条件,即结果取决于 goroutine 不可预测的调度。请使用互斥锁或通道等同步机制来确保对共享数据的安全访问。

更多关于Golang中如何从goroutine调用方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的建议! 我原以为可能会用到通道。 我想闭包可以被放入通道,以延迟对 GetA 的调用。 但这会增加许多复杂性。 您使用简单互斥锁的例子似乎是更好的方法。

你能做类似这样的事情吗?

func main() {
	a := Obj.New()
	
	go func(b){
		b.Get1()
	
	}(&a)
	
	go func(c){
		c.Get2()
	
	}(&a)
	
	...
}

在我看来,地道的做法是在结构体中添加互斥锁。我假设由于这是 get 方法,你只是读取数据,而不修改它。所以可以像这样做:

package main

type A struct {
  sync.RWMutex
  data string
}

func (a *A) Get() string {
  a.RLock()
  defer a.RUnlock()

  return a.data
}

func main() {
  a := &A{data: "some string"}
  go func() {
    fmt.Println(a.Get()) // Prints "some string"
  }()
  // Some code here, to let goroutine to finish...
}

在Go中,从另一个goroutine安全调用对象方法,最符合语言习惯的方式是使用通道进行通信。以下是几种实现方式:

1. 使用通道传递函数调用

type A struct {
    requests chan func()
}

func NewA() *A {
    a := &A{
        requests: make(chan func()),
    }
    go a.run()
    return a
}

func (a *A) run() {
    for f := range a.requests {
        f()
    }
}

func (a *A) Get1() int {
    result := make(chan int)
    a.requests <- func() {
        // 实际执行Get1的逻辑
        result <- 42
    }
    return <-result
}

func (a *A) Get2() string {
    result := make(chan string)
    a.requests <- func() {
        // 实际执行Get2的逻辑
        result <- "value"
    }
    return <-result
}

2. 使用请求-响应模式

type Request struct {
    action string
    data   interface{}
    resp   chan interface{}
}

type A struct {
    reqChan chan Request
}

func NewA() *A {
    a := &A{
        reqChan: make(chan Request),
    }
    go a.processor()
    return a
}

func (a *A) processor() {
    for req := range a.reqChan {
        switch req.action {
        case "Get1":
            req.resp <- a.get1Impl()
        case "Get2":
            req.resp <- a.get2Impl()
        }
    }
}

func (a *A) Get1() int {
    resp := make(chan interface{})
    a.reqChan <- Request{
        action: "Get1",
        resp:   resp,
    }
    return (<-resp).(int)
}

func (a *A) get1Impl() int {
    return 42
}

3. 使用互斥锁的简单方案

type A struct {
    mu sync.RWMutex
    value int
}

func (a *A) Get1() int {
    a.mu.RLock()
    defer a.mu.RUnlock()
    return a.value
}

func (a *A) SetValue(v int) {
    a.mu.Lock()
    defer a.mu.Unlock()
    a.value = v
}

4. 带上下文的通道方案

type A struct {
    cmdChan chan command
}

type command struct {
    fn   func() interface{}
    resp chan interface{}
}

func NewA() *A {
    a := &A{
        cmdChan: make(chan command),
    }
    go a.loop()
    return a
}

func (a *A) loop() {
    for cmd := range a.cmdChan {
        cmd.resp <- cmd.fn()
    }
}

func (a *A) execute(fn func() interface{}) interface{} {
    resp := make(chan interface{})
    a.cmdChan <- command{fn: fn, resp: resp}
    return <-resp
}

func (a *A) Get1() int {
    return a.execute(func() interface{} {
        return 42
    }).(int)
}

通道方案更符合Go的并发哲学:“不要通过共享内存来通信,而应该通过通信来共享内存”。第一种方案是最常见的实现模式,它确保了所有对A的方法调用都在同一个goroutine中顺序执行,完全避免了竞态条件。

回到顶部