GoSuda

Go Konkurensi Paket Pemula

By snowmerak
views ...

Ikhtisar

Pengantar Singkat

Bahasa Go memiliki banyak perangkat untuk manajemen konkurensi. Dalam artikel ini, kami akan memperkenalkan sebagian dari perangkat tersebut beserta trik-trik penggunaannya.

Goroutine?

goroutine adalah model konkurensi jenis baru yang didukung oleh bahasa Go. Secara umum, program menerima OS thread dari OS untuk melakukan banyak tugas secara simultan, sehingga tugas-tugas tersebut dieksekusi secara paralel sesuai dengan jumlah core. Dan untuk mencapai konkurensi dalam unit yang lebih kecil, green thread dibuat di userland, memungkinkan beberapa green thread berjalan di dalam satu OS thread untuk mengeksekusi tugas. Namun, dalam kasus goroutine, bentuk green thread ini telah dibuat menjadi lebih kecil dan lebih efisien. Goroutine semacam ini menggunakan memori yang lebih sedikit dibandingkan thread, serta dapat dibuat dan diganti konteksnya lebih cepat daripada thread.

Untuk menggunakan goroutine, cukup dengan menggunakan keyword go. Hal ini memungkinkan kode sinkron dieksekusi sebagai kode asinkron secara intuitif selama proses penulisan program.

 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}

Kode ini mengubah kode sinkron yang sederhana, yaitu menunggu 1 detik kemudian mencetak Hello, World!, menjadi alur asinkron. Meskipun contoh saat ini sederhana, ketika kode yang sedikit lebih kompleks diubah dari kode sinkron menjadi kode asinkron, keterbacaan, visibilitas, dan pemahaman kode menjadi lebih baik dibandingkan dengan metode seperti async await atau promise yang sudah ada.

Namun, dalam banyak kasus, jika seseorang tidak memahami alur pemanggilan sederhana kode sinkron secara asinkron dan alur seperti fork & join (alur yang mirip dengan divide and conquer), kode goroutine yang kurang optimal dapat tercipta. Beberapa metode dan teknik untuk mengantisipasi kasus semacam itu akan kami perkenalkan dalam artikel ini.

Manajemen Konkurensi

context

Mungkin tampak mengejutkan bahwa context muncul sebagai teknik manajemen pertama. Namun, dalam bahasa Go, context melampaui fungsi pembatalan sederhana dan memainkan peran yang luar biasa dalam mengelola keseluruhan pohon tugas (task tree). Bagi yang belum mengetahuinya, kami akan menjelaskan secara singkat paket tersebut.

 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}

Kode di atas menggunakan context untuk mencetak Context is done! setelah 1 detik. context dapat digunakan untuk memeriksa status pembatalan melalui metode Done(), dan menyediakan berbagai metode pembatalan seperti WithCancel, WithTimeout, WithDeadline, dan WithValue.

Mari kita buat contoh sederhana. Asumsikan Anda sedang menulis kode untuk mengambil user, post, dan comment menggunakan pola aggregator guna memperoleh data tertentu. Dan jika semua permintaan harus selesai dalam waktu 2 detik, Anda dapat menuliskannya seperti berikut.

 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}

Kode di atas akan mencetak Timeout! jika semua data tidak berhasil diambil dalam waktu 2 detik, dan akan mencetak All data is fetched! jika semua data berhasil diambil. Dengan menggunakan context dengan cara ini, pembatalan (cancellation) dan timeout dapat dikelola dengan mudah bahkan dalam kode yang melibatkan banyak goroutine.

Berbagai fungsi dan metode terkait context dapat diperiksa di godoc context. Kami berharap Anda dapat mempelajari hal-hal sederhana tersebut sehingga dapat memanfaatkannya dengan nyaman.

channel

unbuffered channel

channel adalah alat untuk komunikasi antar goroutine. channel dapat dibuat dengan make(chan T). Di sini, T adalah tipe data yang akan dikirimkan oleh channel tersebut. channel dapat digunakan untuk mengirim dan menerima data menggunakan <-, dan dapat ditutup menggunakan close.

 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}

