Nithish Thomas
Nithish Thomas

Reputation: 1565

Any concurrency issues when multiple goroutines access different fields of the same struct

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.

Upvotes: 2

Views: 1412

Answers (2)

Marco
Marco

Reputation: 5109

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

Upvotes: 0

blackgreen
blackgreen

Reputation: 44847

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

Upvotes: 5

Related Questions