GoSuda

Go samanaikaisuus aloituspaketti

By snowmerak
views ...

Yhteenveto

Lyhyt johdanto

Go-kielellä on monia samanaikaisuuden hallintaan tarkoitettuja työkaluja. Tässä artikkelissa esittelemme joitakin niistä ja niihin liittyviä temppuja.

Goroutiinit?

Goroutiini on Go-kielen tukema uusi samanaikaisuusmalli. Yleisesti ottaen ohjelmat suorittavat useita tehtäviä samanaikaisesti saamalla käyttöjärjestelmältä OS-säikeitä ja suorittamalla tehtäviä rinnakkain prosessoriytimien määrän mukaisesti. Pienempien samanaikaisuustasojen suorittamiseksi käyttäjätilassa luodaan green-säikeitä, ja yksi OS-säie suorittaa useita green-säikeitä vuorotellen. Goroutiinit ovat kuitenkin tehneet näistä green-säikeistä pienempiä ja tehokkaampia. Goroutiinit käyttävät vähemmän muistia kuin säikeet ja niitä voidaan luoda ja vaihtaa nopeammin kuin säikeitä.

Goroutiinien käyttö on yksinkertaista: riittää käyttää go-avainsanaa. Tämä tekee ohjelmoinnista intuitiivisempaa, kun synkronista koodia suoritetaan asynkronisesti.

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    ch := make(chan struct{})
10    go func() {
11        defer close(ch)
12        time.Sleep(1 * time.Second)
13        fmt.Println("Hello, World!")
14    }()
15
16    fmt.Println("Waiting for goroutine...")
17    for range ch {}
18}

Tämä koodi muuttaa yksinkertaisen, 1 sekunnin tauon jälkeen "Hello, World!" -tekstin tulostavan synkronisen koodin asynkroniseksi virraksi. Vaikka esimerkki on yksinkertainen, monimutkaisemman koodin muuttaminen synkronisesta asynkroniseksi parantaa koodin luettavuutta, näkyvyyttä ja ymmärrettävyyttä verrattuna async/await- tai promise-tyyppisiin menetelmiin.

Monissa tapauksissa kuitenkin yksinkertaisen asynkronisen kutsun ja fork & join -tyyppisen virtauksen (joka muistuttaa jakoja valloita -menetelmää) ymmärtämättömyys johtaa huonoon goroutiinkoodiin. Esittelemme tässä artikkelissa joitakin tapoja ja tekniikoita tällaisten tilanteiden varalle.

Samanaikaisuuden hallinta

context

Ensimmäisenä hallintatekniikkana context saattaa tuntua yllättävältä. Go-kielellä context kuitenkin ylittää pelkän peruutusfunktionaalisuuden ja toimii erinomaisesti koko työpuun hallinnassa. Lyhyesti sanottuna, jos et ole perehtynyt tähän pakettiin, selitämme sen tässä.

 1package main
 2
 3func main() {
 4    ctx, cancel := context.WithCancel(context.Background())
 5    defer cancel()
 6
 7    go func() {
 8        <-ctx.Done()
 9        fmt.Println("Context is done!")
10    }()
11
12    time.Sleep(1 * time.Second)
13
14    cancel()
15
16    time.Sleep(1 * time.Second)
17}

Yllä oleva koodi käyttää context-pakettia tulostamaan "Context is done!" 1 sekunnin kuluttua. context-paketin Done()-metodi tarkistaa peruutuksen tilan, ja WithCancel, WithTimeout, WithDeadline, WithValue -metodit tarjoavat useita peruutustapoja.

Luodaan yksinkertainen esimerkki. Oletetaan, että kirjoitat koodia, joka hakee tietoja käyttämällä aggregator-kuviota ja hakee tietoja user, post ja comment -lähteistä. Jos kaikkien pyyntöjen tulee valmistua 2 sekunnissa, voit kirjoittaa koodin seuraavasti:

 1package main
 2
 3func main() {
 4    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
 5    defer cancel()
 6
 7    ch := make(chan struct{})
 8    go func() {
 9        defer close(ch)
10        user := getUser(ctx)
11        post := getPost(ctx)
12        comment := getComment(ctx)
13
14        fmt.Println(user, post, comment)
15    }()
16
17    select {
18    case <-ctx.Done():
19        fmt.Println("Timeout!")
20    case <-ch:
21        fmt.Println("All data is fetched!")
22    }
23}

