【问题标题】:Go concurrent access to pointers methods去并发访问指针方法
【发布时间】:2013-08-09 14:05:50
【问题描述】:

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

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

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

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

编辑

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

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

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))
}

【问题讨论】:

    标签: go goroutine


    【解决方案1】:

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

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

    same as

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

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

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

    答:视情况而定。问题配置:

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

    如果foo 避免了上述 tar 坑,那么 可以安全地被多个 goroutine 并发执行,即使 r 的值相同。

    【讨论】:

      【解决方案2】:

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

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

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

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

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

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

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

      【讨论】:

      • 感谢您抽出宝贵时间帮助我,但这并不是我想要的问题。我添加了一个编辑,其中包含我想要了解的代码示例。很抱歉有任何混淆。
      • @Jeff 所以正如我的回答所说,“如果该值始终是只读的(永远不会改变),那么您可以从任意数量的 go 例程中读取。”。它不必是特定于地图的——它是一个指针。同样适用。
      【解决方案3】:

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

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

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

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

      【讨论】:

      • 但我需要从 struct 更改多个属性...如何在例程中实现这一点?目前我想直接操作指针...
      猜你喜欢
      • 1970-01-01
      • 2011-08-14
      • 2018-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-22
      相关资源
      最近更新 更多