MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Mutexuri

Pentru a simplifica sincronizarea între gorutine, Go oferă pachetul sync, care furnizează mai multe posibilități, inclusiv mutexuri. Mutexurile permit delimitarea accesului la anumite resurse comune, garantând că doar o singură gorutină are acces la ele într-un anumit moment. Astfel, cât timp o gorutină nu eliberează resursa comună, altă gorutină nu poate lucra cu acea resursă.

La nivel de cod, un mutex reprezintă tipul sync.Mutex. Pentru a bloca accesul la o resursă comună partajată, se apelează metoda Lock() a mutexului, iar pentru a debloca accesul, se folosește metoda Unlock().

În ce situație ne pot ajuta mutexurile? Să analizăm următorul exemplu:

package main
import "fmt"

var counter int = 0             // resursă comună
func main() {

    ch := make(chan bool)        // canal
    for i := 1; i < 5; i++{
        go work(i, ch)
    }
    // așteptăm finalizarea tuturor gorutinelor
    for i := 1; i < 5; i++{
        <-ch
    }
    fmt.Println("The End")
}

func work (number int, ch chan bool){
    counter = 0
    for k := 1; k <= 5; k++{
        counter++
        fmt.Println("Gorutină", number, "-", counter)
    }
    ch <- true
}

Funcția work resetează valoarea variabilei counter la 0 și o crește secvențial până la 5. În funcția main sunt lansate patru gorutine work. Dar care va fi ieșirea în consolă? Aceasta ar putea fi, de exemplu, următoarea:

Gorutină 3 - 1
Gorutină 3 - 2
Gorutină 3 - 3
Gorutină 3 - 4
Gorutină 3 - 5
Gorutină 2 - 1
Gorutină 2 - 6
Gorutină 2 - 7
Gorutină 2 - 8
Gorutină 2 - 9
Gorutină 1 - 1
Gorutină 1 - 10
Gorutină 1 - 11
Gorutină 1 - 12
Gorutină 1 - 13
Gorutină 4 - 1
Gorutină 4 - 14
Gorutină 4 - 15
Gorutină 4 - 16
Gorutină 4 - 17
The End

Deși în fiecare gorutină valoarea counter este resetată la 0 și apoi incrementată până la 5, vedem că mai multe gorutine, după resetarea variabilei, lucrează cu valori complet diferite. Adică, la lansarea gorutinelor, fiecare dintre ele obține valoarea variabilei counter și începe să lucreze cu ea. În timp ce o gorutină încă nu a terminat să lucreze cu counter în ciclu, alta începe să lucreze cu aceeași variabilă. Astfel, mai multe gorutine lucrează simultan cu aceeași resursă comună - variabila counter, ceea ce poate duce la rezultate incorecte, așa cum se întâmplă în acest caz.

Cu ajutorul mutexurilor, putem limita accesul la variabilă astfel încât doar o singură gorutină să aibă acces la ea într-un anumit moment:

package main
import (
    "fmt"
    "sync"
)

var counter int = 0             // resursă comună
func main() {

    ch := make(chan bool)       // canal
    var mutex sync.Mutex        // definim mutexul
    for i := 1; i < 5; i++{
        go work(i, ch, &mutex)
    }
     
    for i := 1; i < 5; i++{
        <-ch
    }
     
    fmt.Println("The End")
}

func work (number int, ch chan bool, mutex *sync.Mutex){
    mutex.Lock()    // blocăm accesul la variabila counter
    counter = 0
    for k := 1; k <= 5; k++{
        counter++
        fmt.Println("Gorutină", number, "-", counter)
    }
    mutex.Unlock()  // deblocăm accesul
    ch <- true
}

Acum, funcția work primește un pointer către mutex. Prin apelul mutex.Lock(), mutexul este blocat de către gorutina respectivă. Aceasta înseamnă că doar gorutina care a blocat prima mutexul va avea acces la următorul cod. Celelalte gorutine vor aștepta până când mutexul va fi eliberat.

Mai departe, gorutina resetează valoarea variabilei counter la 0 și apoi o crește în ciclu. La final, când toate acțiunile cu resursa comună au fost finalizate, gorutina eliberează mutexul prin apelul mutex.Unlock(). Gorutinele care așteaptă primesc semnalul că mutexul a fost eliberat, iar una dintre gorutine blochează mutexul și începe să lucreze cu variabila counter. Așa mai departe, gorutinele blochează și eliberează mutexul pe rând. În final, la următoarea secțiune:

mutex.Lock()    // blocăm accesul la variabila counter
counter = 0
for k := 1; k <= 5; k++{
    counter++
    fmt.Println("Gorutină", number, "-", counter)
}
mutex.Unlock()  // deblocăm accesul

Va avea acces doar gorutina care a blocat prima mutexul. La final, vom obține următorul rezultat:

Gorutină 1 - 1
Gorutină 1 - 2
Gorutină 1 - 3
Gorutină 1 - 4
Gorutină 1 - 5
Gorutină 4 - 1
Gorutină 4 - 2
Gorutină 4 - 3
Gorutină 4 - 4
Gorutină 4 - 5
Gorutină 3 - 1
Gorutină 3 - 2
Gorutină 3 - 3
Gorutină 3 - 4
Gorutină 3 - 5
Gorutină 2 - 1
Gorutină 2 - 2
Gorutină 2 - 3
Gorutină 2 - 4
Gorutină 2 - 5
The End