Belajar C++ - Concurrency & Multithreading
Series/Belajar C++/Episode 12
Episode 12 of 24

Belajar C++ - Concurrency & Multithreading

Episode ini membahas concurrency di C++ modern: membuat thread dengan std::thread, sinkronisasi dengan std::mutex dan std::lock_guard, condition variables, std::future dan std::promise beserta std::async, menghindari data race dan deadlock, serta thread pools.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Prosesor modern punya banyak core, dan memanfaatkannya membutuhkan concurrency — menjalankan banyak pekerjaan secara bersamaan. Sejak C++11, concurrency menjadi bagian resmi bahasa lewat library <thread>, sehingga kalian tidak perlu bergantung pada API sistem operasi.

Episode 12 membangun pemahaman concurrency secara bertahap: membuat thread dengan std::thread, melindungi data bersama dengan std::mutex dan std::lock_guard, menyinkronkan pekerjaan dengan condition variables, mengambil hasil dengan std::future dan std::async, menghindari data race dan deadlock, serta merangkai semua itu menjadi thread pool.

Membuat Thread dengan std::thread

Thread Pertama

std::thread menerima fungsi beserta argumennya dan menjalankannya di core lain. Wajib memanggil join() agar main menunggu thread selesai, atau detach() untuk melepaskannya:

Thread pertama
cat > thread.cpp <<'EOF'
#include <iostream>
#include <thread>
 
void kerja(int id) {
    std::cout << "Thread " << id << " bekerja\n";
}
 
int main() {
    std::thread t1(kerja, 1);
    std::thread t2(kerja, 2);
 
    t1.join();
    t2.join();
    std::cout << "Semua selesai\n";
}
EOF
g++ -std=c++20 thread.cpp -pthread -o thread
./thread

std::thread t1(kerja, 1) menjalankan fungsi kerja dengan argumen 1 di thread baru. Bendera -pthread wajib saat kompilasi agar library thread di-link, dan t1.join() membuat main menunggu thread selesai.

Bahaya Output Bersama

Program di atas bisa menghasilkan output acak — dua thread menulis std::cout bersamaan. Itu contoh data race: dua thread mengakses data bersama tanpa sinkronisasi. Solusinya adalah mutex.

Mutex dan Sinkronisasi

Melindungi Data Bersama

Mutex mengunci akses ke data agar hanya satu thread masuk pada satu waktu. std::lock_guard membungkus mutex dan menguncinya otomatis selama scope — aman terhadap exception karena dikelola RAII:

Mutex melindungi counter
cat > mutex.cpp <<'EOF'
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
 
std::mutex mtx;
int counter = 0;
 
void tambah(int n) {
    for (int i = 0; i < n; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}
 
int main() {
    std::vector<std::thread> ts;
    for (int i = 0; i < 4; ++i) {
        ts.emplace_back(tambah, 1000);
    }
    for (auto& t : ts) {
        t.join();
    }
    std::cout << "Counter: " << counter << "\n";
}
EOF
g++ -std=c++20 mutex.cpp -pthread -o mutex
./mutex

std::lock_guard<std::mutex> lock(mtx) mengunci mtx saat dibuat dan membuka saat keluar dari blok. Tanpa mutex, empat thread yang menambah counter bersamaan akan kehilangan banyak increment karena operasi read-modify-write saling menimpa.

Menghindari Deadlock

Deadlock terjadi saat dua thread saling menunggu mutex yang masing-masing dipegang lawannya. Aturan emas: kunci beberapa mutex dalam urutan yang sama di semua thread, atau pakai std::lock yang mengunci beberapa mutex sekaligus secara aman.

Condition Variable

Condition variable membuat thread menunggu suatu kondisi sebelum melanjutkan. Pola producer-consumer klasik: satu thread memproduksi, yang lain menunggu. Gunakan std::unique_lock dengan condition variable karena wait membuka dan mengunci ulang:

Condition variable
cat > condvar.cpp <<'EOF'
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex mtx;
std::condition_variable cv;
bool siap = false;
 
void pekerja() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return siap; });
    std::cout << "Pekerja mulai bekerja\n";
}
 
int main() {
    std::thread t(pekerja);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    {
        std::lock_guard<std::mutex> lock(mtx);
        siap = true;
    }
    cv.notify_one();
    t.join();
}
EOF
g++ -std=c++20 condvar.cpp -pthread -o condvar
./condvar

cv.wait(lock, [] { return siap; }) membuka lock, menunggu sampai siap menjadi true, lalu menguncinya kembali. cv.notify_one() membangunkan thread yang menunggu. Predikat kedua menghindari missed wakeup.

Future, Promise, dan async

Mengambil Hasil Thread

Mengembalikan nilai dari thread secara langsung tidak mungkin. Solusinya std::future dan std::promise: promise mengisi nilai, future mengambilnya. Cara paling praktis adalah std::async yang menggabungkan keduanya:

async dan future
cat > async.cpp <<'EOF'
#include <iostream>
#include <future>
 
int kuadrat(int x) {
    return x * x;
}
 
int main() {
    std::future<int> hasil = std::async(std::launch::async, kuadrat, 9);
    std::cout << "Hasil: " << hasil.get() << "\n";
}
EOF
g++ -std=c++20 async.cpp -pthread -o async
./async

std::async(std::launch::async, kuadrat, 9) menjalankan kuadrat(9) secara asinkron dan mengembalikan std::future<int>. hasil.get() menunggu hasilnya. Jika fungsi melempar exception, get() melempar ulang di pemanggil.

Thread Pool

Membuat Thread Mahal

Membuat thread mahal. Untuk banyak task, buat sejumlah thread tetap (misal sebanyak core) dan bagikan pekerjaan — inilah thread pool. Intinya sederhana: queue task bersama dan worker yang mengambil satu per satu. Setiap worker menunggu di cv_.wait, memproses task yang masuk, dan berhenti saat destructor menandai berhenti_ lalu membangunkan semua worker. Pola ini menjadi fondasi server dan aplikasi paralel besar.

Warning

Output std::cout dari banyak thread bisa campur aduk. Untuk log di produksi, serialkan dengan mutex atau pakai library logging — episode 22 akan membahasnya.

Penutup

Inti yang harus dibawa pulang:

  • std::thread menjalankan fungsi di thread baru; wajib join atau detach.
  • std::mutex dan std::lock_guard melindungi data bersama dari data race.
  • Kunci mutex dengan urutan konsisten untuk menghindari deadlock.
  • Condition variable menunggu kondisi; selalu pakai predikat.
  • std::async dan std::future mengambil hasil dari thread dengan mudah.
  • Thread pool membagi banyak task ke sejumlah worker tetap.

Di episode 13 selanjutnya kita akan membahas networking basics — socket programming TCP dan UDP dengan BSD sockets, komunikasi client-server dan desain protokol sederhana, byte order dan address structures, error handling, serta pertimbangan cross-platform di Windows dan Linux.

Belajar C++ - Concurrency & Multithreading | Belajar C++