Within Golang struct shared among multiple goroutines, do non-shared members need mutex protection?
Asked Answered
V

2

6

I have one Golang struct shared among multiple goroutines. For concurrent access to struct members, there is the mutex sync.RWMutex. For struct member that is accessed by one single goroutine, is there need of mutex protection?

For example, in the code below, one single writer goroutine accesses the member shared.exclusiveCounter, without any lock protection. Is this correct/safe? Or is there need of mutex because the whole struct is accessed by multiple goroutines thru a shared pointer?

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    s := &shared{mutex: &sync.RWMutex{}}

    readerDone := make(chan int)
    writerDone := make(chan int)

    go reader(s, readerDone)
    go writer(s, writerDone)

    <-readerDone
    <-writerDone
}

type shared struct {
    mutex            *sync.RWMutex
    sharedCounter    int // member shared between multiple goroutines, protected by mutex
    exclusiveCounter int // member exclusive of one goroutine -- is mutex needed?
}

func (s *shared) readCounter() int {
    defer s.mutex.RUnlock()
    s.mutex.RLock()
    return s.sharedCounter
}

func (s *shared) setCounter(i int) {
    defer s.mutex.Unlock()
    s.mutex.Lock()
    s.sharedCounter = i
}

func reader(s *shared, done chan<- int) {
    for {
        time.Sleep(2 * time.Second)
        counter := s.readCounter()
        fmt.Printf("reader: read counter=%d\n", counter)
        if counter > 5 {
            break
        }
    }
    fmt.Printf("reader: exiting\n")
    done <- 1
}

func writer(s *shared, done chan<- int) {
    s.exclusiveCounter = 0
    for {
        time.Sleep(1 * time.Second)
        s.exclusiveCounter++
        fmt.Printf("writer: writing counter=%d\n", s.exclusiveCounter)
        s.setCounter(s.exclusiveCounter)
        if s.exclusiveCounter > 5 {
            break
        }
    }
    fmt.Printf("writer: exiting\n")
    done <- 1
}

Run it on playground

Vidavidal answered 29/1, 2016 at 12:10 Comment(1)
Just from playing around with your code and running it with go run --race it seems like there is no mutex needed for exclusiveCounter. Also if I understand structs right, they are just holding references to their values, so you are actually accessing sharedCounter and exclusiveCounter directly. I would however also like to hear from more experinced go programmers if these assumptions are true.Peripteral
B
4

If only a single goroutine accesses the struct member, you don't need to have a mutex to control access. I would, probably, use one anyway (either re-use the existing mutex in the struct, or another one), on the basis that while there may only be one goroutine accessing that struct member today, there's nothing enforce that.

Brownedoff answered 29/1, 2016 at 13:10 Comment(0)
H
2

You do not need another mutex for it, also if you're just operating on int* types, you can ditch the mutex all together and use atomic.*

type shared struct {
    sharedCounter    int64 // member shared between multiple goroutines, protected by mutex
    exclusiveCounter int64 // member exclusive of one goroutine -- is mutex needed?
}

func (s *shared) readCounter() int64 {
    return atomic.LoadInt64(&s.sharedCounter)
}

func (s *shared) setCounter(i int64) {
    atomic.StoreInt64(&s.sharedCounter, i)
}

playground

Headward answered 29/1, 2016 at 21:31 Comment(2)
I know I don't need another mutex for the exclusiveCounter member. My doubt is, does it need any kind of protection at all, since it is accessed by a single goroutine?Vidavidal
@Vidavidal nope, it's safe to use, also always use the -race when in doubt.Headward

© 2022 - 2024 — McMap. All rights reserved.