16/04/2020 - GO
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.
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)
}
}
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
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
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)
}
}
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