Kode di atas adalah kode yang mencetak 1 dan 2 menggunakan channel. Kode ini hanya menunjukkan pengiriman dan penerimaan nilai ke/dari channel secara sederhana. Namun, channel menyediakan lebih banyak fungsionalitas daripada itu. Pertama, mari kita pelajari mengenai buffered channel dan unbuffered channel. Sebelum memulai, contoh yang ditulis di atas adalah unbuffered channel, di mana tindakan pengiriman data ke channel dan tindakan penerimaan data dari channel harus terjadi secara simultan. Jika tindakan-tindakan tersebut tidak terjadi secara simultan, deadlock dapat terjadi.

buffered channel

Bagaimana jika kode di atas bukan sekadar output sederhana, melainkan dua proses yang melaksanakan tugas berat? Jika proses kedua memerlukan waktu lama saat membaca dan melakukan pemrosesan, proses pertama juga akan terhenti selama waktu tersebut. Kami dapat menggunakan buffered channel untuk mencegah situasi semacam ini.

 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}

Kode di atas adalah kode yang mencetak 1 dan 2 menggunakan buffered channel. Dalam kode ini, buffered channel digunakan untuk memungkinkan tindakan pengiriman data ke channel dan tindakan penerimaan data tidak perlu terjadi secara simultan. Dengan menyediakan buffer pada channel seperti ini, tercipta ruang kosong sebesar kapasitas buffer tersebut, sehingga penundaan pekerjaan yang disebabkan oleh dampak pekerjaan tahap selanjutnya dapat dicegah.

select

Saat menangani beberapa channel, struktur fan-in dapat diimplementasikan dengan mudah menggunakan sintaks select.

 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}

Kode di atas membuat tiga channel yang secara periodik mengirimkan 1, 2, dan 3, dan menggunakan select untuk menerima dan mencetak nilai dari channel. Dengan menggunakan select dengan cara ini, dimungkinkan untuk menerima data secara bersamaan dari beberapa channel, dan memproses nilai segera setelah nilai tersebut diterima dari channel.

for range

channel dapat menerima data dengan mudah menggunakan for range. Ketika for range digunakan pada channel, ia akan dieksekusi setiap kali data ditambahkan ke channel tersebut, dan loop akan berakhir ketika channel ditutup.

 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}

Kode di atas adalah kode yang mencetak 1 dan 2 menggunakan channel. Dalam kode ini, for range digunakan untuk menerima dan mencetak data setiap kali data ditambahkan ke channel. Dan loop akan diakhiri ketika channel ditutup.

Seperti yang telah ditulis beberapa kali di atas, sintaks ini juga dapat digunakan sebagai sarana sinkronisasi sederhana.

 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}

Kode di atas adalah kode yang mencetak Hello, World! setelah menunggu selama 1 detik. Dalam kode ini, channel digunakan untuk mengubah kode sinkron menjadi kode asinkron. Dengan menggunakan channel dengan cara ini, kode sinkron dapat diubah dengan mudah menjadi kode asinkron, dan titik join dapat ditetapkan.

etc

  1. Mengirim atau menerima data ke channel nil dapat menyebabkan masuk ke dalam loop tak terbatas dan memicu deadlock.
  2. Mengirim data setelah channel ditutup akan menyebabkan panic.
  3. Meskipun channel tidak secara eksplisit ditutup, Garbage Collector (GC) akan menutupnya saat melakukan pemungutan.

mutex

spinlock

spinlock adalah metode sinkronisasi yang mencoba memperoleh lock secara terus-menerus melalui perulangan (loop). Dalam bahasa Go, spinlock dapat diimplementasikan dengan mudah menggunakan pointer.

 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}

Kode di atas adalah implementasi dari paket spinlock. Dalam kode ini, SpinLock diimplementasikan menggunakan paket sync/atomic. Metode Lock menggunakan atomic.CompareAndSwapUintptr untuk mencoba memperoleh lock, dan metode Unlock menggunakan atomic.StoreUintptr untuk melepaskan lock. Metode ini mencoba lock tanpa henti, sehingga terus menggunakan CPU hingga lock diperoleh, yang berpotensi menyebabkan infinite loop. Oleh karena itu, spinlock sebaiknya digunakan hanya untuk sinkronisasi sederhana atau dalam jangka waktu yang sangat singkat.

sync.Mutex

mutex adalah alat untuk sinkronisasi antar goroutine. mutex yang disediakan oleh paket sync menawarkan metode seperti Lock, Unlock, RLock, dan RUnlock. mutex dapat dibuat dengan sync.Mutex, dan lock baca/tulis dapat digunakan dengan sync.RWMutex.

 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}

