0

Like if I have a struct with an array and I want to do something like this

type Paxos struct {
    peers      []string
}

for _, peer := range px.peers {
   \\do stuff
}

My routines/threads will never modify the peers array, just read from it. Peers is an array of server addresses, and servers may fail but that wouldn't affect the peers array (later rpc calls would just fail)

Shisui
  • 1,051
  • 1
  • 8
  • 23
  • 3
    Only reading is always safe, data race may occur if there is at least 1 write. Also see related: [Can I concurrently write different slice elements](https://stackoverflow.com/questions/49879322/can-i-concurrently-write-different-slice-elements/49879469#49879469) – icza Mar 09 '22 at 16:53

2 Answers2

2

If no writes are involved, concurrent reads are always safe, regardless of the data structure. However, as soon as even a single concurrency-unsafe write to a variable is involved, you need to serialise concurrent access (both writes and reads) to the variable.


Moreover, you can safely write to elements of a slice or an array under the condition that no more than one goroutine write to a given element.

For instance, if you run the following programme with the race detector on, it's likely to report a race condition, because multiple goroutines concurrently modify variable results without precautions:

package main

import (
    "fmt"
    "sync"
)

func main() {
    const n = 8
    var results []int
    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        i := i
        go func() {
            defer wg.Done()
            results = append(results, square(i))
        }()
    }
    wg.Wait()
    fmt.Println(results)

}

func square(i int) int {
    return i * i
}

However, the following programme contains no such no synchronization bug, because each element of the slice is modified by a single goroutine:

package main

import (
    "fmt"
    "sync"
)

func main() {
    const n = 8
    results := make([]int, n)
    var wg sync.WaitGroup
    wg.Add(n)
    for i := 0; i < n; i++ {
        i := i
        go func() {
            defer wg.Done()
            results[i] = square(i)
        }()
    }
    wg.Wait()
    fmt.Println(results)

}

func square(i int) int {
    return i * i
}
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • So if you write to the array, but only write from 1 go routine, it's still safe to read from the array from other arrays without locks? – Shisui Mar 09 '22 at 16:57
  • @Shisui No, as soon as you've got even a single write, you need to be careful even for reads. – jub0bs Mar 09 '22 at 17:01
  • so in your example, if I wanted to also read from results[i] from a different thread then I must use a lock? – Shisui Mar 09 '22 at 17:08
  • @Shisui No, that's not necessary in my second programme because only one goroutine is involved for each element. However, if you put a lock around the write to the `results` variable in my first programme and you had concurrent reads, you'd need to put a lock around the reads as well. – jub0bs Mar 09 '22 at 17:18
1

Yes, reads are thread-safe in Go and virtually all other languages. You're just looking up an address in memory and seeing what is there. If nothing is attempting to modify that memory, then you can have as many concurrent reads as you'd like.

user229044
  • 232,980
  • 40
  • 330
  • 338