Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutability of string and concurrency

Should we synchronize on writing strings? Since string is immutable we will never get inconsistent state between write and read from the 2 different threads, right?

On other words, why we don't have atomic for string type?

like image 923
Rudziankoŭ Avatar asked Jul 09 '18 16:07

Rudziankoŭ


1 Answers

string values are immutable, but variables are not. Variables are–what their name say–variable, their values can be changed.

You don't need synchronization for accessing a string value, that can't change. If a string value is handed to you, that (the content of the string) will always remain the same (usage of package unsafe does not count).

You need synchronization when you want to access a variable of string type from multiple goroutines concurrently, if at least one of the accesses is a write (a write that changes the value of the string variable). This is true for variables of any type in Go, the string type is not special in any way.

What does this mean in practice?

If you have a function that receives a string value "hello", you can be sure the string value will stay "hello" no matter what. Consequently if you don't change the argument yourself (e.g. you don't assign a new value to it), it will always hold the string value "hello".

As a counter-example, if your function receives a slice value []byte{1, 2, 3}, you don't have the same guarantee, because slices are mutable. The caller also has the slice value (the slice header), else it couldn't pass it in the first place. And if the caller modifies the elements of the slice concurrently, since they share the same backing array, the slice that was handed to you will also see the changed data... with proper synchronization; because without synchronization this would be a data race (and hence undefined behavior).

See this example:

var sig = make(chan int)

func main() {
    s := []byte{1, 2, 3}
    go func() {
        <-sig
        s[0] = 100
        sig <- 0
    }()
    sliceTest(s)
}

func sliceTest(s []byte) {
    fmt.Println("First  s =", s)

    sig <- 0 // send signal to modify now
    <-sig    // Wait for modification to complete

    fmt.Println("Second s =", s)
}

Output (try it on the Go Playground):

First  s = [1 2 3]
Second s = [100 2 3]

Focus on sliceTest(): it receives a slice, and it prints it. Then waits a little (gives a "go" to a concurrent goroutine to modify it, and waits for this modification to complete), and prints it again, and it has changed, yet sliceTest() itself did not modify it.

Now if sliceTest() would receive a string argument instead, this could not happen.

See related / possible duplicate: Immutable string and pointer address

like image 130
icza Avatar answered Sep 27 '22 15:09

icza