Tahapan Belajar Concurrency Golang Pemula sampai Mahir

Cecep Aprilianto
5 min readJul 30, 2024

--

NY worker illustration

Setelah sebelumnya kita tahu apa saja concurrecy pattern di Golang, selanjutnya mari kita mengenal tahapan belajar concurrency dari pemulai sampai mahir.

Untuk belajar concurrency di Golang dari pemula hingga mahir, berikut adalah beberapa studi kasus yang bisa kalian pelajari

Level Pemula

Goroutines Dasar

  • Menjalankan fungsi sederhana sebagai goroutine.
  • Memahami perbedaan antara goroutine dan thread.
package main

import (
"fmt"
"time"
)

func printHello() {
fmt.Println("Hello from goroutine")
}

func main() {
go printHello()
time.Sleep(time.Second) // Tunggu goroutine selesai
}

Channel Dasar

  • Mengirim dan menerima data antar goroutine menggunakan channel.
  • Memahami buffered dan unbuffered channels.
package main

import "fmt"

func main() {
messages := make(chan string)

go func() {
messages <- "Hello from channel"
}()

msg := <-messages
fmt.Println(msg)
}

Sync Package

  • Penggunaan sync.WaitGroup untuk menunggu beberapa goroutine selesai.
  • Penggunaan sync.Mutex untuk menghindari race condition.
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
var mu sync.Mutex
counter := 0

for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}

wg.Wait()
fmt.Println("Counter:", counter)
}

Level Menengah

Pipeline Pattern

  • Membuat serangkaian goroutine yang berkomunikasi melalui channel untuk memproses data dalam beberapa tahap.
package main

import "fmt"

func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}

func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}

func main() {
nums := gen(2, 3, 4)
sqs := sq(nums)

for n := range sqs {
fmt.Println(n)
}
}

Worker Pool

  • Mengimplementasikan worker pool untuk memproses pekerjaan secara konkuren.
  • Memahami cara mengontrol jumlah goroutine yang berjalan secara bersamaan.
package main

import (
"fmt"
"sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
results <- j * 2
}
}

func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)

for w := 1; w < 4; w++ {
go worker(w, jobs, results)
}

for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)

for a := 1; a <= numJobs; a++ {
fmt.Println(<-results)
}
}

Select Statement

  • Menggunakan select untuk menunggu beberapa channel secara yang terjadi atau dilakukan pada waktu yang bersamaan yang tidak saling menunggu (simultan).
  • Membuat timeout menggunakan select dan time.After.
package main

import (
"fmt"
"time"
)

func main() {
c1 := make(chan string)
c2 := make(chan string)

go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()

for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("Received", msg1)
case msg2 := <-c2:
fmt.Println("Received", msg2)
}
}
}

Context Package

  • Menggunakan context untuk mengatur batas waktu dan pembatalan goroutine.
  • Memahami penggunaan context.WithCancel, context.WithTimeout, dan context.WithDeadline.
package main

import (
"context"
"fmt"
"time"
)

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

go func() {
select {
case <-time.After(1 * time.Second):
fmt.Println("Finished work")
case <-ctx.Done():
fmt.Println("Canceled:", ctx.Err())
}
}()

select {
case <-time.After(3 * time.Second):
fmt.Println("Main finished")
case <-ctx.Done():
fmt.Println("Main canceled:", ctx.Err())
}
}

Level Mahir

Advanced Synchronization

  • Menggunakan sync.Cond untuk mengelola sinyal kondisi.
  • Implementasi skenario producer-consumer yang kompleks.
package main

import (
"fmt"
"sync"
)

func main() {
var mu sync.Mutex
var cond = sync.NewCond(&mu)
ready := false

go func() {
cond.L.Lock()
for !ready {
cond.Wait()
}
fmt.Println("Goroutine proceeding")
cond.L.Unlock()
}()

cond.L.Lock()
ready = true
cond.Signal()
cond.L.Unlock()

// Tunggu sebentar untuk memastikan goroutine selesai
select {}
}

