Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go concurrent access to pointers methods

Tags:

go

goroutine

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))
}
like image 874
Jeff Avatar asked Aug 08 '13 00:08

Jeff


3 Answers

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.

like image 128
zzzz Avatar answered Oct 28 '22 17:10

zzzz


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.

like image 28
Luke Avatar answered Oct 28 '22 17:10

Luke


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.

like image 20
tux21b Avatar answered Oct 28 '22 15:10

tux21b