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))
}
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.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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With