---
id: 01933e13f963942e4c9bc8c3c89fbc2b
author: snowmerak
title: Go-startpaket för samtidighet
description: Vi presenterar en mängd verktyg och tekniker för hantering av samtidighet i Go, inklusive goroutiner, kanaler och mutexar.
language: sv
date: 2024-10-18T05:51:30.010680581Z
path: /blog/posts/go-concurrency-starter-pack-z221a3399
category: concurrency
---

## Översikt

### Kort introduktion

Go-språket har många verktyg för hantering av samtidighet. I den här artikeln vill vi presentera några av dem samt några knep.

### Goroutiner?

En goroutine är en ny form av samtidighetsmodell som stöds av Go-språket. Normalt får ett program OS-trådar från operativsystemet för att utföra flera uppgifter samtidigt, och utför arbete parallellt så många gånger som antalet kärnor. För att utföra samtidighet i mindre skala skapar man gröna trådar i användarutrymmet, så att flera gröna trådar kör arbete i omgångar inom en enda OS-tråd. Goroutiner har dock gjorts ännu mindre och mer effektiva än denna form av gröna trådar. Goroutiner använder mindre minne än trådar och kan skapas och bytas ut snabbare än trådar.

För att använda en goroutine behöver du bara använda nyckelordet `go`. Detta gör det intuitivt möjligt att under programmeringsprocessen köra synkron kod som asynkron kod.

```go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan struct{})
    go func() {
        defer close(ch)
        time.Sleep(1 * time.Second)
        fmt.Println("Hello, World!")
    }()

    fmt.Println("Waiting for goroutine...")
    for range ch {}
}
```

Denna kod ändrar en synkron kod, som vilar en sekund och sedan skriver ut `Hello, World!`, till ett asynkront flöde. Även om exemplet nu är enkelt, blir läsbarheten, synligheten och förståelsen av koden bättre än med tidigare metoder som async await eller promise, när man omvandlar något mer komplicerad kod från synkron till asynkron kod.

I många fall kan dock dåliga goroutinkoder uppstå när man inte förstår flödet att helt enkelt anropa synkron kod asynkront, eller flöden som `fork & join` (ett flöde som liknar divide and conquer). För att förbereda sig för sådana fall kommer den här artikeln att presentera några metoder och tekniker.

## Hantering av samtidighet

### context

Att `context` förekommer som första hanteringsteknik kan vara överraskande. Men i Go spelar `context` en utmärkt roll för att hantera hela arbetsträdet, inte bara för enkel avbrytning. För dem som inte känner till paketet ska vi kort förklara det.

```go
package main

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go func() {
        <-ctx.Done()
        fmt.Println("Context is done!")
    }()

    time.Sleep(1 * time.Second)

    cancel()

    time.Sleep(1 * time.Second)
}
```

Ovanstående kod använder `context` och skriver ut `Context is done!` efter en sekund. `context` kan kontrollera om avbrytning har skett via metoden `Done()`, och erbjuder olika sätt att avbryta genom metoder som `WithCancel`, `WithTimeout`, `WithDeadline` och `WithValue`.

Låt oss skapa ett enkelt exempel. Antag att du vill skriva kod som hämtar `user`, `post` och `comment` med ett `aggregator`-mönster för att hämta data, och att alla förfrågningar måste slutföras inom två sekunder. Då kan du skriva följande.

```go
package main

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
    defer cancel()

    ch := make(chan struct{})
    go func() {
        defer close(ch)
        user := getUser(ctx)
        post := getPost(ctx)
        comment := getComment(ctx)

        fmt.Println(user, post, comment)
    }()

    select {
    case <-ctx.Done():
        fmt.Println("Timeout!")
    case <-ch:
        fmt.Println("All data is fetched!")
    }
}
```

Ovanstående kod skriver ut `Timeout!` om alla data inte hämtas inom två sekunder, och skriver ut `All data is fetched!` om alla data hämtas. På detta sätt kan man med `context` enkelt hantera avbrytning och timeout även i kod där flera goroutiner körs.

