Nadszedł czas wkroczyć do krainy smoków, czyli do wielowątkowości. Jest to dziedzina, z której Go jest znane, więc nie możemy o niej zapomnieć. Zajmiemy się goroutines - czym są, jak ich używać, jak je w ogóle synchronizować, bo to najczęściej jest największym zmartwieniem. W kursie programowania w Golang nie mogło tego zabraknąć. Na filmie pokazuję najprostsze sposoby synchronizacji goroutines i nie tylko, sposoby ich uruchamiania i nie tylko.

Goroutines i sprawy wielowątkowości w Go

Film o goroutines, wielowątkowości i prostych sposobach synchronizacji

 

Przykład z filmu: startowanie funkcji za pomocą słowa kluczowego go

W tym przykładzie zachęcam do eksperymentowania z kolejnością linii 16 i 17 , by sprawdzić jak to wpływa na flow programu.

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func counter(msg string, num int) {
 9	for i := 0; i < num; i++ {
10		fmt.Println(msg, ":", i)
11		time.Sleep(1 * time.Second)
12	}
13}
14
15func main() {
16	go counter("Goroutine1", 5)
17	counter("Normal", 2)
18}

 

Przykład z filmu: prosta synchronizacja kanałami

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func counter2(msg string, synchro chan bool) {
 9	for i := 0; i <= 2; i++ {
10		fmt.Println(msg, ":", i)
11		time.Sleep(1 * time.Second)
12	}
13	synchro <- true
14}
15
16func counter5(msg string, synchro chan bool) {
17	for i := 0; i <= 5; i++ {
18		fmt.Println(msg, ":", i)
19		time.Sleep(1 * time.Second)
20	}
21	synchro <- true
22}
23
24func main() {
25	synch2 := make(chan bool)
26	synch5 := make(chan bool)
27	go counter2("To2", synch2)
28	go counter5("To5", synch5)
29
30	<-synch2
31	<-synch5
32}

 

Przykład z filmu: inne podejście do synchronizacji kanałami

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func counter(msg string, counter int, synch chan int) {
 9	for i := 0; i <= counter; i++ {
10		fmt.Println(msg, "=>", i)
11		time.Sleep(1 * time.Second)
12	}
13	synch <- 1
14}
15
16func main() {
17
18	chSyn := make(chan int)
19	howMany := 0
20
21	for i := 0; i <= 3; i++ {
22		go counter("To", i, chSyn)
23		howMany++
24	}
25
26	num := 0
27	for {
28		num = num + <-chSyn
29		if howMany == num {
30			fmt.Println("Counting finish, closing channel and get out here")
31			close(chSyn)
32			break
33		}
34		fmt.Println("Still working...")
35	}
36
37}

 

Przykład z filmu: synchronizacja za pomocą sync.WaitGroup

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6	"time"
 7)
 8
 9func counter(msg string, wg *sync.WaitGroup) {
10	for i := 0; i <= 3; i++ {
11		fmt.Println(msg, "=>", i)
12		time.Sleep(1 * time.Second)
13	}
14	wg.Done()
15}
16
17func main() {
18
19	wg := &sync.WaitGroup{}
20
21	for i := 0; i <= 3; i++ {
22		wg.Add(1)
23		go counter("To3", wg)
24	}
25
26	wg.Wait()
27
28}

 

Przykład z filmu: case dla race detektora

Poza przykładem, komenda, której używamy: go run -race race.go gdzie race.go, to oczywiście plik z poniższym kodem źródłowym.

 1package main
 2
 3import (
 4	"fmt"
 5	"math/rand"
 6	"sync"
 7	"time"
 8)
 9
10// Source: https://medium.com/@val_deleplace/does-the-race-detector-catch-all-data-races-1afed51d57fb
11
12func main() {
13	// Seeding is important for choosing a random counter
14	rand.Seed(time.Now().Unix())
15
16	// 3 counters
17	a := []int{0, 0, 0}
18
19	// 2 tasks will be launched. The main goroutine will
20	// wait for them to complete.
21	var wg sync.WaitGroup
22	wg.Add(2)
23
24	go func() {
25		i := rand.Intn(3)
26		a[i]++
27		wg.Done()
28	}()
29
30	go func() {
31		j := rand.Intn(3)
32		a[j]++
33		wg.Done()
34	}()
35
36	wg.Wait()
37	fmt.Println(a)
38}

 

Przykład z filmu: gotcha z runtime

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8func main() {
 9	var wg sync.WaitGroup
10	wg.Add(5)
11	for i := 0; i < 5; i++ {
12		go func() {
13			fmt.Println(i)
14			wg.Done()
15		}()
16	}
17	wg.Wait()
18}