9

我试图了解当您对指针方法进行并发访问时会发生什么?

我有一个指针映射并产生了一些 go 例程。我将地图传递给每个 goroutine,每个 goroutine 将使用地图中的一个值。没有任何东西被写入地图,只是被读取。

地图很小,只有 4 个键,因此可能不止一个 go 例程会使用地图中的相同值。

问题是,当两个 go 例程调用同一个指针的方法时会发生什么?我会得到不可预测的结果吗?

编辑

示例:我正在取出地图部分,因为这不是我想要的问题。

我有foowhich 是一个类型的指针,MyStruct这个结构有一个DoSomething带参数的方法。在main函数中,我创建了两个go routines,它们都调用foo.DoSomething传递不同的值。在这个例子中,第一个 go 例程比第二个例程要执行更大的计算(这里只使用睡眠时间来模拟计算)。同样,结构中没有任何变化我只是调用结构方法。foo.DoSomething当第一个 go 例程仍在使用该方法时,我是否必须担心第二个 go 例程会调用该方法?

package main

import (
    "log"
    "time"
)

type MyStruct struct {
}

func (self *MyStruct) DoSomething(value int) {

    log.Printf("%d Start", value)

    calculation_time := time.Duration(value) * time.Second
    log.Printf("%d Calculating", value, calculation_time)
    time.Sleep(calculation_time)

    log.Printf("%d Done", value)
}

func main() {

    var foo = new(MyStruct)

    go foo.DoSomething(5)

            // is this method call a problem when the first one is still working?
    go foo.DoSomething(2)

    time.Sleep(time.Duration(6 * time.Second))
}
4

3 回答 3

13

Go 方法有接收器。接收器可以是指针类型。带有签名的方法,例如:

func (r *R) foo(bar baz) // A method

一样

func foo(r *R, bar baz) // A plain old function

换句话说,接收者,无论是否是指针,都只是一个参数槽。您的问题现在简化为:

当两个 go 例程以相同的 r 值调用上述函数时会发生什么?

答:这取决于。问题配置:

  • foo不得以任何理由重入。
  • foo突变*r(被指点者)没有协调/同步。
  • 最后一点只是一种特殊情况:如果foo改变任何没有协调/同步的共享状态:任何事情都可能发生。

如果foo避免了上述 tar 坑,那么即使 r 的值相同,多个 goroutine 同时执行也是安全的。

于 2013-08-08T07:48:42.197 回答
6

任何指针都被认为不是线程安全的。一个 go 例程应该被视为一个单独的线程,即使它可能不是。Go 例程在 os 线程上多路复用。

如果该值始终是只读的(永远不会改变),那么您可以从任意数量的 go 例程中读取。一旦更改值,您将得到不一致的结果。

要同步访问并避免问题(和潜在的恐慌),您必须使用sync.RWMutex。因此,不是直接读/写,而是使用 getter 和 setter 函数。吸气剂将使用m.RLock()and m.RUnlock()。设置器将使用m.Lock()and m.Unlock()

使用互斥锁时,请尝试尽快解锁。保持锁定和解锁之间的代码尽可能短:

m.Lock()
// Do what you need to do for the lock
mymap[key] = value
m.Unlock()
// Do everything else here

sync.RWMutex与sync.Mutex的不同之处在于它允许您同时拥有尽可能多的读者(RLock 代表读锁)。一旦写入者尝试获取锁,它就会阻止其他读取者获得锁并等待退出的读取者释放他们的锁。

或者,您可以使用通道在 go 例程之间传递值。渠道适用于许多情况,并受到鼓励。您可以在Effective Go中阅读有关并发的更多信息。不过,频道并不总是适合所有情况,因此取决于具体情况。

于 2013-08-08T03:02:05.180 回答
1

每当有人修改变量而其他人正在读取它时,您就会得到竞争条件。在您的示例中,变量foo/self被许多 goroutines 并发读取,但是由于在并发访问时没有人修改它,所以一切都很好。

一个更有趣的例子是MyStruct具有一些附加属性的结构,例如attribute1. 在这种情况下,同样的规则也适用于属性。您可以同时从不同的 goroutine 读取它 - 例如,您的DoSomething方法也可能打印出 的值self.attribute1- 但在此期间您不能修改它。

如果您希望能够在访问变量时对其进行修改,则需要某种同步原语。惯用的 Go 方法是完全避免对同一个变量的并发访问,只要您需要来自另一个 goroutine 的一些数据,就只使用刚刚从单个 goroutine 访问并通过通道进行通信的局部变量。

Go 中也可用的替代方法是sync包中的原语,例如 sync.Mutex。该sync/atomic包还提供了更细粒度的原语,可用于以原子方式加载/存储/修改/比较和交换变量。

于 2013-08-08T10:59:17.893 回答