Veri yarışından kaçınmak için bir veriyi, tek seferinde sadece bir iş parçacığıyla sınırlı tutarız. Bunu yaparak, diğer iş dizilerinin verileri doğrudan okumasını veya yazmasını önleriz. Bunu başarmanın bir yolu "sınırlandırma /hapsetme" stratejisini kullanmaktır.


Bir değişkeni birden fazla goroutinenin çalıştığı hatta paylaşmak için, onun hafıza adresini birinden diğerine kanallar kullanarak iletmek yaygın bir kullanım şeklidir. Hattın her aşaması, bir sonraki aşamaya gönderildikten sonra değişkene/alana erişmekten kaçınırsa, değişkene/alana tüm erişimler sıralı hale gelmiş olur. Sonuç olarak değişken/alan, her seferinde hattın bir aşaması ile sınırlandırılır. Buna serial confinement (seri sınırlandırma) denir. Diğer goroutinler değişkene/alana doğrudan erişemediğinden, sınırlayıcı goroutine değişkeni/alanı manipüle etme isteği göndermek için bir kanal kullanması gerekir ki bu da Golang içinde "Do not communicate by sharing memory; instead, share memory by communicating" (Belleği paylaşarak iletişim kurmayın; bunun yerine, iletişim kurarak belleği paylaşın) olarak anılır.



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"
}
// ...

Bu örneğimizde işlem güvenliğini (thread safety) sağlamak için seri sınırlandırma (confinement)/yeniden giriş (re-entrancy) disiplinlerini kullanacağız. Bunlar değiştirilmeye açık olan Image.state alanını, aynı anda tek bir işlemin (upload, resize, save) kullanımına açarak data races (veri yarışını) engellemiş olurlar. Bu şekilde birden fazla işlemin aynı anda Image.state alanını değiştirme hakkı olmaz.


Örnek 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


Goroutinesiz


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

Goroutine ile


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")
}

Gördüğünüz gibi, yöntemleri yanlış sırada çağırmamıza rağmen, doğru sırada çalışıyorlar!


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

Örnek 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