Dalam kode di atas, hampir bersamaan dua goroutine mengakses variabel count yang sama. Pada saat itu, jika kode yang mengakses variabel count dijadikan sebagai critical section menggunakan mutex, akses konkuren terhadap variabel count dapat dicegah. Maka, kode ini akan selalu mencetak 2 tidak peduli berapa kali dieksekusi.

sync.RWMutex

sync.RWMutex adalah mutex yang memungkinkan pemisahan antara lock baca (read lock) dan lock tulis (write lock). Lock baca dapat dipasang dan dilepaskan menggunakan metode RLock dan RUnlock.

 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}

Kode di atas adalah implementasi ConcurrentMap menggunakan sync.RWMutex. Dalam kode ini, lock baca dipasang pada metode Get, dan lock tulis dipasang pada metode Set sehingga peta data dapat diakses dan dimodifikasi dengan aman. Alasan diperlukan lock baca adalah untuk memungkinkan beberapa goroutine melakukan operasi baca secara simultan tanpa harus memasang lock tulis ketika operasi tersebut hanya berupa pembacaan sederhana. Melalui hal ini, kinerja dapat ditingkatkan dengan hanya memasang lock baca ketika tidak ada perubahan status yang memerlukan lock tulis.

fakelock

fakelock adalah trik sederhana untuk mengimplementasikan sync.Locker. Struktur ini menyediakan metode yang sama dengan sync.Mutex, namun tidak melakukan operasi nyata.

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

Kode di atas adalah implementasi dari paket fakelock. Paket ini mengimplementasikan sync.Locker dan menyediakan metode Lock serta Unlock, tetapi pada kenyataannya tidak melakukan tindakan apa pun. Alasan mengapa kode semacam ini diperlukan akan kami jelaskan jika ada kesempatan.

waitgroup

sync.WaitGroup

sync.WaitGroup adalah alat untuk menunggu hingga semua pekerjaan goroutine selesai. Ia menyediakan metode Add, Done, dan Wait; metode Add digunakan untuk menambahkan jumlah goroutine, dan metode Done menginformasikan bahwa pekerjaan goroutine telah selesai. Kemudian, metode Wait digunakan untuk menunggu hingga pekerjaan semua goroutine selesai.

 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}

Kode di atas menggunakan sync.WaitGroup untuk 100 goroutine yang secara bersamaan menambahkan nilai ke variabel c. Dalam kode ini, sync.WaitGroup digunakan untuk menunggu hingga semua goroutine selesai, kemudian mencetak nilai total yang ditambahkan ke variabel c. Meskipun menggunakan channel saja sudah cukup untuk kasus fork & join sederhana dari beberapa tugas, menggunakan sync.WaitGroup juga merupakan pilihan yang baik untuk kasus fork & join dalam jumlah besar.

with slice

Jika digunakan bersamaan dengan slice, waitgroup dapat menjadi alat yang sangat baik untuk mengelola pekerjaan eksekusi konkuren tanpa memerlukan lock.

 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}

Kode di atas menggunakan waitgroup saja untuk membuat setiap goroutine menghasilkan 10 bilangan bulat acak secara bersamaan dan menyimpannya di indeks yang dialokasikan. Dalam kode ini, waitgroup digunakan untuk menunggu hingga semua goroutine selesai, kemudian mencetak Done. Dengan menggunakan waitgroup dengan cara ini, beberapa goroutine dapat melakukan pekerjaan secara simultan, menyimpan data tanpa lock hingga semua goroutine selesai, dan melakukan pemrosesan lanjutan secara terpusat setelah semua pekerjaan selesai.

golang.org/x/sync/errgroup.ErrGroup

errgroup adalah paket yang merupakan perluasan dari sync.WaitGroup. Berbeda dengan sync.WaitGroup, errgroup akan membatalkan semua goroutine dan mengembalikan error jika salah satu pekerjaan goroutine menghasilkan error.

 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}

Kode di atas menggunakan errgroup untuk membuat 10 goroutine, dengan maksud menunjukkan kasus di mana error terjadi dengan menimbulkan error pada goroutine kelima. Namun, pada penggunaan aktual, Anda dapat membuat goroutine menggunakan errgroup dan melanjutkan dengan pemrosesan akhir yang beragam untuk setiap kasus di mana error terjadi di dalam setiap goroutine.