Yllä oleva koodi tulostaa "Timeout!", jos kaikkia tietoja ei ole haettu 2 sekunnissa, ja "All data is fetched!", jos kaikki tiedot on haettu. context-paketin avulla voit helposti hallita peruutuksia ja aikakatkaisuja koodissa, jossa toimii useita goroutiineja.

Lisätietoja context-funktiosta ja sen metodeista löytyy osoitteesta godoc context. Toivomme, että opit käyttämään niitä helposti.

channel

Puskurettomat kanavat

channel on työkalu goroutiinien välisen viestinnän helpottamiseen. channel luodaan make(chan T)-funktiolla, missä T on kanavan välittämän datan tyyppi. channel-kanavan kautta dataa lähetetään ja vastaanotetaan <--operaattorilla ja kanava suljetaan close-funktiolla.

 1package main
 2
 3func main() {
 4    ch := make(chan int)
 5    go func() {
 6        ch <- 1
 7        ch <- 2
 8        close(ch)
 9    }()
10
11    for i := range ch {
12        fmt.Println(i)
13    }
14}

Yllä oleva koodi tulostaa 1 ja 2 käyttäen channel-kanavaa. Tämä koodi näyttää vain yksinkertaisen datan lähettämisen ja vastaanottamisen kanavassa. channel tarjoaa kuitenkin paljon enemmän toimintoja. Tutustumme ensin puskurettomiin ja puskuroituihin kanaviin. Ennen kuin aloitamme, huomaa, että yllä oleva esimerkki käyttää puskuretonta kanavaa, jossa datan lähettämisen ja vastaanottamisen on tapahtuttava samanaikaisesti. Jos näin ei ole, voi syntyä lukkiutuminen (deadlock).

Puskuroidut kanavat

Entä jos yllä oleva koodi ei ole yksinkertainen tulostus, vaan kaksi raskasta prosessia? Jos toisen prosessin lukeminen ja käsittely kestää kauan, ensimmäinen prosessi pysähtyy myös kyseiseksi ajaksi. Voimme välttää tämän käyttämällä puskuroitua kanavaa.

 1package main
 2
 3func main() {
 4    ch := make(chan int, 2)
 5    go func() {
 6        ch <- 1
 7        ch <- 2
 8        close(ch)
 9    }()
10
11    for i := range ch {
12        fmt.Println(i)
13    }
14}

Yllä oleva koodi tulostaa 1 ja 2 käyttäen puskuroitua kanavaa. Tässä koodissa puskuroitu kanava mahdollistaa datan lähettämisen ja vastaanottamisen tapahtumisen ilman samanaikaisuutta. Kanavan puskuri lisää tilaa, mikä estää myöhempien tehtävien aiheuttamia viiveitä.

select

select-lauseketta käyttämällä voidaan helposti toteuttaa fan-in -rakenne, kun käsitellään useita kanavia.

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    ch1 := make(chan int, 10)
10    ch2 := make(chan int, 10)
11    ch3 := make(chan int, 10)
12
13    go func() {
14        for {
15            ch1 <- 1
16            time.Sleep(1 * time.Second)
17        }
18    }()
19    go func() {
20        for {
21            ch2 <- 2
22            time.Sleep(2 * time.Second)
23        }
24    }()
25    go func() {
26        for {
27            ch3 <- 3
28            time.Sleep(3 * time.Second)
29        }
30    }()
31
32    for i := 0; i < 3; i++ {
33        select {
34        case v := <-ch1:
35            fmt.Println(v)
36        case v := <-ch2:
37            fmt.Println(v)
38        case v := <-ch3:
39            fmt.Println(v)
40        }
41    }
42}

Yllä oleva koodi luo kolme kanavaa, jotka välittävät 1, 2 ja 3 säännöllisesti, ja tulostaa arvot kanavista käyttäen select-lauseketta. select-lausekkeen avulla voidaan vastaanottaa dataa useista kanavista samanaikaisesti ja käsitellä dataa heti, kun se on saatavilla.

for range

channel-kanavista dataa voidaan vastaanottaa helposti for range -lausekkeella. Kun for range -lauseketta käytetään kanavan kanssa, se toimii aina, kun kanavaan lisätään dataa, ja silmukka päättyy, kun kanava suljetaan.

 1package main
 2
 3func main() {
 4    ch := make(chan int)
 5    go func() {
 6        ch <- 1
 7        ch <- 2
 8        close(ch)
 9    }()
10
11    for i := range ch {
12        fmt.Println(i)
13    }
14}

