Go 并发访问指针方法

huangapple go评论80阅读模式
英文:

Go concurrent access to pointers methods

问题

我正在尝试理解当同时访问指针的方法时会发生什么情况。

我有一个指针的映射,并生成了几个Go协程。我将映射传递给每个Go协程,每个Go协程将使用映射中的一个值。映射中没有写入操作,只有读取操作。

这个映射很小,只有4个键,所以可能会有多个Go协程使用映射中的同一个值。

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

编辑

示例:我将去掉映射部分,因为这不是我要问的问题。

我有一个类型为MyStruct的指针foo,这个结构体有一个名为DoSomething的方法,接受参数。在main函数中,我创建了两个Go协程,它们都调用foo.DoSomething并传递不同的值。在这个示例中,第一个Go协程的计算量比第二个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)

	// 当第一个Go协程仍在工作时,这个方法调用是否会有问题?
	go foo.DoSomething(2)

	time.Sleep(time.Duration(6 * time.Second))
}
英文:

I'm trying to understand what happens when you make concurrent access to a pointers methods?

I have a map of pointers and spawn off a few go routines. I pass the map into each go routine and each go routine will use one of the values in the map. Nothing is being written to the map only being read from.

The map is small, only 4 keys so it's possible that more than one go routine will be using the same value from the map.

Question is, what happens when two go routines call a method of the same pointer? Will I get unpredictable results?

EDIT

Example: I'm taking out the map portion as that's not the question I'm after.

I have foo which is a pointer of type MyStruct and this structure has a method DoSomething that takes arguments. In the main function I'm creating two go routines and both of them make calls to foo.DoSomething passing different values. In this example the first go routine has a much larger calculation to preform than the second one (just using sleep times here to simulate calculations). Again nothing in the structure is changing I'm only making a call to the structures method. Do I have to worry about the second go routine making a call to foo.DoSomething when the first go routine is still working with the method?

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

答案1

得分: 13

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

func (r *R) foo(bar baz) // 一个方法

与以下相同

func foo(r *R, bar baz) // 一个普通的函数

换句话说,接收器(无论是指针还是非指针)只是一个参数槽。你现在的问题可以简化为:

> 当两个Go协程使用相同的r值调用上述函数时会发生什么?

答:这取决于情况。问题的配置如下:

  • foo由于任何原因都不可重入。
  • foo在没有协调/同步的情况下改变了*r(指针所指向的值)。
  • 最后一点只是一个特例:如果foo在没有协调/同步的情况下改变了_任何共享_状态:_任何事情_都可能发生。

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

英文:

Go methods have receivers. Receiver can be a pointer type. A method with the signature, for example:

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

is the same as

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

In other words, the receiver, pointer or not, is just an argument slot. Your question now reduces to:

> What happens when two go routines call the above function with the same value of r?

A: It depends. Problem configurations:

  • foo is not re-entrant for any reason.
  • foo mutates *r (the pointee) w/o coordination/synchronization.
  • The last point is only a special case of: If foo mutates any shared state w/o coordination/synchronization: anything can happen.

If foo avoids the above tar pits then it is safe for being executed concurrently by multiple goroutines even with the same value of r.

答案2

得分: 7

任何指针都被认为是不安全的。即使可能不是,Go协程也应被视为单独的线程。Go协程在操作系统线程上进行多路复用。

如果值始终是只读的(永远不会改变),你可以从任意数量的Go协程中读取该值。一旦你改变了该值,你将得到不一致的结果。

为了同步访问并避免问题(以及潜在的恐慌),你必须使用sync.RWMutex。因此,你需要使用getter和setter函数来代替直接读取/写入。getter函数将使用m.RLock()m.RUnlock(),setter函数将使用m.Lock()m.Unlock()

在使用互斥锁时,尽量快速解锁。将锁定和解锁之间的代码尽可能简短:

m.Lock()
// 在锁定期间执行所需的操作
mymap[key] = value
m.Unlock()
// 在此处执行其他操作

sync.RWMutexsync.Mutex不同之处在于它允许你拥有任意数量的同时读取者(RLock代表读取锁定)。一旦写入者尝试获取锁定,它将阻止其他读取者获取锁定,并等待现有的读取者释放锁定。

另外,你可以使用通道在Go协程之间传递值。通道适用于许多情况,并且被鼓励使用。你可以在Effective Go中了解更多关于并发的内容。然而,通道并不总是适用于每种情况,这取决于具体情况。

英文:

Any pointer is considered not thread safe. A go routine should be treated as a separate thread, even if it may not be. Go routines are multiplexed over os threads.

If the value is always read-only (will never change), you can read from as many go routines as you want. As soon as you change the value you will get inconsistent results.

To synchronize access and avoid problems (and potential panics) you must use a sync.RWMutex. So instead of reading/writing directly, you use a getter and setter function. The getter would use m.RLock() and m.RUnlock(). The setter would use m.Lock() and m.Unlock().

When using mutexes try to unlock as quickly as possible. Keep the code between a lock and unlock as short as possible:

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

sync.RWMutex is different from sync.Mutex in that it allows you to have as many simultaneous readers as you want (RLock stands for read-lock). As soon as a writer tries to take a lock it prevents other readers from obtaining a lock and waits for the exiting readers to release their locks.

Alternatively you can use channels to pass values between go routines. Channels work for many situations, and encouraged. You can read more about concurrency in Effective Go. Channels don't always fit every situation though, so it depends on the situation.

答案3

得分: 1

每当有人在另一个人正在读取它时修改变量时,就会出现竞态条件。在你的例子中,变量foo/self被多个goroutine同时读取,但由于在并发访问期间没有人修改它,所以一切都正常。

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

如果你想在访问时修改变量,你需要一些同步原语。Go语言的惯用方法是通过仅使用局部变量来避免对同一变量的并发访问,这些变量仅从单个goroutine访问,并且在需要从另一个goroutine获取一些数据时通过通道进行通信。

在Go语言中,还有其他可用的方法,如sync包中的原语,比如sync.Mutex。sync/atomic包还提供了更细粒度的原语,可以用于原子地加载/存储/修改/比较和交换变量。

英文:

You get a race condition whenever someone modifies a variable while someone else is reading it. In your example, the variable foo / self is read by many goroutines concurrently, but since nobody is modifying it while it is accessed concurrently, everything is fine.

A more interesting example would be a struct MyStruct with some additional attributes, e.g. attribute1. In that case, the same rules apply for the attribute as well. You can read it from different goroutines concurrently - for example your DoSomething method might print out the value of self.attribute1 as well - but you are not allowed to modify it during that duration.

If you want to be able to modify a variable while it is accessed, you need some kind of synchronization primitive. The idiomatic Go approach would be to avoid concurrent accesses to the same variable altogether, by only using local variables that are just accessed from a single goroutine and communicate over channels, whenever you need some data from another goroutine.

Alternative approaches that are also available in Go are the primitives from the sync package, like sync.Mutex. The sync/atomic package also offers more fain-grained primitives that can be used to load/store/modify/compare-and-swap variables atomically.

huangapple
  • 本文由 发表于 2013年8月8日 08:55:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/18116224.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定