once

Alat untuk mengeksekusi kode yang hanya perlu dijalankan sekali. Kode terkait dapat dieksekusi melalui konstruktor berikut.

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 hanya memastikan bahwa fungsi tersebut dapat dieksekusi tepat satu kali sepanjang eksekusi program.

 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}

Kode di atas menggunakan sync.OnceFunc untuk mencetak Hello, World!. Dalam kode ini, fungsi once dibuat menggunakan sync.OnceFunc, dan meskipun fungsi once dipanggil berkali-kali, Hello, World! hanya akan dicetak satu kali.

OnceValue

OnceValue tidak hanya memastikan bahwa fungsi tersebut dieksekusi tepat satu kali sepanjang eksekusi program, tetapi juga menyimpan nilai kembalian fungsi tersebut dan mengembalikannya ketika dipanggil lagi.

 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}

Kode di atas menggunakan sync.OnceValue untuk meningkatkan variabel c sebesar 1. Dalam kode ini, fungsi once dibuat menggunakan sync.OnceValue, dan meskipun fungsi once dipanggil berkali-kali, variabel c hanya akan bertambah satu kali, dan nilai 1 akan dikembalikan.

OnceValues

OnceValues bekerja identik dengan OnceValue, tetapi dapat mengembalikan beberapa nilai.

 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}

Kode di atas menggunakan sync.OnceValues untuk meningkatkan variabel c sebesar 1. Dalam kode ini, fungsi once dibuat menggunakan sync.OnceValues, dan meskipun fungsi once dipanggil berkali-kali, variabel c hanya akan bertambah satu kali, dan nilai 1 akan dikembalikan.

atomic

Paket atomic menyediakan operasi atomik. Paket atomic menawarkan metode seperti Add, CompareAndSwap, Load, Store, dan Swap, namun saat ini penggunaan tipe seperti Int64, Uint64, dan Pointer dianjurkan.

 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}

Ini adalah contoh yang pernah digunakan sebelumnya. Kode ini secara atomik meningkatkan variabel c menggunakan tipe atomic.Int64. Variabel dapat ditingkatkan dan dibaca secara atomik menggunakan metode Add dan Load. Selain itu, nilai dapat disimpan menggunakan metode Store, nilai dapat ditukar menggunakan metode Swap, dan nilai dapat diperiksa lalu ditukar jika sesuai menggunakan metode CompareAndSwap.

cond

sync.Cond

Paket cond menyediakan variabel kondisi (condition variable). Paket cond dapat dibuat dengan sync.Cond dan menawarkan metode Wait, Signal, dan Broadcast.

 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}

Kode di atas menggunakan sync.Cond untuk menunggu hingga variabel ready menjadi true. Dalam kode ini, sync.Cond digunakan untuk menunggu hingga variabel ready menjadi true, kemudian mencetak Ready!. Dengan menggunakan sync.Cond dengan cara ini, beberapa goroutine dapat dibuat menunggu hingga kondisi tertentu terpenuhi secara bersamaan.

Ini dapat dimanfaatkan untuk mengimplementasikan queue sederhana.

 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}

Dengan memanfaatkan sync.Cond seperti ini, alih-alih menggunakan banyak sumber daya CPU seperti pada spin-lock, sistem dapat menunggu secara efisien dan melanjutkan operasi ketika kondisi terpenuhi.

semaphore

golang.org/x/sync/semaphore.Semaphore

Paket semaphore menyediakan semaphore. Paket semaphore dapat dibuat dengan golang.org/x/sync/semaphore.Semaphore dan menawarkan metode Acquire, Release, dan TryAcquire.

 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}

Kode di atas membuat semaphore menggunakan paket semaphore, dan menunjukkan cara memperoleh semaphore menggunakan metode Acquire dan melepaskannya menggunakan metode Release. Dalam kode ini, kami telah menunjukkan cara memperoleh dan melepaskan semaphore menggunakan semaphore.

Penutup

Sepertinya konten dasar sudah cukup sampai di sini. Berdasarkan isi artikel ini, kami berharap Anda dapat memahami cara mengelola konkurensi menggunakan goroutine dan dapat menerapkannya dalam praktik. Kami berharap artikel ini bermanfaat bagi Anda. Terima kasih.