Yllä oleva koodi tulostaa 1 ja 2 käyttäen channel-kanavaa. Tässä koodissa for range -lauseke vastaanottaa ja tulostaa datan aina, kun kanavaan lisätään dataa. Silmukka päättyy, kun kanava suljetaan.

Tämä syntaksi sopii myös yksinkertaiseen synkronointiin, kuten jo aiemmin esitettiin:

 1package main
 2
 3func main() {
 4    ch := make(chan struct{})
 5    go func() {
 6        defer close(ch)
 7        time.Sleep(1 * time.Second)
 8        fmt.Println("Hello, World!")
 9    }()
10
11    fmt.Println("Waiting for goroutine...")
12    for range ch {}
13}

Tämä koodi tulostaa "Hello, World!" 1 sekunnin tauon jälkeen. channel-kanavaa käyttämällä synkroninen koodi on muutettu asynkroniseksi ja join-kohta on määritetty.

jne.

  1. Datan lähettäminen tai vastaanottaminen nil-kanavasta aiheuttaa äärettömän silmukan ja lukkiutumisen.
  2. Datan lähettäminen suljettuun kanavaan aiheuttaa paniikin.
  3. Kanavaa ei tarvitse sulkea erikseen, roskienkerääjä sulkee sen.

mutex

spinlock

spinlock on synkronointimenetelmä, jossa lukitusta yritetään toistuvasti silmukassa. Go-kielellä spinlock voidaan helposti toteuttaa osoittimien avulla.

 1package spinlock
 2
 3import (
 4    "runtime"
 5    "sync/atomic"
 6)
 7
 8type SpinLock struct {
 9    lock uintptr
10}
11
12func (s *SpinLock) Lock() {
13    for !atomic.CompareAndSwapUintptr(&s.lock, 0, 1) {
14        runtime.Gosched()
15    }
16}
17
18func (s *SpinLock) Unlock() {
19    atomic.StoreUintptr(&s.lock, 0)
20}
21
22func NewSpinLock() *SpinLock {
23    return &SpinLock{}
24}

Yllä oleva koodi toteuttaa spinlock-paketin. Koodi käyttää sync/atomic-pakettia SpinLock-rakenteen toteuttamiseen. Lock-metodissa atomic.CompareAndSwapUintptr yrittää asettaa lukituksen, ja Unlock-metodissa atomic.StoreUintptr poistaa lukituksen. Tämä menetelmä käyttää jatkuvasti prosessoria, kunnes lukitus saadaan, mikä voi johtaa äärettömään silmukkaan. Siksi spinlock sopii parhaiten yksinkertaiseen synkronointiin tai tilanteisiin, joissa lukitus tarvitaan vain lyhyeksi ajaksi.

sync.Mutex

mutex on työkalu goroutiinien synkronointiin. sync-paketin tarjoama mutex tarjoaa Lock, Unlock, RLock ja RUnlock -metodit. mutex luodaan sync.Mutex-rakenteella, ja lukemisen/kirjoittamisen lukitus voidaan toteuttaa sync.RWMutex-rakenteella.

 1package main
 2
 3import (
 4    "sync"
 5)
 6
 7func main() {
 8    var mu sync.Mutex
 9    var count int
10
11    go func() {
12        mu.Lock()
13        count++
14        mu.Unlock()
15    }()
16
17    mu.Lock()
18    count++
19    mu.Unlock()
20
21    println(count)
22}

Yllä olevassa koodissa kaksi goroutiinia yrittää käyttää samaa count-muuttujaa lähes samanaikaisesti. mutex tekee count-muuttujan käyttöalueesta kriittisen alueen, mikä estää samanaikaisen käytön. Koodi tulostaa aina 2, riippumatta siitä, kuinka monta kertaa sitä suoritetaan.

sync.RWMutex

