2

Are there are any concurrency issues if we access mutually exclusive fields of a struct inside different go co-routines?

I remember reading somewhere that if two parallel threads access same object they might get run on different cores of the cpu both having different cpu level caches with different copies of the object in question. (Not related to Go)

Will the below code be sufficient to achieve the functionality correctly or does additional synchronization mechanism needs to be used?

package main

import (
    "fmt"
    "sync"
)

type structure struct {
    x string
    y string
}

func main() {
    val := structure{}
    wg := new(sync.WaitGroup)
    wg.Add(2)
    go func1(&val, wg)
    go func2(&val, wg)
    wg.Wait()
    fmt.Println(val)
}

func func1(val *structure, wg *sync.WaitGroup) {
    val.x = "Test 1"
    wg.Done()
}

func func2(val *structure, wg *sync.WaitGroup) {
    val.y = "Test 2"
    wg.Done()
}

Edit: - for people who ask why not channels unfortunately this is not the actual code I am working on. Both the func has calls to different api and get the data in a struct, those struct has a pragma.DoNotCopy in them ask protobuf auto generator why they thought it was a good idea. So those data can't be sent over the channel, or else i have to create another struct to send the data over or ask the linter to stop complaining. Or i can send a pointer to the object but feel that is also sharing the memory.

4
  • 1
    To clarify, CPUs don’t have any concept of objects or structs, these boundaries exist only conceptually in the language you are using. Commented Oct 16, 2021 at 12:03
  • Why would you not use concurrency through channels? Could you tell more about the context of the problem? Commented Oct 17, 2021 at 11:43
  • Here is the specific scenario i am thinking about :- main func creates the fields and maybe the cpu1's L1 cache, caches the data, now func1& func2 runs on cpu2 writes data to those fields and finishes the coroutine. when main runs again on cpu1 wouldn't it still think that its cache is the right data. Basically something like this tutorialspoint.com/parallel_computer_architecture/… Or are coroutines guaranteed to run on the same cpu? Commented Oct 22, 2021 at 12:31
  • @Thundercat The memory model text says: “a write of an array, struct, or complex number may be implemented as a write of each individual sub-value” – So, a Go implementation might decide to write struct always as a whole? Commented Jan 30 at 7:13

2 Answers 2

6

You must synchronize when at least one of accesses to shared resources is a write.

Your code is doing write access, yes, but different struct fields have different memory locations. So you are not accessing shared variables.

If you run your program with the race detector, eg. go run -race main.go it will not print a warning.

Now add fmt.Println(val.y) in func1 and run again, it will print:

WARNING: DATA RACE
Write at 0x00c0000c0010 by goroutine 8:
... rest of race warning
Sign up to request clarification or add additional context in comments.

1 Comment

Here is the specific scenario i am thinking about :- main func creates the fields and maybe the cpu1's L1 cache, caches the data, now func1& func2 runs on cpu2 writes data to those fields and finishes the coroutine. when main runs again on cpu1 wouldn't it still think that its cache is the right data. basically something like this tutorialspoint.com/parallel_computer_architecture/… will be required to fix it i guess
0

The preferred way in Go would be to communicate memory as opposed to share the memory.

In practice that would mean you should use the Go channels as I show in this blogpost.

https://marcofranssen.nl/concurrency-in-go

If you really want to stick with sharing memory you will have to use a Mutex.

https://tour.golang.org/concurrency/9

However that would cause context switching and Go routines synchronization that slows down your program.

Example using channels

package main

import (
    "fmt"
    "time"
)

type structure struct {
    x string
    y string
}

func main() {
    val := structure{}
    c := make(chan structure)
    go func1(c)
    go func2(c)

    func(c chan structure) {
        for {
            select {
            case v, ok := <-c:
                if !ok {
                    return
                }

                if v.x != "" {
                    fmt.Printf("Received %v\n", v)
                    val.x = v.x
                }
                if v.y != "" {
                    fmt.Printf("Received %v\n", v)
                    val.y = v.y
                }
                if val.x != "" && val.y != "" {
                    close(c)
                }
            }
        }
    }(c)
    fmt.Printf("%v\n", val)
}

func func1(c chan<- structure) {
    time.Sleep(1 * time.Second)
    c <- structure{x: "Test 1"}
}

func func2(c chan<- structure) {
    c <- structure{y: "Test 2"}
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.