Tahapan Belajar Concurrency Golang Pemula sampai Mahir
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
dantime.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
, dancontext.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:
- Profil CPU: http://localhost:6060/debug/pprof/profile?seconds=30
- Profil Memori: http://localhost:6060/debug/pprof/heap
- Profil Goroutines: http://localhost:6060/debug/pprof/goroutine
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.