sync.RWMutex on mutex, joka erottaa lukemisen ja kirjoittamisen lukitukset. RLock ja RUnlock -metodeilla voidaan asettaa ja poistaa lukemisen lukitus.

 1package cmap
 2
 3import (
 4    "sync"
 5)
 6
 7type ConcurrentMap[K comparable, V any] struct {
 8    sync.RWMutex
 9    data map[K]V
10}
11
12func (m *ConcurrentMap[K, V]) Get(key K) (V, bool) {
13    m.RLock()
14    defer m.RUnlock()
15
16    value, ok := m.data[key]
17    return value, ok
18}
19
20func (m *ConcurrentMap[K, V]) Set(key K, value V) {
21    m.Lock()
22    defer m.Unlock()
23
24    m.data[key] = value
25}

Yllä oleva koodi toteuttaa ConcurrentMap-rakenteen käyttäen sync.RWMutex-rakennetta. Koodissa Get-metodi asettaa lukemisen lukituksen ja Set-metodi kirjoittamisen lukituksen, mikä takaa turvallisen pääsyn ja muokkaamisen data-karttaan. Lukemisen lukitus on tarpeen, koska jos lukemista tapahtuu paljon, kirjoittamisen lukituksen sijasta voidaan käyttää lukemisen lukitusta, jolloin useat goroutiinit voivat lukea dataa samanaikaisesti. Tämä parantaa suorituskykyä, kun tilan muutosta ei tarvita ja kirjoittamisen lukitusta ei tarvita.

fakelock

fakelock on yksinkertainen temppu, joka toteuttaa sync.Locker-rajapinnan. Tämä rakenne tarjoaa samat metodit kuin sync.Mutex, mutta se ei tee mitään.

1package fakelock
2
3type FakeLock struct{}
4
5func (f *FakeLock) Lock() {}
6
7func (f *FakeLock) Unlock() {}

Yllä oleva koodi toteuttaa fakelock-paketin. Paketti toteuttaa sync.Locker-rajapinnan ja tarjoaa Lock ja Unlock -metodit, mutta ne eivät tee mitään. Selitämme, miksi tämä koodi on hyödyllinen, myöhemmin.

waitgroup

sync.WaitGroup

sync.WaitGroup on työkalu, joka odottaa, kunnes kaikkien goroutiinien tehtävät ovat valmiit. Se tarjoaa Add, Done ja Wait -metodit. Add lisää goroutiinien määrän, Done ilmoittaa, että goroutiinin tehtävä on valmis, ja Wait odottaa, kunnes kaikkien goroutiinien tehtävät ovat valmiit.

 1package main
 2
 3import (
 4    "sync"
 5    "sync/atomic"
 6)
 7
 8func main() {
 9    wg := sync.WaitGroup{}
10    c := atomic.Int64{}
11
12    for i := 0; i < 100 ; i++ {
13        wg.Add(1)
14        go func() {
15            defer wg.Done()
16            c.Add(1)
17        }()
18    }
19
20    wg.Wait()
21    println(c.Load())
22}

Yllä oleva koodi on esimerkki siitä, kuinka sync.WaitGroup-objektia käytetään 100:n goroutinen samanaikaiseen c-muuttujan arvon lisäämiseen. Koodi odottaa sync.WaitGroup:n avulla kaikkien goroutiinien päättymistä ennen kuin c-muuttujan lisätty arvo tulostetaan. Jos kyseessä on vain muutamien tehtävien fork & join -tyyppinen suoritus, kanavia voi riittää, mutta suuremmassa määrin fork & join -tapauksessa sync.WaitGroup on hyvä vaihtoehto.

with slice

Viipaleiden kanssa käytettynä waitgroup on erinomainen työkalu samanaikaisten tehtävien hallintaan ilman lukitusmekanismeja.

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6    "rand"
 7)
 8
 9func main() {
10	var wg sync.WaitGroup
11	arr := [10]int{}
12
13	for i := 0; i < 10; i++ {
14		wg.Add(1)
15		go func(id int) {
16			defer wg.Done()
17
18			arr[id] = rand.Intn(100)
19		}(i)
20	}
21
22	wg.Wait()
23	fmt.Println("Done")
24
25    for i, v := range arr {
26        fmt.Printf("arr[%d] = %d\n", i, v)
27    }
28}

Yllä oleva koodi käyttää ainoastaan waitgroup-objektia siten, että jokainen goroutiini luo samanaikaisesti 10 satunnaislukua ja tallentaa ne osoitettuun indeksiin. Koodi odottaa waitgroup:n avulla kaikkien goroutiinien päättymistä ennen kuin tulostaa "Done". Tällä tavalla waitgroup:a käyttämällä useat goroutiinit voivat suorittaa tehtäviä samanaikaisesti, tallentaa dataa lukituksetta ja suorittaa jälkikäsittelyn tehokkaasti tehtävien päätyttyä.