Olika funktioner och metoder relaterade till context finns i [godoc context](https://pkg.go.dev/context). Vi hoppas att du kan lära dig de enkla delarna och använda dem bekvämt.

### channel

#### unbuffered channel

`channel` är ett verktyg för kommunikation mellan goroutiner. En `channel` skapas med `make(chan T)`. Här är `T` den typ av data som kanalen ska överföra. Data skickas och tas emot med `<-`, och en `channel` stängs med `close`.

```go
package main

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

Ovanstående kod använder `channel` för att skriva ut 1 och 2. Denna kod visar endast enkel sändning och mottagning av värden till en kanal. Men `channel` erbjuder fler funktioner än så. Låt oss först titta på `buffered channel` och `unbuffered channel`. Innan vi börjar bör vi notera att exemplet ovan använder en `unbuffered channel`, där sändningen av data till kanalen och mottagningen av data måste ske samtidigt. Om dessa åtgärder inte sker samtidigt kan en deadlock uppstå.

#### buffered channel

Vad händer om ovanstående kod, i stället för en enkel utskrift, är två processer som utför tunga uppgifter? Om den andra processen tar emot och bearbetar data men fastnar under lång tid, kommer även den första processen att stoppas under den tiden. För att förhindra en sådan situation kan vi använda en `buffered channel`.

```go
package main

func main() {
    ch := make(chan int, 2)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

Ovanstående kod använder `buffered channel` för att skriva ut 1 och 2. I denna kod används en `buffered channel` så att sändningen och mottagningen av data till kanalen inte behöver ske samtidigt. När kanalen har en buffert finns det utrymme motsvarande dess längd, vilket förhindrar fördröjningar i arbetet orsakade av påverkan från efterföljande uppgifter.

#### select

När man hanterar flera kanaler kan man enkelt implementera en `fan-in`-struktur med hjälp av `select`-syntaxen.

```go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int, 10)
    ch2 := make(chan int, 10)
    ch3 := make(chan int, 10)

    go func() {
        for {
            ch1 <- 1
            time.Sleep(1 * time.Second)
        }
    }()
    go func() {
        for {
            ch2 <- 2
            time.Sleep(2 * time.Second)
        }
    }()
    go func() {
        for {
            ch3 <- 3
            time.Sleep(3 * time.Second)
        }
    }()

    for i := 0; i < 3; i++ {
        select {
        case v := <-ch1:
            fmt.Println(v)
        case v := <-ch2:
            fmt.Println(v)
        case v := <-ch3:
            fmt.Println(v)
        }
    }
}
```

Ovanstående kod skapar tre kanaler som periodiskt skickar 1, 2 och 3, och använder `select` för att ta emot värden från kanalerna och skriva ut dem. På detta sätt kan man med `select` ta emot data från flera kanaler samtidigt och bearbeta värdena i den ordning de tas emot.

#### for range

En `channel` kan enkelt ta emot data med `for range`. När `for range` används på en kanal körs loopen varje gång data läggs till i kanalen, och loopen avslutas när kanalen stängs.

```go
package main

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
```

Ovanstående kod använder `channel` för att skriva ut 1 och 2. I denna kod används `for range` för att ta emot och skriva ut data varje gång data läggs till i kanalen. När kanalen stängs avslutas loopen.

Som vi skrivit några gånger ovan kan denna syntax också användas som ett enkelt synkroniseringsverktyg.

```go
package main

func main() {
    ch := make(chan struct{})
    go func() {
        defer close(ch)
        time.Sleep(1 * time.Second)
        fmt.Println("Hello, World!")
    }()

    fmt.Println("Waiting for goroutine...")
    for range ch {}
}
```

Ovanstående kod vilar en sekund och skriver sedan ut `Hello, World!`. I denna kod används `channel` för att omvandla synkron kod till asynkron kod. På detta sätt kan man med `channel` enkelt omvandla synkron kod till asynkron kod och även definiera en `join`-punkt.

#### etc

1. Om man skickar eller tar emot data på en nil channel kan programmet fastna i en oändlig loop, vilket leder till en deadlock.
2. Om man skickar data efter att en kanal har stängts uppstår en panic.
3. Även om man inte stänger en kanal explicit, kommer GC att samla in den och stänga kanalen.

### mutex

#### spinlock

`spinlock` är en synkroniseringsmetod som upprepade gånger försöker ta låset i en loop. I Go kan en spinlock enkelt implementeras med hjälp av pekare.

```go
package spinlock

import (
    "runtime"
    "sync/atomic"
)

type SpinLock struct {
    lock uintptr
}

func (s *SpinLock) Lock() {
    for !atomic.CompareAndSwapUintptr(&s.lock, 0, 1) {
        runtime.Gosched()
    }
}

func (s *SpinLock) Unlock() {
    atomic.StoreUintptr(&s.lock, 0)
}

func NewSpinLock() *SpinLock {
    return &SpinLock{}
}
```

Ovanstående kod implementerar paketet `spinlock`. Här implementeras `SpinLock` med hjälp av paketet `sync/atomic`. I metoden `Lock` används `atomic.CompareAndSwapUintptr` för att försöka ta låset, och i metoden `Unlock` används `atomic.StoreUintptr` för att frigöra låset. Eftersom denna metod försöker ta låset utan paus, använder den CPU kontinuerligt tills låset erhålls, vilket kan leda till en oändlig loop. Därför är `spinlock` lämplig att använda för enkel synkronisering eller endast under kort tid.

#### sync.Mutex

`mutex` är ett verktyg för synkronisering mellan goroutiner. `mutex` som tillhandahålls av paketet `sync` erbjuder metoderna `Lock`, `Unlock`, `RLock`, `RUnlock` med flera. En `mutex` skapas med `sync.Mutex`, och `sync.RWMutex` kan användas för läs- och skrivlås.

```go
package main

import (
    "sync"
)

func main() {
    var mu sync.Mutex
    var count int

    go func() {
        mu.Lock()
        count++
        mu.Unlock()
    }()

    mu.Lock()
    count++
    mu.Unlock()

    println(count)
}
```

I ovanstående kod får två goroutiner nästan samtidigt åtkomst till samma variabel `count`. Om man använder `mutex` för att göra koden som kommer åt variabeln `count` till ett kritiskt avsnitt, kan samtidig åtkomst till `count` förhindras. Då skriver denna kod ut `2` varje gång den körs.

#### sync.RWMutex

`sync.RWMutex` är en `mutex` som kan användas genom att skilja mellan läslås och skrivlås. Med metoderna `RLock` och `RUnlock` kan ett läslås sättas och släppas.

```go
package cmap

import (
    "sync"
)

type ConcurrentMap[K comparable, V any] struct {
    sync.RWMutex
    data map[K]V
}

func (m *ConcurrentMap[K, V]) Get(key K) (V, bool) {
    m.RLock()
    defer m.RUnlock()

    value, ok := m.data[key]
    return value, ok
}

func (m *ConcurrentMap[K, V]) Set(key K, value V) {
    m.Lock()
    defer m.Unlock()

    m.data[key] = value
}
```

Ovanstående kod implementerar `ConcurrentMap` med hjälp av `sync.RWMutex`. I denna kod sätts ett läslås i metoden `Get` och ett skrivlås i metoden `Set`, vilket gör det möjligt att säkert komma åt och ändra mappen `data`. Anledningen till att läslås behövs är att när det finns många enkla läsoperationer, kan flera goroutiner utföra läsoperationer samtidigt genom att endast sätta ett läslås, utan att sätta ett skrivlås. På så sätt kan prestandan förbättras i de fall där det inte sker någon förändring av tillståndet och därmed inte är nödvändigt att sätta ett skrivlås.

#### fakelock

`fakelock` är ett enkelt knep som implementerar `sync.Locker`. Denna struktur tillhandahåller samma metoder som `sync.Mutex`, men utför faktiskt ingen funktion.

```go
package fakelock

type FakeLock struct{}

func (f *FakeLock) Lock() {}

func (f *FakeLock) Unlock() {}
```

Ovanstående kod implementerar paketet `fakelock`. Paketet implementerar `sync.Locker` och tillhandahåller metoderna `Lock` och `Unlock`, men gör i praktiken ingenting. Varför en sådan kod behövs kommer jag att beskriva vid tillfälle.

### waitgroup

#### sync.WaitGroup

`sync.WaitGroup` är ett verktyg som väntar tills alla goroutinernas uppgifter är klara. Det tillhandahåller metoderna `Add`, `Done` och `Wait`. Med metoden `Add` lägger man till antalet goroutiner, med metoden `Done` meddelar man att en goroutins uppgift är klar, och med metoden `Wait` väntar man tills alla goroutiners uppgifter är klara.

```go
package main

import (
    "sync"
    "sync/atomic"
)

func main() {
    wg := sync.WaitGroup{}
    c := atomic.Int64{}

    for i := 0; i < 100 ; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Add(1)
        }()
    }

    wg.Wait()
    println(c.Load())
}
```

Ovanstående kod använder `sync.WaitGroup` så att 100 goroutiner samtidigt adderar ett värde till variabeln `c`. I denna kod väntar `sync.WaitGroup` tills alla goroutiner är klara, och skriver sedan ut det värde som adderats till `c`. När man bara `fork & join`:ar ett fåtal uppgifter räcker det med kanaler, men när man `fork & join`:ar ett stort antal uppgifter är `sync.WaitGroup` ett bra alternativ.

#### with slice

När den används tillsammans med slice kan `waitgroup` bli ett utmärkt verktyg för att hantera samtidig exekvering utan lås.

```go
package main

import (
	"fmt"
	"sync"
    "rand"
)

func main() {
	var wg sync.WaitGroup
	arr := [10]int{}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()

			arr[id] = rand.Intn(100)
		}(i)
	}

	wg.Wait()
	fmt.Println("Done")

    for i, v := range arr {
        fmt.Printf("arr[%d] = %d\n", i, v)
    }
}
```

Ovanstående kod använder endast `waitgroup` så att varje goroutin samtidigt genererar 10 slumpmässiga heltal och lagrar dem på det tilldelade indexet. I denna kod väntar `waitgroup` tills alla goroutiner är klara, och skriver sedan ut `Done`. På detta sätt kan man med `waitgroup` låta flera goroutiner utföra arbete samtidigt, lagra data utan lås tills alla goroutiner är klara, och sedan utföra efterbearbetning i ett svep efter att arbetet avslutats.

#### golang.org/x/sync/errgroup.ErrGroup

`errgroup` är ett paket som utökar `sync.WaitGroup`. Till skillnad från `sync.WaitGroup` avbryter `errgroup` alla goroutiner och returnerar felet om ett fel uppstår i någon av goroutinernas uppgifter.

```go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
)

func main() {
    g, ctx := errgroup.WithContext(context.Background())
    _ = ctx

    for i := 0; i < 10; i++ {
        i := i
        g.Go(func() error {
            if i == 5 {
                return fmt.Errorf("error")
            }
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        fmt.Println(err)
    }
}
```

Ovanstående kod använder `errgroup` för att skapa tio goroutiner, och genererar ett fel i den femte goroutinen. Eftersom felet avsiktligt genereras i den femte goroutinen visas ett exempel på en felsituation. I praktiken används `errgroup` genom att skapa goroutiner med det, och sedan tillämpa olika efterbearbetningar när fel uppstår i respektive goroutin.

### once

Det är ett verktyg för att köra kod som endast ska köras en gång. Relaterad kod kan köras via konstruktorerna nedan.

```go
func OnceFunc(f func()) func()
func OnceValue[T any](f func() T) func() T
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)
```

#### OnceFunc

`OnceFunc` garanterar helt enkelt att den givna funktionen endast kan köras exakt en gång under hela programmets livstid.

```go
package main

import "sync"

func main() {
    once := sync.OnceFunc(func() {
        println("Hello, World!")
    })

    once()
    once()
    once()
    once()
    once()
}
```

Ovanstående kod använder `sync.OnceFunc` för att skriva ut `Hello, World!`. I denna kod används `sync.OnceFunc` för att skapa funktionen `once`, och även om `once` anropas flera gånger skrivs `Hello, World!` endast ut en gång.

#### OnceValue

`OnceValue` körs inte bara en gång under hela programmets livstid; den sparar dessutom funktionens returvärde och returnerar det sparade värdet när den anropas igen.

```go
package main

import "sync"

func main() {
    c := 0
    once := sync.OnceValue(func() int {
        c += 1
        return c
    })

    println(once())
    println(once())
    println(once())
    println(once())
    println(once())
}
```

Ovanstående kod använder `sync.OnceValue` för att öka variabeln `c` med 1. I denna kod används `sync.OnceValue` för att skapa funktionen `once`, och även om `once` anropas flera gånger returnerar den 1, eftersom variabeln `c` endast ökades en gång.

#### OnceValues

`OnceValues` fungerar på samma sätt som `OnceValue`, men kan returnera flera värden.

```go
package main

import "sync"

func main() {
    c := 0
    once := sync.OnceValues(func() (int, int) {
        c += 1
        return c, c
    })

    a, b := once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
    a, b = once()
    println(a, b)
}
```

Ovanstående kod använder `sync.OnceValues` för att öka variabeln `c` med 1. I denna kod används `sync.OnceValues` för att skapa funktionen `once`, och även om `once` anropas flera gånger returnerar den 1, eftersom variabeln `c` endast ökades en gång.

### atomic

Paketet `atomic` är ett paket som tillhandahåller atomära operationer. Paketet `atomic` erbjuder metoder som `Add`, `CompareAndSwap`, `Load`, `Store` och `Swap`, men på senare tid rekommenderas användning av typer som `Int64`, `Uint64` och `Pointer`.

```go
package main

import (
    "sync"
    "sync/atomic"
)

func main() {
    wg := sync.WaitGroup{}
    c := atomic.Int64{}

    for i := 0; i < 100 ; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Add(1)
        }()
    }

    wg.Wait()
    println(c.Load())
}
```

Detta är exemplet som användes tidigare. Det använder typen `atomic.Int64` för att öka variabeln `c` atomärt. Med metoderna `Add` och `Load` kan variabeln ökas atomärt och läsas. Dessutom kan man med metoden `Store` lagra ett värde, med metoden `Swap` byta ut ett värde, och med metoden `CompareAndSwap` jämföra ett värde och byta ut det om det är lämpligt.

### cond

#### sync.Cond

Paketet `cond` är ett paket som tillhandahåller villkorsvariabler. Paketet `cond` kan skapas med `sync.Cond` och erbjuder metoderna `Wait`, `Signal` och `Broadcast`.

```go
package main

import (
    "sync"
)

func main() {
    c := sync.NewCond(&sync.Mutex{})
    ready := false

    go func() {
        c.L.Lock()
        ready = true
        c.Signal()
        c.L.Unlock()
    }()

    c.L.Lock()
    for !ready {
        c.Wait()
    }
    c.L.Unlock()

    println("Ready!")
}
```

Ovanstående kod använder `sync.Cond` för att vänta tills variabeln `ready` blir `true`. I denna kod används `sync.Cond` för att vänta tills variabeln `ready` blir `true`, och därefter skrivs `Ready!` ut. På detta sätt kan man med `sync.Cond` låta flera goroutiner vänta tills ett specifikt villkor är uppfyllt.

Med detta kan man implementera en enkel `queue`.

```go
package queue

import (
    "sync"
    "sync/atomic"
)

type Node[T any] struct {
    Value T
    Next  *Node[T]
}

type Queue[T any] struct {
    sync.Mutex
    Cond *sync.Cond
    Head *Node[T]
    Tail *Node[T]
    Len  int
}

func New[T any]() *Queue[T] {
    q := &Queue[T]{}
    q.Cond = sync.NewCond(&q.Mutex)
    return q
}

func (q *Queue[T]) Push(value T) {
    q.Lock()
    defer q.Unlock()

    node := &Node[T]{Value: value}
    if q.Len == 0 {
        q.Head = node
        q.Tail = node
    } else {
        q.Tail.Next = node
        q.Tail = node
    }
    q.Len++
    q.Cond.Signal()
}

func (q *Queue[T]) Pop() T {
    q.Lock()
    defer q.Unlock()

    for q.Len == 0 {
        q.Cond.Wait()
    }

    node := q.Head
    q.Head = q.Head.Next
    q.Len--
    return node.Value
}
```

När man använder `sync.Cond` på detta sätt kan man vänta effektivt och återuppta arbetet när villkoret är uppfyllt, i stället för att använda mycket CPU med `spin-lock`.

### semaphore

#### golang.org/x/sync/semaphore.Semaphore

Paketet `semaphore` är ett paket som tillhandahåller semaforer. Paketet `semaphore` kan skapas med `golang.org/x/sync/semaphore.Semaphore` och erbjuder metoderna `Acquire`, `Release` och `TryAcquire`.

```go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/semaphore"
)

func main() {
    s := semaphore.NewWeighted(1)

    if s.TryAcquire(1) {
        fmt.Println("Acquired!")
    } else {
        fmt.Println("Not Acquired!")
    }

    s.Release(1)
}
```

Ovanstående kod skapar en semafor med `semaphore`, och använder metoden `Acquire` för att ta semaforen och metoden `Release` för att frigöra den. I denna kod visas hur man tar och frigör en semafor med `semaphore`.

## Avslutning

Det grundläggande innehållet tror vi räcker här. Utifrån innehållet i den här artikeln hoppas vi att du förstår hur man hanterar samtidighet med goroutiner och kan använda det i praktiken. Vi hoppas att den här artikeln har varit till nytta för dig. Tack.
