← Back to Cecep Azhar
Cecep Azhar #golang#concurrency#goroutines#channels#performance

Go Concurrency Patterns: Tips Menulis Kode Go yang Efisien

Cecep Saeful Azhar • 16 Oct 2026
Go Concurrency Patterns: Tips Menulis Kode Go yang Efisien
Tips dan trik Go concurrency patterns untuk developer Indonesia. Pelajari goroutines, channels, dan patterns yang proven di production system.

Concurrency adalah Keunggulan Go

Salah satu alasan utama saya memilih Go untuk backend development adalah concurrency model-nya yang elegant. Goroutines dan channels memudahkan kita menulis kode yang concurrent tanpa complexity seperti threading di bahasa lain. Tapi banyak developer Indonesia yang belum memanfaatkan concurrency Go secara optimal.

Di artikel ini, saya akan share beberapa patterns yang saya gunakan di production system. Semua sudah terbukti reliable di environment concurrent yang tinggi.

Pattern 1: Fan-Out Fan-In

Pattern ini sangat berguna saat kamu perlu memproses banyak data secara parallel dan menggabungkan hasilnya:

func FanOutFanIn(ctx context.Context, items []Item, processFn func(Item) (Result, error)) ([]Result, error) {
    // Fan-Out: distribusi work ke banyak goroutines
    const numWorkers = 10
    itemChan := make(chan Item, len(items))
    resultChan := make(chan Result, len(items))
    errChan := make(chan error, numWorkers)
    
    // Start workers
    var wg sync.WaitGroup
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for item := range itemChan {
                result, err := processFn(item)
                if err != nil {
                    errChan <- err
                    continue
                }
                resultChan <- result
            }
        }()
    }
    
    // Send items to workers
    go func() {
        for _, item := range items {
            itemChan <- item
        }
        close(itemChan)
    }()
    
    // Fan-In: collect results
    go func() {
        wg.Wait()
        close(resultChan)
        close(errChan)
    }()
    
    var results []Result
    var errs []error
    for result := range resultChan {
        results = append(results, result)
    }
    for err := range errChan {
        errs = append(errs, err)
    }
    
    if len(errs) > 0 {
        return results, fmt.Errorf("%d errors occurred", len(errs))
    }
    return results, nil
}

Pattern 2: Worker Pool

Worker pool adalah pattern yang paling sering saya gunakan. Pattern ini membatasi jumlah goroutines yang aktif sehingga tidak menghabiskan semua resources:

type WorkerPool struct {
    tasks    chan func()
    numWorkers int
    wg       sync.WaitGroup
}

func NewWorkerPool(numWorkers int) *WorkerPool {
    wp := &WorkerPool{
        tasks:      make(chan func(), 100),
        numWorkers: numWorkers,
    }
    
    // Start workers
    for i := 0; i < numWorkers; i++ {
        wp.wg.Add(1)
        go func() {
            defer wp.wg.Done()
            for task := range wp.tasks {
                task()
            }
        }()
    }
    
    return wp
}

func (wp *WorkerPool) Submit(task func()) {
    wp.tasks <- task
}

func (wp *WorkerPool) Stop() {
    close(wp.tasks)
    wp.wg.Wait()
}

Pattern 3: Context dengan Timeout

Di production, kita harus selalu handle timeout untuk setiap operation. Context adalah cara Go untuk mengelola cancellation dan timeout:

func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        if ctx.Err() == context.DeadlineExceeded {
            return nil, fmt.Errorf("request timeout after 5 seconds")
        }
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

Common Mistakes

Kesalahan paling umum yang saya lihat: goroutine leak (lupa close channel atau cancel context), race condition (akses shared state tanpa proper synchronization), dan over-engineering (terlalu banyak goroutine untuk task yang simpel). Selalu gunakan go vet dan race detector (go test -race) untuk mendeteksi masalah ini sebelum ke production.

← More articles from Cecep Azhar Central Content Hub Home →