golang.org/x/sync/errgroup.ErrGroup

errgroup on sync.WaitGroup-paketin laajennus. Toisin kuin sync.WaitGroup, errgroup keskeyttää kaikki goroutiinit ja palauttaa virheen, jos jokin goroutiini tuottaa virheen.

 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "golang.org/x/sync/errgroup"
 7)
 8
 9func main() {
10    g, ctx := errgroup.WithContext(context.Background())
11    _ = ctx
12
13    for i := 0; i < 10; i++ {
14        i := i
15        g.Go(func() error {
16            if i == 5 {
17                return fmt.Errorf("error")
18            }
19            return nil
20        })
21    }
22
23    if err := g.Wait(); err != nil {
24        fmt.Println(err)
25    }
26}

Yllä oleva koodi luo errgroup:n avulla 10 goroutinia ja aiheuttaa virheen viidennessä goroutiinessa. Virheen aiheuttaminen viidennessä goroutiinessa on tarkoituksellista virhetilanteiden havainnollistamiseksi. Käytännön sovelluksissa errgroup:a käytetään goroutiinien luomiseen ja erilaisten jälkikäsittelyjen suorittamiseen virhetilanteissa.

once

Työkalu koodin suorittamiseen vain kerran. Seuraavien konstrukttoreiden avulla voidaan suorittaa siihen liittyvää koodia.

1func OnceFunc(f func()) func()
2func OnceValue[T any](f func() T) func() T
3func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)

OnceFunc

OnceFunc varmistaa, että funktio suoritetaan vain kerran koko ohjelman ajon aikana.

 1package main
 2
 3import "sync"
 4
 5func main() {
 6    once := sync.OnceFunc(func() {
 7        println("Hello, World!")
 8    })
 9
10    once()
11    once()
12    once()
13    once()
14    once()
15}

Yllä oleva koodi käyttää sync.OnceFunc-funktiota "Hello, World!" -tulostukseen. Vaikka once-funktiota kutsutaan useita kertoja, "Hello, World!" tulostuu vain kerran.

OnceValue

OnceValue ei ainoastaan suorita funktiota vain kerran, vaan se tallentaa myös funktion palauttaman arvon ja palauttaa tallennetun arvon seuraavilla kutsulla.

 1package main
 2
 3import "sync"
 4
 5func main() {
 6    c := 0
 7    once := sync.OnceValue(func() int {
 8        c += 1
 9        return c
10    })
11
12    println(once())
13    println(once())
14    println(once())
15    println(once())
16    println(once())
17}

Yllä oleva koodi käyttää sync.OnceValue-funktiota c-muuttujan arvon lisäämiseen yhdellä. Vaikka once-funktiota kutsutaan useita kertoja, c-muuttujan arvo kasvaa vain kerran ja funktio palauttaa aina arvon 1.

OnceValues

OnceValues toimii samalla tavalla kuin OnceValue, mutta se voi palauttaa useita arvoja.

 1package main
 2
 3import "sync"
 4
 5func main() {
 6    c := 0
 7    once := sync.OnceValues(func() (int, int) {
 8        c += 1
 9        return c, c
10    })
11
12    a, b := once()
13    println(a, b)
14    a, b = once()
15    println(a, b)
16    a, b = once()
17    println(a, b)
18    a, b = once()
19    println(a, b)
20    a, b = once()
21    println(a, b)
22}

Yllä oleva koodi käyttää sync.OnceValues-funktiota c-muuttujan arvon lisäämiseen yhdellä. Vaikka once-funktiota kutsutaan useita kertoja, c-muuttujan arvo kasvaa vain kerran ja funktio palauttaa aina arvon 1.

atomic

atomic-paketti tarjoaa atomisia operaatioita. atomic-paketti tarjoaa menetelmiä kuten Add, CompareAndSwap, Load, Store ja Swap, mutta nykyään suositellaan Int64, Uint64 ja Pointer-tyyppien käyttöä.

 1package main
 2
 3import (
 4    "sync"
 5    "sync/atomic"
 6)
 7
 8func main() {
 9    wg := sync.WaitGroup{}
10    c := atomic.Int64{}
11
12    for i := 0; i < 100 ; i++ {
13        wg.Add(1)
14        go func() {
15            defer wg.Done()
16            c.Add(1)
17        }()
18    }
19
20    wg.Wait()
21    println(c.Load())
22}

