Mutex memecahkan masalah akses bersama, tetapi tidak efisien ketika thread harus menunggu kondisi tertentu—misalnya buffer tidak kosong. Tanpa condition variable, thread konsumen harus melakukan busy-waiting (loop cek status sambil memegang CPU), yang boros. Condition variable (pthread_cond_t) memungkinkan thread tidur hingga thread lain mengirim sinyal bahwa kondisi sudah terpenuhi. Pada chapter ini kita membangun dua demo: pola wait/signal sederhana antara dua thread, lalu pola klasik producer-consumer dengan buffer berbatas.
Pola Dasar wait/signal #
API condition variable terdiri dari tiga fungsi inti: pthread_cond_wait (menidurkan thread dan melepaskan mutex atomik), pthread_cond_signal (membangunkan satu thread yang menunggu), dan pthread_cond_broadcast (membangunkan semua thread yang menunggu).
/* File: cv_dasar.c - pola wait/signal sederhana */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <pthread.h>
static int siap = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
static void *pekerja(void *arg) {
(void) arg;
printf("pekerja: mulai menyiapkan data...\n");
/* simulasi pekerjaan 1 detik */
struct timespec ts = { 1, 0 };
nanosleep(&ts, NULL);
pthread_mutex_lock(&mtx);
siap = 1;
printf("pekerja: data siap, kirim sinyal\n");
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
return NULL;
}
int main(void) {
pthread_t t;
pthread_create(&t, NULL, pekerja, NULL);
pthread_mutex_lock(&mtx);
printf("main: menunggu data siap...\n");
while (!siap) {
pthread_cond_wait(&cv, &mtx);
}
printf("main: data siap, lanjut eksekusi\n");
pthread_mutex_unlock(&mtx);
pthread_join(t, NULL);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -pthread -o cv_dasar cv_dasar.c && ./cv_dasarOutput yang Dihasilkan:
main: menunggu data siap...
pekerja: mulai menyiapkan data...
pekerja: data siap, kirim sinyal
main: data siap, lanjut eksekusiMari kita telaah baris-baris kode di atas:
- Baris 6-8 (state global): Tiga objek global: flag
siap, mutexmtx, condition variablecv. Condition variable selalu dipakai bersama mutex—tidak pernah berdiri sendiri. - Baris 19 (
pthread_mutex_lock(&mtx);): Pekerja mengunci mutex sebelum menulissiap = 1dan memberi sinyal. Tanpa kunci ini, terdapat race:mainbisa ceksiapsebelum pekerja menulisnya, lalu memanggilwaitsetelah sinyal sudah lewat—mainakan menunggu selamanya. - Baris 22 (
pthread_cond_signal(&cv);): Membangunkan satu thread yang sedangpthread_cond_wait. Bila tidak ada thread yang menunggu, sinyal hilang (bukan antrian). Inilah mengapa kita selalu cek kondisi di loop. - Baris 30 (
while (!siap)): Penting: gunakanwhile, bukanif. Spurious wakeup (thread dibangunkan tanpa sinyal) diperbolehkan oleh POSIX. Loopwhilememastikan thread mengecek ulang kondisi setelah bangun. - Baris 31 (
pthread_cond_wait(&cv, &mtx);): Operasi atomik: (a) melepaskanmtxagar thread lain bisa masuk critical section, (b) menidurkan thread ini, (c) saat dibangunkan, mengunci kembalimtxsebelum kembali. Tanpa melepas mutex, pekerja tidak akan pernah bisa mengunci untuk mengirim sinyal.
Pola Producer-Consumer dengan Buffer Berbatas #
Pola producer-consumer adalah salah satu penggunaan condition variable paling klasik. Produsen memproduksi item dan menaruh di buffer. Konsumen mengambil item dari buffer. Bila buffer penuh, produsen menunggu; bila kosong, konsumen menunggu. Dua condition variable dipakai: satu untuk “buffer tidak penuh” (sinyal ke produsen), satu untuk “buffer tidak kosong” (sinyal ke konsumen).
/* File: cv_prodcon.c - producer-consumer dengan buffer berbatas */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define KAPASITAS 5
static int buffer[KAPASITAS];
static int jumlah = 0; /* jumlah item di buffer */
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t tidak_kosong = PTHREAD_COND_INITIALIZER;
static pthread_cond_t tidak_penuh = PTHREAD_COND_INITIALIZER;
static void jeda_ms(int ms) {
struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L };
nanosleep(&ts, NULL);
}
static void *produsen(void *arg) {
long id = (long) arg;
for (int i = 1; i <= 8; i++) {
pthread_mutex_lock(&mtx);
while (jumlah == KAPASITAS) {
printf("[P%ld] buffer penuh, menunggu...\n", id);
pthread_cond_wait(&tidak_penuh, &mtx);
}
buffer[jumlah++] = i;
printf("[P%ld] produksi %d (jumlah=%d)\n", id, i, jumlah);
pthread_cond_signal(&tidak_kosong);
pthread_mutex_unlock(&mtx);
jeda_ms(100);
}
return NULL;
}
static void *konsumen(void *arg) {
long id = (long) arg;
for (int i = 0; i < 8; i++) {
pthread_mutex_lock(&mtx);
while (jumlah == 0) {
printf("[K%ld] buffer kosong, menunggu...\n", id);
pthread_cond_wait(&tidak_kosong, &mtx);
}
int item = buffer[--jumlah];
printf("[K%ld] konsumsi %d (jumlah=%d)\n", id, item, jumlah);
pthread_cond_signal(&tidak_penuh);
pthread_mutex_unlock(&mtx);
jeda_ms(200); /* konsumen lebih lambat */
}
return NULL;
}
int main(void) {
pthread_t p, k;
pthread_create(&p, NULL, produsen, (void *) 1L);
pthread_create(&k, NULL, konsumen, (void *) 1L);
pthread_join(p, NULL);
pthread_join(k, NULL);
printf("Selesai. jumlah akhir = %d\n", jumlah);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -pthread -o cv_prodcon cv_prodcon.c && ./cv_prodconOutput yang Dihasilkan (urutan dapat sedikit berbeda antar eksekusi):
[K1] buffer kosong, menunggu...
[P1] produksi 1 (jumlah=1)
[K1] konsumsi 1 (jumlah=0)
[P1] produksi 2 (jumlah=1)
[K1] konsumsi 2 (jumlah=0)
[P1] produksi 3 (jumlah=1)
[P1] produksi 4 (jumlah=2)
[K1] konsumsi 4 (jumlah=1)
[P1] produksi 5 (jumlah=2)
[P1] produksi 6 (jumlah=3)
[K1] konsumsi 6 (jumlah=2)
[P1] produksi 7 (jumlah=3)
[P1] produksi 8 (jumlah=4)
[K1] konsumsi 8 (jumlah=3)
[K1] konsumsi 7 (jumlah=2)
[K1] konsumsi 5 (jumlah=1)
[K1] konsumsi 3 (jumlah=0)
Selesai. jumlah akhir = 0Mari kita telaah baris-baris kode di atas:
- Baris 11-13 (dua condition variable):
tidak_kosongditunggu konsumen saatjumlah == 0, diberi sinyal produsen setelah menambah item.tidak_penuhditunggu produsen saatjumlah == KAPASITAS, diberi sinyal konsumen setelah mengambil item. Dua variabel ini menggambarkan dua kondisi berbeda—tidak bisa digabung menjadi satu. - Baris 20-22 (
while (jumlah == KAPASITAS)): Produsen menunggu selama buffer penuh.pthread_cond_wait(&tidak_penuh, &mtx)melepaskan mutex dan menidurkan thread. Saat dibangunkan, mutex dikunci kembali dan loopwhiledicek ulang. - Baris 26 (
pthread_cond_signal(&tidak_kosong);): Setelah menambah item, produsen memberi sinyal “ada item baru” ke konsumen yang mungkin sedang menunggu. Bila tidak ada konsumen yang menunggu, sinyal ini hilang—tetapi tidak masalah karena konsumen berikutnya akan langsung melihatjumlah > 0di loopwhile. - Baris 33-35 (
while (jumlah == 0)): Konsumen menunggu selama buffer kosong. Logika simetris dengan produsen—wait, cek ulangjumlah, ambil item. - Baris 39 (
jeda_ms(200)di konsumen vsjeda_ms(100)di produsen): Konsumen sengaja dibuat lebih lambat agar buffer sempat penuh. Dalam output terlihat produsen sempat menumpuk 3-4 item sebelum konsumen mengambil.
Jebakan Umum #
Tiga kesalahan paling sering ditemui saat memakai condition variable:
- Tidak memakai mutex: Condition variable tidak menyimpan state—ia hanya sinyal “ada yang berubah”. Anda wajib punya variabel kondisi (
siap,jumlah, dll.) yang dilindungi mutex. Tanpa mutex, race condition pada variabel kondisi membuat sinyal bisa hilang. - Memakai
ifalih-alihwhile: Spurious wakeup dapat membangunkan thread tanpa alasan. Loopwhilemengecek ulang kondisi dan kembali tidur bila belum terpenuhi. - Lupa
pthread_cond_signaldi akhir critical section: Bila sinyal diberikan sebelum mutex dilepas, beberapa platform mungkin membangunkan thread yang segera mencoba lock mutex (yang masih dipegang pemanggil)—mubazir. Letakkansignaldi akhir critical section sebelumunlock, atau gunakan pola signal di luar lock untuk efisiensi pada kasus tertentu.