Threads
Pergeseran model mental
Sampai sini kita biasanya membayangkan process punya satu alur kontrol tunggal — satu PC yang bergerak dari instruksi ke instruksi.
Di sistem modern, kenyataannya lebih luas: sebuah process bisa terdiri dari beberapa unit eksekusi yang disebut thread. Masing-masing thread:
- jalan di dalam context process yang sama
- berbagi code dan global data yang sama
Jadi hubungannya bertingkat: OS punya banyak process, dan tiap process bisa punya banyak thread di dalamnya.
+---------------------- Process ----------------------+ | | | Code + Global data <- dibagi bersama | | | | +-----------+ +-----------+ +-----------+ | | | Thread 1 | | Thread 2 | | Thread 3 | | | | PC | | PC | | PC | | | | Register | | Register | | Register | | | | Stack | | Stack | | Stack | | | +-----------+ +-----------+ +-----------+ | | | +-----------------------------------------------------+
Kenapa thread makin penting
CS:APP menyebut tiga alasan thread jadi model pemrograman yang makin sentral:
- Kebutuhan concurrency di network server. Server harus melayani banyak koneksi sekaligus. Model satu-thread-per-koneksi jauh lebih natural daripada memaksa semuanya masuk satu alur kontrol.
- Berbagi data antar thread lebih mudah daripada antar process. Karena thread satu process sudah otomatis berbagi ruang alamat, nggak perlu mekanisme IPC seperti pipe, shared memory segment, atau socket.
- Thread biasanya lebih efisien daripada process. Membuat thread lebih murah daripada membuat process, dan switching antar thread dalam satu process juga lebih ringan.
Selain itu, multi-threading adalah salah satu cara membuat program berjalan lebih cepat ketika tersedia beberapa processor — dibahas lebih jauh di §1.9.2.
Sisi lain dari kemudahan berbagi
Poin nomor 2 di atas adalah pisau bermata dua, dan ini penting banget dipahami sejak awal.
Karena thread berbagi ruang alamat yang sama, dua thread bisa menulis ke variabel yang sama pada saat yang sama tanpa ada yang menghalangi. Hasilnya bergantung urutan eksekusi yang tidak kita kendalikan.
Kemudahan berbagi data itu sekaligus sumber kelas bug paling sulit di pemrograman sistem: data race, deadlock, dan bug yang cuma muncul satu dari sejuta eksekusi. Isolasi antar process yang tadinya terasa merepotkan itu ternyata juga bentuk perlindungan.
Antar process, isolasi memory bikin bug semacam ini secara struktural mustahil terjadi. Antar thread, perlindungan itu hilang dan harus digantikan disiplin programmer plus mekanisme sinkronisasi.
Apa yang dibagi, apa yang privat
Ringkasan pembagiannya (detailnya di Bab 12):
| Dibagi bersama seluruh thread | Privat per thread |
|---|---|
| Code (text segment) | Program counter |
| Global data | Register file |
| Heap | Stack sendiri |
| File descriptor yang terbuka | Thread ID |
Aturan praktisnya: kalau datanya di stack, biasanya aman; kalau global atau di heap, hati-hati.
Catatan untuk kerja systems
- "Thread lebih efisien" itu benar tapi ada batasnya. Membuat thread memang jauh lebih murah daripada membuat process, tapi tiap thread tetap butuh stack sendiri. Ribuan thread berarti ribuan stack — ini salah satu alasan model async/event-driven muncul untuk kasus koneksi sangat banyak.
- Switch antar thread satu process lebih murah karena page table-nya sama. Nggak ada penggantian CR3, jadi TLB nggak perlu di-flush total. Tapi cache tetap bisa terganggu.
- False sharing adalah jebakan performa klasik. Dua thread menulis ke variabel berbeda yang kebetulan berada di cache line yang sama akan saling membatalkan cache satu sama lain. Secara logika nggak ada konflik, secara hardware ada. Solusinya biasanya padding sampai batas cache line.
- Di HPC, thread biasanya di-pin ke core. OpenMP lewat
OMP_PROC_BINDdanOMP_PLACES, ataunumactl/tasksetdi level proses, supaya thread nggak berpindah core dan kehilangan lokalitas cache. - Bahasa punya sikap berbeda soal ini. Rust memindahkan pengecekan keamanan thread ke compile time lewat trait
SenddanSync, sehingga banyak data race ditolak sebelum program jalan. Python sebaliknya membatasi eksekusi bytecode dengan GIL, sehingga thread bagus untuk I/O-bound tapi tidak memberi speedup untuk CPU-bound.
Tip: Kalau tujuannya cuma concurrency (banyak hal saling menunggu I/O), thread bukan satu-satunya jawaban — async runtime sering lebih hemat. Thread jadi wajib kalau tujuannya parallelism untuk kerja CPU-bound.
Yang akan datang
Konsep dasar concurrency, termasuk cara menulis program ber-thread, dibahas di Bab 12. Diskusi soal memanfaatkan beberapa processor ada di §1.9.2.
Istilah kunci
- Thread
- Unit eksekusi di dalam sebuah process; berjalan dalam context process itu dan berbagi code serta global data dengan thread lain.
- Multi-threading
- Menjalankan beberapa thread dalam satu process, salah satunya untuk memanfaatkan beberapa processor.
- Data race
- Kondisi ketika dua thread mengakses data yang sama tanpa sinkronisasi dan minimal satu di antaranya menulis.
- False sharing
- Penurunan performa akibat dua thread menulis ke variabel berbeda yang berada di cache line yang sama.