Tämä on aiempi esimerkki, jossa atomic.Int64-tyyppiä käytetään c-muuttujan atomisiin lisäyksiin. Add- ja Load-menetelmien avulla muuttujaa voidaan lisätä ja lukea atomisesti. Lisäksi Store-menetelmällä voidaan tallentaa arvoja, Swap-menetelmällä vaihtaa arvoja ja CompareAndSwap-menetelmällä vertailla ja vaihtaa arvoja tarvittaessa.

cond

sync.Cond

cond-paketti tarjoaa ehtojen muuttujia. cond-paketti luodaan sync.Cond:lla ja se tarjoaa Wait, Signal ja Broadcast -menetelmiä.

 1package main
 2
 3import (
 4    "sync"
 5)
 6
 7func main() {
 8    c := sync.NewCond(&sync.Mutex{})
 9    ready := false
10
11    go func() {
12        c.L.Lock()
13        ready = true
14        c.Signal()
15        c.L.Unlock()
16    }()
17
18    c.L.Lock()
19    for !ready {
20        c.Wait()
21    }
22    c.L.Unlock()
23
24    println("Ready!")
25}

Yllä oleva koodi odottaa sync.Cond:n avulla, että ready-muuttujan arvoksi tulee true. Koodi tulostaa "Ready!" kun ready-muuttujan arvo on true. sync.Cond:n avulla useat goroutiinit voivat odottaa tietyn ehdon täyttymistä.

Tämä voidaan hyödyntää yksinkertaisen queue:n toteutuksessa.

 1package queue
 2
 3import (
 4    "sync"
 5    "sync/atomic"
 6)
 7
 8type Node[T any] struct {
 9    Value T
10    Next  *Node[T]
11}
12
13type Queue[T any] struct {
14    sync.Mutex
15    Cond *sync.Cond
16    Head *Node[T]
17    Tail *Node[T]
18    Len  int
19}
20
21func New[T any]() *Queue[T] {
22    q := &Queue[T]{}
23    q.Cond = sync.NewCond(&q.Mutex)
24    return q
25}
26
27func (q *Queue[T]) Push(value T) {
28    q.Lock()
29    defer q.Unlock()
30
31    node := &Node[T]{Value: value}
32    if q.Len == 0 {
33        q.Head = node
34        q.Tail = node
35    } else {
36        q.Tail.Next = node
37        q.Tail = node
38    }
39    q.Len++
40    q.Cond.Signal()
41}
42
43func (q *Queue[T]) Pop() T {
44    q.Lock()
45    defer q.Unlock()
46
47    for q.Len == 0 {
48        q.Cond.Wait()
49    }
50
51    node := q.Head
52    q.Head = q.Head.Next
53    q.Len--
54    return node.Value
55}

sync.Cond:n avulla voidaan odottaa tehokkaasti ehdon täyttymistä, mikä on tehokkaampaa kuin spin-lock, joka kuluttaa paljon prosessorin resursseja.

semaphore

golang.org/x/sync/semaphore.Semaphore

semaphore-paketti tarjoaa semaforeja. semaphore-paketti luodaan golang.org/x/sync/semaphore.Semaphore:lla ja se tarjoaa Acquire, Release ja TryAcquire -menetelmät.

 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "golang.org/x/sync/semaphore"
 7)
 8
 9func main() {
10    s := semaphore.NewWeighted(1)
11
12    if s.TryAcquire(1) {
13        fmt.Println("Acquired!")
14    } else {
15        fmt.Println("Not Acquired!")
16    }
17
18    s.Release(1)
19}

Yllä oleva koodi luo semaforin ja hankkii sen Acquire-menetelmän avulla ja vapauttaa sen Release-menetelmän avulla. Koodi näyttää, kuinka semaforia hankitaan ja vapautetaan.

Lopuksi

Tämä kattaa perusteet. Toivottavasti tämä artikkeli auttaa teitä ymmärtämään ja käyttämään goroutiineja samanaikaisuuden hallinnassa. Toivottavasti tämä artikkeli oli hyödyllinen! Kiitos.