Apa itu mutex?
Mutex, singkatan dari Mutual Exclusion, adalah mekanisme sinkronisasi yang digunakan untuk memastikan bahwa hanya satu proses (thread) yang dapat mengakses suatu resources pada satu waktu. Mutex mencegah terjadinya kondisi balapan (race condition) ketika beberapa thread berusaha mengakses data yang sama secara bersamaan.
Ilustrasi Konsep Mutex
Tanpa Mutex (Race Condition):
┌─────────┐ ┌─────────┐
│ Thread1 │ │ Thread2 │
└────┬────┘ └────┬────┘
│ │
├───► Read: 0 │
│ ├───► Read: 0
├───► +300 │
│ ├───► +500
├───► Write:300 │
│ └───► Write:500
▼
Result: 500 SALAH (seharusnya 800)
Dengan Mutex (Terlindungi):
┌─────────┐ ┌─────────┐
│ Thread1 │ │ Thread2 │
└────┬────┘ └────┬────┘
│ │
├───► Lock │
├───► Read: 0 │
├───► +300 │
├───► Write:300 │
├───► Unlock │
│ ├───► Lock
│ ├───► Read: 300
│ ├───► +500
│ ├───► Write:800
│ └───► Unlock
▼
Result: 800 BENAR
Kenapa Mutex Diperlukan?
Ketika beberapa thread mengakses dan memodifikasi data yang sama tanpa sinkronisasi, bisa terjadi race condition. Bayangkan dua orang mencoba update saldo rekening bank secara bersamaan:
- Thread A baca saldo: 0
- Thread B baca saldo: 0 (masih 0 karena Thread A belum selesai update)
- Thread A tambah 300, tulis: 300
- Thread B tambah 500, tulis: 500 (menimpa hasil Thread A!)
- Hasil akhir: 500 (padahal seharusnya 800!)
Mutex memastikan operasi baca-modifikasi-tulis dilakukan secara atomik (tidak terputus).
Implementasi Mutex
Struktur Proyek
.
├── main.c
├── wallet.c
└── wallet.h
Step 1: Deklarasi Interface Wallet
Pertama, kita deklarasikan fungsi wallet yang akan kita gunakan. Library fungsi sederhana untuk memanajemen wallet. Kita bisa melihat saldo dan memperbarui saldo.
#ifndef WALLET_H
#define WALLET_H
int lihat_saldo();
void update_saldo(int nominal);
#endif
Step 2: Implementasi Fungsi Wallet
Selanjutnya, kita implementasikan fungsi wallet dengan delay untuk mensimulasikan operasi I/O atau proses yang membutuhkan waktu. Ini adalah skenario yang sangat rentan terhadap race condition.
#include "wallet.h"
#include <unistd.h>
// Variable global untuk menyimpan saldo
int saldo = 0;
int lihat_saldo()
{
// Simulasi delay baca database (200ms)
usleep(200000); // 200 milidetik = 0.2 detik
return saldo;
}
void update_saldo(int nominal)
{
// Simulasi delay tulis database (200ms)
usleep(200000);
saldo = nominal;
}
Catatan: Fungsi usleep() mensimulasikan operasi yang membutuhkan waktu (seperti akses database atau file I/O). Delay ini membuat race condition lebih mudah terjadi karena memberikan kesempatan thread lain untuk mengakses data di tengah-tengah operasi.
Step 3: Program Utama dengan Mutex
Selanjutnya, kita buat implementasi multithreading dengan mutex untuk melindungi akses ke saldo wallet.
#include "wallet.h"
#include <pthread.h>
#include <stdio.h>
// Deklarasi fungsi thread
void *deposit(void *arg);
// Deklarasi mutex sebagai variable global
pthread_mutex_t mutex;
int main(void)
{
int saldo_awal = lihat_saldo();
printf("Saldo awal: %d\n", saldo_awal);
// Membuat identifier untuk dua thread
pthread_t thread1, thread2;
// Inisialisasi mutex dengan atribut default (NULL)
pthread_mutex_init(&mutex, NULL);
// Jumlah deposit untuk masing-masing thread
int deposit1 = 300;
int deposit2 = 500;
printf("\nMemulai deposit dari 2 thread...\n");
// Thread untuk menambah saldo
pthread_create(&thread1, NULL, deposit, &deposit1);
pthread_create(&thread2, NULL, deposit, &deposit2);
// Menunggu kedua thread selesai eksekusi
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// Hancurkan mutex setelah selesai digunakan
pthread_mutex_destroy(&mutex);
int saldo_akhir = lihat_saldo();
printf("\nSaldo akhir: %d\n", saldo_akhir);
printf("Expected: %d\n", saldo_awal + deposit1 + deposit2);
return 0;
}
void *deposit(void *nominal)
{
// CRITICAL SECTION START
// Lock mutex - thread lain harus menunggu
pthread_mutex_lock(&mutex);
printf("Thread sedang memproses deposit %d...\n", *(int *)nominal);
// Baca saldo saat ini
int saldo = lihat_saldo();
// Tambahkan nominal deposit
saldo += *(int *)nominal;
// Update saldo baru
update_saldo(saldo);
printf("Deposit %d berhasil!\n", *(int *)nominal);
// Unlock mutex - thread lain bisa masuk
pthread_mutex_unlock(&mutex);
// CRITICAL SECTION END
return NULL;
}
Penjelasan Detail
1. Inisialisasi Mutex
pthread_mutex_init(&mutex, NULL);
- Parameter pertama: pointer ke mutex
- Parameter kedua: atribut mutex (NULL = default)
2. Lock Mutex
pthread_mutex_lock(&mutex);
- Thread yang memanggil ini akan mendapat “kunci”
- Thread lain yang memanggil
lock()akan terblokir (menunggu) - Memastikan hanya 1 thread yang masuk critical section
3. Critical Section
Bagian code antara lock() dan unlock():
int saldo = lihat_saldo(); // Baca
saldo += *(int *)nominal; // Modifikasi
update_saldo(saldo); // Tulis
Ini adalah bagian yang harus dilindungi dari akses konkuren.
4. Unlock Mutex
pthread_mutex_unlock(&mutex);
- Melepas “kunci” agar thread lain bisa masuk
- Thread yang sedang menunggu akan mendapat giliran
5. Destroy Mutex
pthread_mutex_destroy(&mutex);
- Membersihkan resource yang dialokasikan untuk mutex
- Harus dipanggil setelah semua thread selesai
Visualisasi Eksekusi
Timeline Eksekusi dengan Mutex:
| Time | Thread1 | Thread2 | Saldo |
|---|---|---|---|
| t0 | start | start | 0 |
| t1 | LOCK | (waiting…) | 0 |
| t2 | read: 0 | (waiting…) | 0 |
| t3 | calc: 0+300 | (waiting…) | 0 |
| t4 | write: 300 | (waiting…) | 300 |
| t5 | UNLOCK | LOCK | 300 |
| t6 | FINISH | read: 300 | 300 |
| t7 | calc: 300+500 | 300 | |
| t8 | write: 800 | 800 | |
| t9 | UNLOCK | 800 | |
| t10 | FINISH | 800 |
Kompilasi dan Menjalankan
Untuk compile program dengan pthread:
gcc -o wallet main.c wallet.c -lpthread
./wallet
Output yang diharapkan:
Saldo awal: 0
Memulai deposit dari 2 thread...
Thread sedang memproses deposit 300...
Deposit 300 berhasil!
Thread sedang memproses deposit 500...
Deposit 500 berhasil!
Saldo akhir: 800
Expected: 800
Best Practices
- Selalu unlock mutex - Gunakan pattern yang konsisten agar tidak lupa unlock
- Minimize critical section - Hanya lindungi bagian yang benar-benar perlu
- Hindari nested locks - Bisa menyebabkan deadlock1
- Destroy mutex - Bebaskan resource setelah selesai
- Error handling - Cek return value dari fungsi pthread
Kesimpulan
Mutex adalah tool fundamental untuk sinkronisasi thread. Dengan mutex, kita bisa:
- Mencegah race condition
- Memastikan konsistensi data
- Mengontrol akses ke shared resources
Ingat: Lock → Critical Section → Unlock adalah pattern dasar yang harus selalu diikuti!
Footnotes
-
Situasi yang terjadi ketika 2 atau lebih proses terjebak saling menunggu untuk mengakses masing-masing resources, sehingga tidak ada satu pun proses yang berjalan. ↩