Custom Scheduler

  • Membuat scheduler sederhana untuk mengatur eksekusi tugas secara konkuren.
package main

import (
"fmt"
"time"
)

type Task struct {
id int
}

func (t *Task) Execute() {
fmt.Printf("Executing task %d\n", t.id)
}

func scheduler(tasks []*Task, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()

for _, task := range tasks {
<-ticker.C
go task.Execute()
}
}

func main() {
tasks := []*Task{
{id: 1},
{id: 2},
{id: 3},
}
scheduler(tasks, 2*time.Second)

// Tunggu sebentar untuk memastikan semua task selesai
select {}
}

Handling Deadlocks and Starvation

  • Memahami dan mengatasi masalah deadlock dan starvation dalam program konkuren.
package main

import (
"fmt"
"sync"
)

func main() {
var mu1, mu2 sync.Mutex

go func() {
mu1.Lock()
defer mu1.Unlock()
fmt.Println("Goroutine 1: locked mu1")

mu2.Lock()
defer mu2.Unlock()
fmt.Println("Goroutine 1: locked mu2")
}()

go func() {
mu2.Lock()
defer mu2.Unlock()
fmt.Println("Goroutine 2: locked mu2")

mu1.Lock()
defer mu1.Unlock()
fmt.Println("Goroutine 2: locked mu1")
}()

// Tunggu sebentar untuk mengamati deadlock
select {}
}

Concurrency Patterns

  • Menggunakan berbagai pola concurrency seperti fan-out/fan-in, rate limiting, dan load shedding.
package main

import (
"fmt"
"math/rand"
"sync"
"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
time.Sleep(time.Second)
fmt.Printf("Worker %d processed job %d\n", id, j)
results <- j * 2
}
}

func main() {
rand.Seed(time.Now().UnixNano())

jobs := make(chan int, 100)
results := make(chan int, 100)

for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}

for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)

for a := 1; a <= 9; a++ {
fmt.Println("Result:", <-results)
}
}

Profiling and Optimization

  • Menggunakan tools profiling seperti pprof untuk mengidentifikasi dan mengoptimalkan kinerja program konkuren.
  • Memahami bottleneck dalam program konkuren dan cara mengatasinya.
package main

import (
"net/http"
_ "net/http/pprof"
"runtime"
"time"
)

func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()

// Simulasi workload
for i := 0; i < runtime.NumCPU(); i++ { // Menjalankan workload sesuai dengan jumlah CPU yang tersedia
go func() {
for {
// Contoh workload: Fibonacci
fib(30)
time.Sleep(time.Millisecond * 100)
}
}()
}

// Tunggu sebentar untuk mengamati profil
select {}
}

// Contoh fungsi workload (Fibonacci)
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}

Jalankan program:

go run main.go

Setelah program berjalan, kalian dapat mengakses profil melalui browser dengan membuka URL berikut:

kalian juga dapat menggunakan tools go tool pprof untuk menganalisis profil. Contoh penggunaan:

Mengambil profil CPU selama 30 detik:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Mengambil profil heap:

go tool pprof http://localhost:6060/debug/pprof/heap

Setelah mendownload profil, kalian bisa menggunakan https://www.speedscope.app/ untuk melihat grafiknya.

Building Concurrent Systems

  • Mengimplementasikan sistem konkuren yang kompleks seperti server HTTP yang mendukung banyak klien secara simultan, atau sistem pemrosesan data real-time.
package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}

func main() {
http.HandleFunc("/", handler)
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}

Dengan mempelajari dan mengimplementasikan studi kasus di atas, kalian akan mendapatkan pemahaman yang komprehensif tentang concurrency di Golang dari dasar hingga mahir.

--

--

Cecep Aprilianto
Cecep Aprilianto

Written by Cecep Aprilianto

Web and Backend Developer at Internet

No responses yet