Menggunakan Teknik Worker Pool dengan Mekanisme Semaphore di Golang

Menggunakan Teknik Worker Pool dengan Mekanisme Semaphore di Golang

Concurrency adalah salah satu kekuatan utama bahasa Go, yang memungkinkan kita menjalankan banyak tugas secara paralel dengan efisiensi tinggi.

Namun, saat menjalankan banyak goroutine secara bersamaan, kita perlu mengelola jumlah goroutine yang berjalan agar tidak membebani sistem.

Di sinilah konsep Worker Pool dan Semaphore menjadi sangat berguna.

Apa Itu Worker Pool dan Semaphore di Go?

Apa Itu Worker Pool?

Worker Pool adalah pola yang memungkinkan kalian mengelola sejumlah worker (goroutine) untuk menangani sejumlah tugas tertentu.

Setiap worker mengambil satu tugas, menyelesaikannya, dan kemudian mengambil tugas berikutnya hingga semua tugas selesai.

Dengan menggunakan worker pool, kalian dapat mengontrol jumlah goroutine yang berjalan secara bersamaan, sehingga mencegah overloading sistem.

Apa Itu Semaphore?

Semaphore adalah alat sinkronisasi yang digunakan untuk mengontrol akses ke sumber daya bersama dengan batasan tertentu.

Dalam konteks concurrency di Go, semaphore dapat digunakan untuk membatasi jumlah goroutine yang berjalan pada satu waktu.

Langkah-Langkah Implementasi Worker Pool dengan Semaphore

Langkah 1: Membuat Worker Pool di Go

Pertama, kita perlu membuat semaphore yang membatasi jumlah goroutine yang bisa berjalan bersamaan. Misalnya, jika kita ingin maksimal 10 goroutine berjalan bersamaan, kita bisa menggunakan semaphore.NewWeighted(10).

package main

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

func main() {
	// Membuat semaphore dengan batas 10 worker
	sem := semaphore.NewWeighted(10)
	ctx := context.TODO()

	// Simulasi tugas yang harus dilakukan
	tasks := make([]int, 20)
	for i := range tasks {
		tasks[i] = i + 1
	}

	// Menjalankan worker pool
	for _, task := range tasks {
		if err := sem.Acquire(ctx, 1); err != nil {
			fmt.Println("Failed to acquire semaphore:", err)
			break
		}

		go func(task int) {
			defer sem.Release(1)
			workerID := task % 10
			fmt.Printf("Worker %d is processing task %d\n", workerID, task)
			time.Sleep(1 * time.Second) // Simulasi pekerjaan
			fmt.Printf("Worker %d has completed task %d\n", workerID, task)
		}(task)
	}

	// Menunggu semua worker selesai
	if err := sem.Acquire(ctx, 10); err != nil {
		fmt.Println("Failed to acquire semaphore:", err)
	}
}

Langkah 2: Menjalankan dan Mengelola Worker Pool

Dalam contoh di atas, kita membuat 20 tugas dan 10 worker. Setiap worker mengambil tugas, memprosesnya, dan kemudian mengambil tugas berikutnya setelah selesai.

Penggunaan semaphore memastikan bahwa hanya 10 goroutine yang berjalan secara bersamaan, menjaga penggunaan sumber daya tetap efisien.

Penjelasan Kode Implementasi Worker Pool dengan Semaphore

Berikut adalah penjelasan untuk kode yang telah kita tulis sebelumnya:

Inisialisasi Semaphore

sem := semaphore.NewWeighted(10)
ctx := context.TODO()
  • semaphore.NewWeighted(10): Membuat sebuah semaphore dengan bobot 10. Ini berarti semaphore ini dapat mengelola hingga 10 unit sekaligus, atau dalam konteks ini, hingga 10 goroutine yang berjalan secara bersamaan.
  • context.TODO(): Membuat sebuah context kosong yang bisa digunakan untuk mengontrol lifecycle dari goroutine. Dalam contoh ini, context belum dipakai untuk pembatalan atau pengontrolan lebih lanjut, tetapi disiapkan untuk penggunaan future enhancement.

Mempersiapkan Tasks

tasks := make([]int, 20)
for i := range tasks {
    tasks[i] = i + 1
}
  • tasks := make([]int, 20): Membuat slice tasks yang terdiri dari 20 elemen integer.
  • Loop for: Mengisi slice tasks dengan angka 1 hingga 20, yang akan menjadi tugas-tugas yang akan diproses oleh worker.

Menjalankan Worker Pool

for _, task := range tasks {
    if err := sem.Acquire(ctx, 1); err != nil {
        fmt.Println("Failed to acquire semaphore:", err)
        break
    }

    go func(task int) {
        defer sem.Release(1)
        workerID := task % 10
        fmt.Printf("Worker %d is processing task %d\n", workerID, task)
        time.Sleep(1 * time.Second) // Simulasi pekerjaan
        fmt.Printf("Worker %d has completed task %d\n", workerID, task)
    }(task)
}
  • Loop for: Loop ini mengiterasi setiap task di dalam slice tasks.
  • sem.Acquire(ctx, 1): Sebelum menjalankan goroutine, program mencoba untuk “mengambil” 1 unit dari semaphore. Jika berhasil, goroutine dapat dijalankan. Jika semaphore sudah penuh (artinya ada 10 goroutine yang sedang berjalan), program akan menunggu sampai ada unit yang dilepaskan (Release).
  • Goroutine go func(task int): Setiap tugas diproses dalam goroutine yang berjalan secara paralel:
    • defer sem.Release(1): Saat goroutine selesai, ia melepaskan kembali 1 unit ke semaphore, memungkinkan goroutine lain untuk dijalankan.
    • workerID := task % 10: Menghitung workerID berdasarkan task number modulo 10, untuk memberikan ID worker yang berada dalam rentang 1-10.
    • fmt.Printf: Menampilkan worker ID yang sedang memproses tugas dan setelah menyelesaikan tugas.
    • time.Sleep(1 * time.Second): Simulasi waktu pemrosesan, yang membuat goroutine tertidur selama 1 detik untuk mensimulasikan pekerjaan yang memakan waktu.

Manfaat Menggunakan Semaphore dalam Pengelolaan Worker Pool

  1. Kontrol Penuh atas Concurrency: kalian bisa menentukan berapa banyak goroutine yang berjalan bersamaan, sehingga menghindari overloading pada sistem.
  2. Sederhana dan Efisien: Dengan menggunakan semaphore, implementasi worker pool menjadi lebih sederhana, dan kalian bisa mendapatkan efisiensi yang lebih baik dalam penggunaan sumber daya.
  3. Penanganan Kesalahan yang Lebih Baik: Dengan mengkombinasikan semaphore dan context, kalian bisa menangani kesalahan dengan lebih baik, termasuk membatalkan semua goroutine jika diperlukan.

Kesimpulan

Menggunakan worker pool dengan semaphore di Go adalah cara yang efektif untuk mengelola concurrency dan memaksimalkan penggunaan sumber daya tanpa membebani sistem.

Dengan pendekatan ini, kalian dapat memastikan aplikasi kalian tetap responsif dan efisien, bahkan saat menangani banyak tugas secara bersamaan.

Jika kalian tertarik untuk mempelajari lebih lanjut, pastikan kalian bereksperimen dengan berbagai skenario dan menyesuaikan jumlah worker berdasarkan kebutuhan spesifik aplikasi kalian.