We avoid date races on mutable data by keeping it confined to a single thread at a time. By doing this, we prevent other threads from reading or writing the data directly. One way of achieving this is, using "confinement" strategy.


It is a common practise to share a variable/field between goroutines in a pipeline by passing its memory address from one stage to the next over a channel. If each stage of the pipeline refrains from accessing the variable/field after sending it to the next stage, then all accesses to the variable/field are sequential. As a result, the variable/field is confined to one stage of the pipeline then confined to the next and next so on. This is called serial confinement. Since other goroutines cannot access the variable/field directly, they must use a channel to send the confining goroutine a request to manipulate the variable/field which is what meant by "Do not communicate by sharing memory; instead, share memory by communicating" in Golang.



var i string
sync.Mutex.Lock() // Sharing
i = "done" // Read/Write
sync.Mutex.Unlock() // Communicate to say "I am done"

// Then next deals with it.
// Then next ...
// Then next ...


var i string
aChan := make(chan i, 1)
bChan := make(chan i, 1)
cChan := make(chan i, 1)

// Operation a starts first
i = "a done"
aChan<- i

for v := range aChan {
i = "b done"
}
for v := range bChan {
i = "c done"
}
// ...

In this example we are achieving thread safety by using confinement/re-entrancy disiplines. They avoid data races on mutable Image.state field by keeping that data "confined" to a single thread at a time (upload, resize, save). This way we don't give multiple threads the ability to change the Image.state field at same time.


Example 1



import (
"fmt"
"time"
)

type Image struct {
state string
upChan chan struct{}
reChan chan struct{}
}

func New() *Image {
return &Image{
state: "",
upChan: make(chan struct{}, 1),
reChan: make(chan struct{}, 1),
}
}

func (i *Image) Upload() *Image {
i.state = "UPLOADED"

fmt.Println(time.Now().UTC(), "-", i.state)

i.upChan <- struct{}{}
close(i.upChan)

return i
}

func (i *Image) Resize() *Image {
for range i.upChan {
i.state = "RESIZED"

fmt.Println(time.Now().UTC(), "-", i.state)

i.reChan <- struct{}{}
close(i.reChan)
}

return i
}

func (i *Image) Save() {
for range i.reChan {
i.state = "SAVED"

fmt.Println(time.Now().UTC(), "-", i.state)
}
}

Test


Without goroutines


package main

import (
"fmt"

"internal/image"
)

func main() {
fmt.Println("START")

image.New().Upload().Resize().Save()

fmt.Println("FINISH")
}

START
2020-04-16 19:38:30.092691 +0000 UTC - UPLOADED
2020-04-16 19:38:30.092737 +0000 UTC - RESIZED
2020-04-16 19:38:30.092754 +0000 UTC - SAVED
FINISH

With goroutines


package main

import (
"fmt"
"time"

"internal/image"
)

func main() {
fmt.Println("START")

img := image.New()

go img.Save()
go img.Upload()
go img.Resize()

time.Sleep(1 * time.Second)
fmt.Println("FINISH")
}

As you can see, although we are calling methods in wrong order, they run in right order!


START
2020-04-16 20:55:23.206613 +0000 UTC - UPLOADED
2020-04-16 20:55:23.206695 +0000 UTC - RESIZED
2020-04-16 20:55:23.206723 +0000 UTC - SAVED
FINISH

Example 2


package image

import (
"fmt"
"time"
)

type Image struct {
state string
}

func New() *Image {
return &Image{}
}

func Upload(upChan chan<- *Image, img *Image) {
img.state = "UPLOADED"
fmt.Println(time.Now().UTC(), "-", img.state)

upChan<- img
}

func Resize(reChan chan<- *Image, upChan <-chan *Image) {
for img := range upChan {
img.state = "RESIZED"
fmt.Println(time.Now().UTC(), "-", img.state)

reChan<- img
}
}

func Save(reChan <-chan *Image) {
for img := range reChan {
img.state = "SAVED"
fmt.Println(time.Now().UTC(), "-", img.state)
}
}

Test


package main

import (
"fmt"

"internal/image"
)

func main() {
fmt.Println("START")

var (
upChan = make(chan *image.Image, 1)
reChan = make(chan *image.Image, 1)
)

img := image.New()

image.Upload(upChan, img)
close(upChan)
image.Resize(reChan, upChan)
close(reChan)
image.Save(reChan)

fmt.Println("FINISH")
}

START
2020-04-16 10:58:31.018522 +0000 UTC - UPLOADED
2020-04-16 10:58:31.018572 +0000 UTC - RESIZED
2020-04-16 10:58:31.018578 +0000 UTC - SAVED
FINISH