去并发访问指针方法

时间:2013-08-08 00:55:50

标签: go goroutine

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

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

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

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

修改

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

我有foo这是MyStruct类型的指针,这个结构有一个带参数的方法DoSomething。在main函数中,我创建了两个go routines,并且它们都调用foo.DoSomething传递不同的值。在这个例子中,第一个例程具有比第二个更大的预编码计算(这里仅使用休眠时间来模拟计算)。结构中没有任何东西在改变我只是在调用结构方法。当第一个例行程序仍在使用该方法时,我是否必须担心第二个例行程序调用{​​{1}}?

foo.DoSomething

3 个答案:

答案 0 :(得分:9)

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

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

same

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

换句话说,接收器,指针或非指针只是一个参数槽。您的问题现在缩小为:

  

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

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

  • foo因任何原因无法重入。
  • foo变异*r(指针)没有协调/同步。
  • 最后一点只是一个特例:如果foo突变任何共享状态,没有协调/同步:任何都可能发生。

如果foo避免了上述tar坑,则 可以安全地由多个goroutine同时执行,即使r值相同。

答案 1 :(得分:3)

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

如果值始终是只读的(永远不会更改),则可以根据需要从尽可能多的go例程中读取。一旦更改了值,就会得到不一致的结果。

要同步访问权限并避免问题(以及潜在的恐慌),您必须使用sync.RWMutex。因此,不使用直接读/写,而是使用getter和setter函数。 getter将使用m.RLock()m.RUnlock()。设置者将使用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中阅读有关并发性的更多信息。虽然频道并不总是适合所有情况,所以它取决于具体情况。

答案 2 :(得分:1)

当有人修改变量而其他人正在阅读变量时,您会遇到竞争条件。在您的示例中,变量foo / self被许多goroutine同时读取,但由于没有人在同时访问它时修改它,一切都很好。

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

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

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