Apa itu barrier?

Barrier membuat sekumpulan thread saling menunggu sampai semuanya mencapai satu titik tertentu, baru semuanya diizinkan lanjut bersamaan. Beda dari mutex atau semaphore yang mengatur akses ke resource, barrier murni soal timing — memastikan tidak ada thread yang melangkah ke fase berikutnya sebelum semua thread lain menyelesaikan fase saat ini.

Ilustrasi

Tanpa barrier: thread lanjut sendiri-sendiri, tidak sinkron
  Thread1 selesai fase 1 cepat, langsung mulai fase 2
  Thread2 masih di fase 1
  Thread3 mulai fase 2 padahal data Thread2 belum lengkap
 
Dengan barrier: semua nunggu titik yang sama
  Thread1, Thread2, Thread3 -> semua sampai barrier -> semua lanjut fase 2 bersamaan

Kenapa diperlukan

Bayangkan komputasi paralel yang dibagi jadi beberapa fase, di mana fase kedua butuh hasil lengkap dari semua thread di fase pertama — misalnya tiap thread memproses satu chunk data di fase 1, lalu fase 2 menggabungkan semua chunk itu. Kalau salah satu thread lanjut ke fase 2 sebelum thread lain selesai fase 1, hasil gabungannya jadi tidak lengkap atau salah. Barrier memastikan semua thread benar-benar sampai di garis yang sama dulu.

Implementasi

Struktur proyek

.
└── barrier_demo.c

Step 1: program dengan barrier

4 thread, masing-masing mengerjakan fase 1, menunggu di barrier, lalu lanjut fase 2.

#include <pthread.h>
#include <stdio.h>
 
#define JUMLAH_THREAD 4
 
pthread_barrier_t barrier;
 
void *proses_fase(void *arg)
{
    int id = *(int *)arg;
 
    printf("Thread %d: memproses fase 1...\n", id);
 
    printf("Thread %d: menunggu thread lain selesai fase 1\n", id);
    pthread_barrier_wait(&barrier);
 
    printf("Thread %d: semua selesai, mulai fase 2...\n", id);
 
    return NULL;
}
 
int main(void)
{
    pthread_barrier_init(&barrier, NULL, JUMLAH_THREAD);
 
    pthread_t threads[JUMLAH_THREAD];
    int ids[JUMLAH_THREAD];
 
    for (int i = 0; i < JUMLAH_THREAD; i++) {
        ids[i] = i + 1;
        pthread_create(&threads[i], NULL, proses_fase, &ids[i]);
    }
    for (int i = 0; i < JUMLAH_THREAD; i++) {
        pthread_join(threads[i], NULL);
    }
 
    pthread_barrier_destroy(&barrier);
    return 0;
}

Step 2: barrier untuk banyak fase

Barrier bisa dipakai berkali-kali, cocok untuk komputasi yang punya banyak fase berurutan.

void *proses_multi_fase(void *arg)
{
    int id = *(int *)arg;
 
    for (int fase = 1; fase <= 3; fase++) {
        printf("Thread %d: mengerjakan fase %d\n", id, fase);
        pthread_barrier_wait(&barrier);
    }
 
    return NULL;
}

Penjelasan

pthread_barrier_init(&barrier, NULL, JUMLAH_THREAD) — parameter ketiga adalah jumlah thread yang harus memanggil pthread_barrier_wait sebelum semuanya diizinkan lanjut.

pthread_barrier_wait memblok thread yang memanggilnya sampai jumlah thread yang menunggu mencapai angka yang di-set saat init. Begitu jumlahnya terpenuhi, semua thread yang menunggu dilepas bersamaan. Fungsi ini mengembalikan PTHREAD_BARRIER_SERIAL_THREAD untuk satu thread (dipilih arbitrer) dan 0 untuk sisanya — bisa dimanfaatkan kalau butuh satu thread melakukan semacam cleanup antar fase.

pthread_barrier_destroy membersihkan resource barrier setelah tidak dipakai lagi.

Timeline eksekusi

Time Thread1 Thread2 Thread3 Thread4
t0 fase 1 (cepat) fase 1 fase 1 (lambat) fase 1
t1 BARRIER (nunggu) fase 1 fase 1 (lambat) BARRIER (nunggu)
t2 (nunggu) BARRIER (nunggu) fase 1 (lambat) (nunggu)
t3 (nunggu) (nunggu) BARRIER — semua sampai! (nunggu)
t4 fase 2 fase 2 fase 2 fase 2

Semua thread mulai fase 2 di waktu yang sama persis (t4), walau kecepatan menyelesaikan fase 1 mereka berbeda-beda.

Kompilasi

gcc -o barrier_demo barrier_demo.c -lpthread
./barrier_demo

Best practices

Jumlah thread yang memanggil pthread_barrier_wait harus tepat sama dengan yang di-set di init, atau program akan hang menunggu thread yang tidak pernah datang. Manfaatkan PTHREAD_BARRIER_SERIAL_THREAD kalau butuh satu thread melakukan cleanup atau reset state bersama antar fase tanpa race condition.

Barrier itu reusable, bisa dipakai berkali-kali untuk banyak fase, tapi pastikan tidak ada thread yang telat gabung karena bug logika. Kurang cocok untuk jumlah thread yang dinamis1, karena barrier statis butuh jumlah thread yang diketahui di awal. Destroy setelah semua fase selesai, jangan destroy barrier selagi masih ada thread yang mungkin memanggil wait.

Kesimpulan

Barrier menyelesaikan masalah sinkronisasi yang murni soal timing antar fase, bukan soal akses ke shared resource seperti mutex atau semaphore. Cocok untuk komputasi paralel bertahap, dari map-reduce sederhana sampai simulasi berbasis step — dan jadi penutup yang pas untuk seri ini, dari melindungi satu variabel dengan mutex sampai menyinkronkan timing seluruh kumpulan thread.

Footnotes

  1. Barrier di POSIX bersifat lokal ke satu kelompok thread yang sudah ditentukan jumlahnya di awal. Untuk sinkronisasi yang lebih fleksibel, dengan jumlah partisipan yang berubah-ubah, biasanya dibangun struktur kustom di atas mutex + condition variable. ↩