| Tweet |
|
Topik:
|
Process vs Thread: Penjelasan Lengkap untuk Memahami PerbedaannyaOleh: Hobon.id (20/08/2026)
Setiap software yang pernah berjalan di komputer pasti dieksekusi oleh suatu entitas; memahami apa sebenarnya entitas tersebut—serta bagaimana sistem operasi mengelola ribuan entitas semacam itu yang berjalan secara bersamaan pada perangkat keras dengan jumlah inti (core) CPU fisik yang relatif terbatas—merupakan salah satu konsep paling mendasar dalam ilmu komputer. Istilah "process" dan "thread" sering muncul dalam percakapan teknis, yaitu seperti developer mengatakan aplikasinya "memulai proses baru", pemrogram sistem berbicara tentang "mengaktifkan thread pekerja", dan task manager atau activity monitor menampilkan daftar proses yang sedang berjalan, masing-masing dengan sejumlah thread. Meskipun istilah-istilah ini sering digunakan secara santai dan kadang saling menggantikan dalam percakapan sehari-hari, keduanya sebenarnya merujuk pada konsep yang sangat berbeda, dengan karakteristik, pertimbangan teknis, dan pola kegagalan yang juga berbeda secara signifikan.Memahami perbedaan ini dengan tepat sangatlah penting, jauh melampaui sekadar kebutuhan untuk lulus wawancara teknis atau agar tampak paham dalam percakapan. Pilihan antara merancang software konkuren menggunakan banyak proses atau banyak thread memiliki konsekuensi praktis langsung terhadap penggunaan memori, kompleksitas komunikasi, isolasi kegagalan, dan performa mentah. Konsekuensi ini nyata dampaknya dalam insiden operasional (produksi) jika model yang dipilih keliru atau jika perilaku sebenarnya dari proses dan thread disalahpahami oleh para engineer yang membangun sistem di atasnya. Advertisement:
Apa Itu Process SebenarnyaSebuah process atau proses adalah instansi dari program yang sedang berjalan, yaitu unit dasar sistem operasi untuk alokasi sumber daya dan eksekusi. Saat kita mengeklik dua kali ikon aplikasi, menjalankan perintah di terminal, atau server memulai sebuah aplikasi, sistem operasi membuat sebuah proses, yaitu sistem operasi memuat kode eksekusi program ke dalam memori, mengalokasikan wilayah memori khusus untuk digunakan program tersebut, dan mulai menjalankan instruksi-instruksi program. Yang terpenting, proses bukan sekadar "kode yang sedang berjalan", tetapi proses adalah lingkungan eksekusi yang lengkap dan mandiri yang dibangun serta dikelola oleh sistem operasi untuk program tersebut. Setiap proses diberi ruang alamat virtualnya sendiri, yaitu rentang alamat memori privat yang hanya dapat dilihat dan digunakan oleh proses itu, serta terisolasi sepenuhnya dari proses lain yang berjalan di mesin yang sama. Di dalam ruang alamat ini, sistem operasi mengatur memori ke dalam wilayah-wilayah terpisah, yaitu seperti segmen teks yang berisi instruksi kode mesin hasil kompilasi yang sedang dijalankan; segmen data yang berisi variabel global dan statis; heap, yaitu wilayah untuk memori yang dialokasikan secara dinamis yang ukurannya dapat membesar dan mengecil saat program meminta serta melepaskan memori selama eksekusi; dan stack, yang digunakan untuk pemanggilan fungsi, variabel lokal, serta informasi pencatatan yang diperlukan untuk mengembalikan kendali secara tepat saat fungsi selesai dijalankan. Selain memori, sistem operasi melacak sejumlah besar informasi status tambahan untuk setiap proses, yang secara kolektif sering disebut sebagai Process Control Block (PCB)—yaitu process's unique identifier (PID), status eksekusi saat ini (running, waiting, ready), nilai-nilai yang tersimpan di register CPU (diperlukan untuk menjeda dan kemudian melanjutkan eksekusi proses dengan benar), tabel deskriptor file yang mewakili file terbuka dan koneksi jaringan, informasi keamanan dan izin yang menentukan apa yang boleh dilakukan proses tersebut, serta penunjuk ke struktur manajemen memori yang mendefinisikan ruang alamatnya. Besarnya informasi status khusus dan terisolasi inilah yang menjadikan proses sebagai unit yang benar-benar berat dan mandiri. Membuat proses baru merupakan operasi yang relatif "mahal" (membutuhkan banyak sumber daya) bagi sistem operasi—karena melibatkan alokasi dan inisialisasi memori baru serta struktur pencatatan kernel dalam jumlah yang signifikan—namun biaya tersebut menghasilkan isolasi yang nyata dan berharga: dalam kondisi normal, satu proses tidak dapat membaca atau merusak memori proses lain secara langsung, karena kedua proses tersebut sama sekali tidak berbagi ruang alamat. Apa Itu Thread SebenarnyaThread adalah unit eksekusi terkecil yang dikelola dan dijalankan oleh penjadwal sistem operasi pada inti CPU. Setiap proses memiliki setidaknya satu thread—yang sering disebut sebagai main thread (thread utama)—yang mulai mengeksekusi kode program segera setelah proses tersebut dimulai. Namun, sebuah proses dapat membuat thread tambahan, dan setiap thread yang dibuat dalam proses tersebut berbagi ruang alamat memori, deskriptor file, serta sebagian besar sumber daya lain yang tercatat dalam Process Control Block (PCB) milik proses tersebut. Sifat berbagi memori ini merupakan perbedaan paling signifikan antara thread dan proses, dan hal ini perlu dinyatakan secara jelas dan tepat: beberapa thread dalam proses yang sama dapat secara langsung membaca dan menulis variabel serta struktur data yang sama persis di memori, tanpa perlu menyalin data secara eksplisit di antara mereka atau berkomunikasi melalui saluran apa pun yang dimediasi oleh sistem operasi. Jika satu thread dalam proses mengalokasikan objek di heap dan menyimpan referensinya dalam variabel yang juga dapat dilihat oleh thread lain dalam proses yang sama, setiap thread lain tersebut dapat segera mengakses objek yang sama persis secara langsung di lokasi memorinya, tanpa ada proses penyalinan sama sekali. Meskipun thread-thread dalam suatu proses berbagi sebagian besar sumber daya proses tersebut, setiap thread individu tetap memiliki sejumlah kecil status yang benar-benar privat; tanpa hal ini, sistem operasi tidak akan mungkin dapat menjalankan beberapa thread secara independen. Secara khusus, setiap thread memiliki program counter sendiri (yang melacak instruksi mana yang sedang dieksekusi oleh thread tersebut, terlepas dari apa yang sedang dilakukan thread lain dalam proses yang sama), nilai register sendiri (isi register CPU yang spesifik untuk titik eksekusi thread saat itu), dan stack sendiri (yang digunakan untuk pemanggilan fungsi dan variabel lokal thread tersebut, serta dipisahkan dari stack thread lain agar pemanggilan fungsi konkuren pada thread yang berbeda tidak merusak variabel lokal maupun data pelacakan pemanggilan fungsi milik thread lainnya). Karena thread membawa jauh lebih sedikit informasi status khusus dibandingkan proses utuh—tidak ada ruang alamat terpisah, tidak ada tabel deskriptor berkas terpisah, dan tidak ada struktur manajemen memori terpisah—maka pembuatan thread baru di dalam proses yang sedang berjalan merupakan operasi yang jauh lebih ringan dan cepat bagi sistem operasi dibandingkan pembuatan proses yang benar-benar baru; demikian pula, pengalihan fokus CPU dari satu thread ke thread lainnya juga merupakan operasi yang jauh lebih ringan. Perbedaan Utama: Memori dan Ruang AlamatJika ada satu fakta penting yang perlu dipahami dari panduan ini, inilah faktanya: secara bawaan, proses tidak saling berbagi memori, sedangkan thread dalam proses yang sama saling berbagi memori. Segala perbedaan praktis lainnya antara proses dan thread—seperti cara berkomunikasi, keamanan saat dijalankan secara konkuren, biaya pembuatan dan peralihan konteks, serta dampak kegagalan satu bagian terhadap bagian lainnya—pada akhirnya berakar pada fakta arsitektural mendasar ini. Karena setiap proses memiliki ruang alamat virtual yang privat dan terisolasi, dua proses yang sepenuhnya terpisah dan berjalan pada mesin yang sama—bahkan jika keduanya merupakan instans dari program yang persis sama—tidak dapat melihat atau memodifikasi variabel, objek, atau struktur data satu sama lain secara langsung. Jika Proses A memiliki variabel bernilai 42 yang tersimpan di memorinya sendiri, Proses B sama sekali tidak memiliki akses langsung untuk melihat lokasi memori tersebut; bagi ruang alamat Proses B, lokasi spesifik itu tidak memiliki makna apa pun, karena alamat virtual Proses B dipetakan ke wilayah RAM fisik yang sepenuhnya terpisah (pemetaan ini dikelola dan ditegakkan oleh komponen perangkat keras bernama Memory Management Unit yang bekerja sama dengan kernel sistem operasi). Jika kedua proses tersebut benar-benar perlu bertukar data atau berkoordinasi, mereka harus melakukannya melalui mekanisme komunikasi eksplisit yang difasilitasi oleh sistem operasi. Sebaliknya, thread dalam proses yang sama berbagi ruang alamat yang sama sepenuhnya. Ketika Thread 1 dalam suatu proses menulis nilai 42 ke dalam variabel yang berada di heap (bukan di stack privat thread itu sendiri), Thread 2 yang berjalan dalam proses yang sama dapat membaca nilai 42 tersebut secara langsung dan seketika, tanpa memerlukan mekanisme komunikasi, penyalinan data, ataupun keterlibatan sistem operasi; kedua thread tersebut secara harfiah melihat dan memodifikasi lokasi memori fisik yang sama persis. Inilah yang membuat multithreading sangat ampuh untuk jenis masalah tertentu, yaitu berbagi data dan koordinasi kerja antar-thread dapat dilakukan dengan cepat dan langsung. Hal ini juga merupakan faktor utama yang membuat multithreading sangat berbahaya dan rentan terhadap bug jika akses bersama tersebut tidak dikelola dengan sangat cermat dan penuh kehati-hatian. Cara Sistem Operasi Menjadwalkan Proses dan ThreadSistem operasi modern tidak menjadwalkan proses sebagai unit dasar waktu eksekusi CPU, melainkan menjadwalkan thread. Ini adalah poin penting yang sering disalahpahami: bahkan sebuah proses yang hanya memiliki satu thread (yaitu thread utamanya) dianggap oleh scheduler sebagai satu unit kerja yang dapat dijadwalkan—yakni thread tunggal tersebut—di dalam kumpulan tugas yang bersaing memperebutkan waktu CPU; proses tersebut tidak dipandang sebagai satu kesatuan besar yang tak terbagi. Scheduler sistem operasi adalah komponen kernel yang bertugas memutuskan, pada saat tertentu, thread mana dari sekian banyak thread yang ingin berjalan di sistem yang berhak mendapatkan inti CPU yang sedang melakukan pengambilan keputusan tersebut. Pada mesin dengan jumlah thread aktif yang sangat banyak—yang bisa mencapai ratusan atau ribuan pada desktop atau server modern—namun memiliki jumlah inti CPU fisik yang relatif sedikit (umumnya antara 4 hingga 64 pada perangkat keras konsumen dan server), scheduler tidak dapat menjalankan setiap thread secara bersamaan dalam paralelisme sejati; scheduler harus terus-menerus memutuskan thread mana yang akan dieksekusi pada inti yang tersedia pada saat tertentu, dan untuk durasi berapa lama. Scheduler menciptakan ilusi kuat bahwa banyak hal terjadi "secara bersamaan"—bahkan pada perangkat keras dengan jumlah inti fisik yang jauh lebih sedikit dibandingkan jumlah thread yang siap berjalan—melalui teknik time-slicing yang sangat cepat. Teknik ini memberikan potongan waktu CPU yang sangat singkat (sering kali dalam orde beberapa milidetik) kepada setiap thread yang siap, lalu menghentikan sementara eksekusi thread tersebut dan mengalihkan inti CPU untuk menjalankan thread lain yang juga siap. Proses ini berputar di antara kumpulan thread yang siap berjalan dengan kecepatan dan kesinambungan sedemikian rupa sehingga, dari sudut pandang persepsi manusia, banyak program tampak benar-benar berjalan secara bersamaan—bahkan pada mesin yang hanya memiliki satu inti CPU, di mana eksekusi simultan yang sesungguhnya atas lebih dari satu tugas pada satu momen fisik yang sama sebenarnya tidak mungkin dilakukan. Context Switching: Mengapa Thread Lebih CepatTindakan scheduler yang menghentikan sementara eksekusi satu thread dan melanjutkan eksekusi thread lain pada inti CPU tertentu disebut sebagai context switch. Memahami secara tepat pekerjaan apa saja yang terlibat dalam proses ini—dan yang terpenting, bagaimana pekerjaan tersebut berbeda tergantung pada apakah pergantian terjadi antara dua thread dari proses yang sama atau antara thread dari dua proses yang sepenuhnya berbeda—menjelaskan secara langsung mengapa thread sering disebut lebih "ringan" dan lebih cepat dalam operasionalnya dibandingkan proses. Ketika scheduler mengalihkan CPU dari menjalankan satu thread ke thread lain yang merupakan bagian dari proses yang sama, pergantian tersebut relatif "murah" (membutuhkan sumber daya minimal), yaitu nilai register CPU saat ini perlu disimpan (agar thread yang dihentikan dapat melanjutkan kembali tepat dari titik terakhirnya) dan nilai register thread yang akan dijalankan (yang sebelumnya telah disimpan) perlu dipulihkan, serta stack pointer yang aktif saat ini perlu diperbarui agar menunjuk ke stack milik thread yang baru tersebut. Karena kedua thread berasal dari proses yang sama dan dengan demikian berbagi ruang alamat virtual yang persis sama, bagian yang "mahal" (memakan banyak sumber daya) dari pergantian konteks—yaitu mengonfigurasi ulang pemetaan translasi alamat pada Memory Management Unit (MMU) agar CPU dapat menerjemahkan alamat memori virtual dengan benar sesuai tata letak memori proses yang sama sekali berbeda—sama sekali tidak diperlukan dalam kasus khusus ini. Ketika scheduler mengalihkan CPU dari menjalankan thread milik suatu proses ke thread milik proses yang sama sekali berbeda, proses peralihan tersebut memakan sumber daya yang jauh lebih besar karena alasan yang baru saja dijelaskan, yaitu karena kedua proses memiliki ruang alamat virtual yang sepenuhnya terpisah dan tidak saling terkait, status penerjemahan alamat pada Memory Management Unit (MMU) benar-benar perlu dikonfigurasi ulang sebagai bagian dari peralihan tersebut. Pada banyak arsitektur perangkat keras, hal ini juga memicu pembatalan validitas entri penerjemahan alamat yang tersimpan dalam cache (di dalam struktur perangkat keras yang disebut Translation Lookaside Buffer atau TLB)—entri yang sebelumnya terbentuk khusus untuk proses yang kini ditinggalkan. Akibatnya, proses yang baru dijadwalkan akan mengalami latensi akses memori tambahan (setidaknya pada tahap awal) hingga entri penerjemahan alamat miliknya sendiri terbentuk kembali di dalam cache melalui penggunaan normal. Inilah alasan mengapa aplikasi yang perlu melakukan peralihan konteks secara sangat sering dan cepat di antara berbagai unit kerja konkuren—seperti server web yang menangani banyak koneksi klien secara bersamaan—sering kali memperoleh manfaat kinerja nyata yang dapat diukur dengan membangun aplikasi menggunakan beberapa thread dalam satu proses, alih-alih menggunakan beberapa proses yang sepenuhnya terpisah. Hal ini dikarenakan peralihan konteks antar-thread dalam proses yang sama jauh lebih ringan bagi CPU dan sistem operasi untuk dilaksanakan. Inter-Process Communication vs. Shared MemoryKarena proses-proses yang terpisah tidak berbagi memori secara bawaan, dua proses yang benar-benar perlu bertukar data atau mengoordinasikan pekerjaannya harus melakukannya melalui mekanisme eksplisit yang secara kolektif dikenal sebagai Inter-Process Communication atau IPC. Memahami keberadaan serta biaya relatif dari mekanisme-mekanisme ini merupakan konteks penting untuk memahami mengapa pilihan antara proses dan thread menjadi sangat krusial bagi sistem yang mengharuskan komponen-komponennya bekerja sama secara aktif dan saling berbagi informasi. Pipe memungkinkan satu proses menulis aliran byte yang dapat dibaca oleh proses lain yang terkait (biasanya proses anak yang dibuat oleh proses pertama); mekanisme ini umum digunakan untuk merangkai utilitas baris perintah sederhana. Named pipe (atau disebut juga FIFO) memperluas konsep dasar ini untuk memungkinkan komunikasi antara proses yang tidak harus memiliki hubungan induk-anak, dengan mengidentifikasi komunikasi tersebut melalui nama dalam sistem file alih-alih mengharuskan proses-proses tersebut untuk saling mengenal secara langsung. Antrean pesan memungkinkan proses saling mengirim pesan terstruktur yang terpisah melalui antrean yang dikelola oleh kernel, di mana sistem operasi menangani proses pengiriman dan buffering di tingkat dasar. Segmen shared memory merupakan pengecualian yang disengaja dan eksplisit terhadap aturan umum bahwa proses tidak berbagi memori; sistem operasi dapat diperintahkan secara khusus untuk memetakan wilayah memori fisik tertentu ke dalam ruang alamat virtual lebih dari satu proses secara bersamaan. Hal ini memungkinkan proses-proses tersebut berkomunikasi dengan cara membaca dan menulis secara langsung pada wilayah yang dibagikan secara eksplisit itu, meskipun tindakan ini menuntut disiplin sinkronisasi yang cermat—sama halnya dengan kebutuhan sinkronisasi pada memori yang secara inheren dibagikan antar-thread dalam satu proses, yang akan dibahas pada bagian berikutnya. Socket memungkinkan proses berkomunikasi melalui antarmuka bergaya jaringan, baik saat proses-proses tersebut berjalan pada mesin fisik yang sama persis maupun pada mesin yang sepenuhnya terpisah dan terhubung melalui jaringan nyata; mekanisme berbasis socket inilah yang menjadi cara utama bagi sebagian besar arsitektur microservices untuk memungkinkan berbagai proses yang sepenuhnya terpisah agar dapat saling berkomunikasi. Setiap mekanisme IPC ini melibatkan kernel sistem operasi sebagai perantara dalam kapasitas tertentu. Artinya, tanpa kecuali, komunikasi antar-proses membawa overhead nyata yang dapat diukur, berbeda dengan tindakan membaca variabel secara langsung yang pada dasarnya gratis dan instan—seperti saat satu thread membaca variabel yang telah ditulis oleh thread lain dalam proses yang sama ke dalam ruang memori bersama mereka. Beban tambahan (overhead) ini merupakan salah satu biaya praktis yang paling nyata dan signifikan akibat pemilihan arsitektur multi-proses dibandingkan multi-thread untuk perangkat lunak yang secara khusus dirancang dengan komponen-komponen yang perlu berkomunikasi serta berkoordinasi satu sama lain secara sering dan cepat. Isolasi Kesalahan: Mengapa Proses Lebih AmanKarena setiap proses memiliki ruang alamat yang sepenuhnya terpisah dan terlindungi, bug serius pada satu proses—seperti penulisan ke alamat memori yang tidak valid, penggunaan null pointer, atau kegagalan total (crash)—pada sebagian besar kasus akan terisolasi sepenuhnya di dalam proses spesifik tersebut. Sistem operasi biasanya akan menghentikan proses yang mengalami kegagalan saja, sementara proses lain yang berjalan di mesin yang sama tetap beroperasi secara normal tanpa terpengaruh sama sekali. Hal ini terjadi karena memori proses-proses lain tersebut sejak awal tidak dapat diakses oleh proses yang mengalami kegagalan, sehingga tidak mungkin mengalami kerusakan akibat masalah yang terjadi pada proses tersebut. Sebaliknya, thread berbagi ruang alamat tunggal milik proses induknya secara penuh. Artinya, bug yang cukup serius pada satu thread—misalnya merusak struktur data bersama yang juga diandalkan oleh thread lain atau menyebabkan kegagalan total pada proses—berpotensi besar melumpuhkan seluruh proses. Akibatnya, semua thread yang berjalan di dalamnya ikut berhenti, termasuk thread yang saat itu sedang melakukan tugas yang sama sekali tidak berkaitan dengan sumber masalah utamanya. Sifat isolasi kesalahan inilah yang menjadi alasan mengapa banyak sistem produksi yang mengutamakan keandalan dan waktu aktif (uptime) tinggi sengaja memilih arsitektur multi-proses. Pilihan ini diambil untuk komponen-komponen di mana isolasi kegagalan yang bersih antar-komponen dianggap lebih penting daripada keuntungan performa mentah dan kecepatan komunikasi yang sebenarnya bisa ditawarkan oleh desain multi-thread yang lebih terintegrasi erat. Browser web yang mungkin sedang kita gunakan untuk membaca artikel ini adalah contoh nyata dan populer dari penerapan pertimbangan desain tersebut: browser modern sengaja menjalankan setiap tab yang terbuka (atau, dalam arsitektur beberapa peramban, konten dari setiap situs web secara terpisah) dalam proses sistem operasi yang terpisah. Tujuannya adalah agar satu tab yang bermasalah atau mengalami kegagalan tidak menyebabkan seluruh aplikasi browser serta tab lain yang sedang terbuka ikut lumpuh. Konkurensi vs. ParalelismeKonkurensi mengacu pada kemampuan sistem untuk membuat kemajuan yang berarti pada lebih dari satu tugas dalam rentang waktu tertentu—melalui kombinasi penyisipan (interleaving) dan perpindahan (switching) antar-tugas—tanpa mengharuskan lebih dari satu tugas dijalankan pada saat yang persis sama secara fisik. CPU single-core yang menjalankan sistem operasi dengan beberapa thread aktif merupakan sistem yang benar-benar konkuren, yaitu melalui teknik pembagian waktu dan context-switching yang cepat, semua thread tersebut membuat kemajuan nyata seiring berjalannya waktu, meskipun pada satu saat yang tepat, hanya satu thread yang benar-benar sedang dieksekusi pada inti fisik tunggal tersebut. Paralelisme secara khusus mengacu pada kondisi di mana lebih dari satu tugas benar-benar dieksekusi pada saat yang persis sama secara fisik. Hal ini mutlak memerlukan lebih dari satu unit pemrosesan fisik—baik berupa beberapa inti CPU maupun beberapa CPU yang sepenuhnya terpisah—agar tersedia dan aktif secara bersamaan. CPU quad-core yang menjalankan empat thread terpisah dan independen—dengan satu thread aktif dieksekusi pada masing-masing dari keempat inti yang tersedia pada saat yang persis sama—merupakan contoh paralelisme sejati, bukan sekadar konkurensi. Berdasarkan definisi tersebut, dapat disimpulkan bahwa konkurensi sama sekali tidak mengharuskan adanya paralelisme; mesin single-core pun bisa bersifat konkuren secara nyata dan bermakna dengan menyisipkan kemajuan berbagai tugas seiring waktu, meskipun mesin tersebut tidak akan pernah bisa mencapai paralelisme sejati karena pada dasarnya hanya memiliki satu inti yang tersedia untuk mengeksekusi tugas apa pun pada saat tertentu. Sebaliknya, paralelisme secara mutlak dan tak terelakkan memerlukan ketersediaan lebih dari satu inti pemrosesan. Perbedaan ini sangat penting untuk memahami manfaat kinerja praktis yang nyata (atau, dalam kasus tertentu, ketiadaan manfaat tersebut) yang dapat diberikan oleh multithreading maupun multiprocessing pada perangkat keras fisik. Masalah pada Thread: Race Condition dan SinkronisasiSifat memori bersama yang membuat multithreading begitu cepat dan efisien dalam hal komunikasi justru menjadi sumber langsung dari salah satu kategori bug yang paling sulit ditangani dalam software engineering, yaitu race condition. Race condition terjadi ketika dua thread atau lebih mengakses data bersama pada waktu yang praktis bersamaan, dan hasil akhir dari akses tersebut bergantung—dengan cara yang tidak dapat diprediksi, bergantung pada waktu eksekusi, dan sulit direproduksi secara konsisten—pada urutan persis saat scheduler sistem operasi menyelingi operasi tingkat rendah dari masing-masing thread tersebut pada saat itu. Sebuah contoh yang tampak sederhana dapat memperjelas masalah dasarnya, yaitu bayangkan dua thread yang sama-sama perlu menaikkan nilai sebuah variabel counter bersama. Pada tingkat perangkat keras yang sebenarnya, operasi menaikkan nilai variabel bukanlah operasi tunggal yang tak terbagi (atomic); operasi ini sesungguhnya melibatkan tiga langkah terpisah, yaitu membaca nilai counter saat ini dari memori, menambahkan angka satu pada nilai tersebut, dan kemudian menuliskan kembali hasil yang telah dinaikkan itu ke memori. Jika Thread A membaca nilai counter saat ini (misalnya, 5), dan sebelum Thread A sempat menuliskan kembali hasil kenaikan nilainya (yaitu 6) ke memori, scheduler beralih dan membiarkan Thread B membaca counter tersebut (yang masih bernilai 5, karena hasil kenaikan dari Thread A belum tertulis kembali), maka kedua thread tersebut akan secara independen menghitung dan menuliskan kembali hasil yang sama—yaitu 6—padahal secara logis, dua kali operasi kenaikan nilai seharusnya menghasilkan nilai akhir 7. Akibatnya, salah satu operasi kenaikan nilai tersebut hilang begitu saja tanpa jejak, tanpa ada satu pun thread yang memunculkan error atau memberikan indikasi bahwa telah terjadi kesalahan. Untuk mencegah jenis bug spesifik ini—serta kelompok masalah terkait yang lebih luas—secara tepat, diperlukan penggunaan mekanisme sinkronisasi yang secara eksplisit mengatur bagaimana dan kapan berbagai thread diizinkan mengakses data bersama. Sebuah mutex (singkatan dari mutual exclusion) hanya mengizinkan satu thread untuk mengakses bagian data bersama yang terlindungi pada satu waktu; hal ini memaksa thread lain yang juga ingin mengakses data tersebut untuk benar-benar menunggu giliran hingga thread yang sedang memegang akses selesai dan melepaskan lock tersebut. Semaphore memperluas konsep dasar ini dengan memungkinkan sejumlah thread yang dapat dikonfigurasi (bukan sekadar satu thread saja) untuk mengakses sumber daya bersama secara bersamaan. Hal ini berguna, misalnya, untuk membatasi jumlah thread yang boleh mengakses sumber daya yang kapasitas penggunaannya terbatas—yang hanya mampu melayani sejumlah pengguna secara konkuren, namun tidak bisa melayani jumlah pengguna yang tak terbatas. Condition variable memungkinkan thread untuk menunggu secara efisien dan tepat hingga kondisi tertentu terpenuhi—seperti menunggu tersedianya item baru dalam antrean bersama untuk diproses—tanpa harus melakukan looping yang sia-sia dan memboroskan siklus CPU selama masa penantian tersebut. Mekanisme sinkronisasi ini benar-benar dapat mengatasi masalah kebenaran program jika diterapkan secara cermat dan konsisten, namun mekanisme ini juga membawa tingkat kompleksitas tersendiri yang nyata, yaitu kode yang memerlukan sinkronisasi cermat dan tepat jauh lebih sulit untuk ditulis dengan benar, sering kali jauh lebih sulit untuk diuji secara menyeluruh (karena race condition sering kali hanya muncul pada kondisi waktu yang sangat spesifik, langka, dan sulit direproduksi secara konsisten, sehingga mungkin tidak terdeteksi sama sekali dalam pengujian biasa), serta menimbulkan risiko nyata berupa jenis bug lain yang disebut deadlock. Deadlock adalah situasi di mana dua thread atau lebih saling menunggu—tanpa batas waktu—untuk mendapatkan lock yang justru sedang ditunggu oleh thread lain yang terlibat dalam deadlock yang sama, sehingga tidak ada satu pun thread yang dapat melanjutkan prosesnya. Global Interpreter Lock: Sebuah Komplikasi di Dunia NyataSebuah komplikasi nyata yang sangat penting—dan patut dipahami secara khusus karena sering kali menjebak developer yang beralih dari bahasa pemrograman lain yang tidak memiliki batasan serupa—adalah Global Interpreter Lock (GIL). Mekanisme ini terdapat dalam implementasi standar bahasa pemrograman Python yang paling banyak digunakan (serta hadir dalam bentuk yang agak mirip pada implementasi standar MRI untuk Ruby). GIL adalah sebuah mutex tunggal dan spesifik—bagian internal dari implementasi runtime bahasa itu sendiri—yang memastikan hanya satu thread dalam suatu proses Python yang dapat secara aktif mengeksekusi bytecode Python pada satu waktu tertentu. Hal ini berlaku terlepas dari berapa banyak inti CPU fisik yang tersedia dan menganggur (siap digunakan) pada mesin tersebut. Artinya, khususnya untuk Python standar, pembuatan banyak thread dalam satu proses tidak menghasilkan paralelisme sejati untuk tugas-tugas yang intensif secara komputasi atau berat bagi CPU—berbeda dengan anggapan awal yang wajar dimiliki oleh banyak pemula. Bahkan pada mesin dengan delapan, enam belas, atau lebih inti fisik yang tersedia, program Python yang terikat pada kinerja CPU (CPU-bound) dan menggunakan banyak thread akan tetap hanya menjalankan satu thread untuk mengeksekusi kode Python pada satu waktu karena adanya GIL, sementara thread lainnya harus menunggu giliran. Thread dalam Python tetap sangat berguna dan tidak menjadi sia-sia sama sekali akibat keberadaan GIL, khususnya untuk pekerjaan yang bersifat I/O-bound—yaitu tugas yang sebagian besar waktunya dihabiskan hanya untuk menunggu selesainya operasi eksternal yang relatif lambat seperti permintaan jaringan, kueri database, atau pembacaan disk, alih-alih benar-benar membutuhkan komputasi CPU yang intensif dan berkelanjutan. Hal ini dimungkinkan karena GIL secara khusus dan sengaja dilepaskan oleh runtime Python itu sendiri setiap kali sebuah thread terblokir saat menunggu operasi I/O semacam itu, sehingga memungkinkan thread lain dalam proses yang sama untuk melakukan pekerjaan yang nyata dan bermanfaat tepat selama jeda waktu menunggu tersebut. Sebaliknya, untuk pekerjaan yang benar-benar bersifat CPU-bound dalam Python standar, pencapaian paralelisme yang sesungguhnya di berbagai inti prosesor justru mengharuskan penggunaan proses yang terpisah dan sepenuhnya berbeda (melalui modul multiprocessing Python, yang sepenuhnya menghindari batasan GIL karena GIL—sebagai detail implementasi internal dari satu instans runtime bahasa—tidak berlaku lintas proses tingkat sistem operasi yang benar-benar terpisah, di mana setiap proses menjalankan instans interpreter Python-nya sendiri secara independen dan memiliki GIL-nya sendiri yang terpisah pula), bukan menggunakan banyak thread dalam satu proses tunggal. Komplikasi nyata dan spesifik ini merupakan ilustrasi konkret yang sangat baik mengenai alasan mengapa pemahaman mendalam tentang mekanisme dasar proses dan thread di tingkat sistem operasi—alih-alih sekadar menghafal klaim umum dan abstrak seperti "thread memungkinkan paralelisme" tanpa penjelasan lebih lanjut—benar-benar penting untuk pengambilan keputusan teknis yang tepat dan berdasar pada pemahaman yang kuat dalam praktik nyata. Multiprocessing vs. Multithreading dalam PraktikSecara historis, server web yang menangani banyak koneksi klien secara bersamaan telah dibangun menggunakan beragam model konkurensi yang sangat bervariasi, justru karena adanya pertimbangan-pertimbangan tersebut. Beberapa server web membuat proses yang sepenuhnya terpisah untuk setiap permintaan atau koneksi yang masuk; mereka sengaja memprioritaskan isolasi kegagalan yang kuat (sehingga satu penangan permintaan yang mengalami kegagalan tidak akan melumpuhkan seluruh server) dengan konsekuensi yang disadari berupa penggunaan memori yang lebih besar dan overhead komunikasi yang menyertai penggunaan proses terpisah yang utuh. Server lain menggunakan kumpulan thread pekerja dalam satu proses bersama, yang memprioritaskan overhead memori lebih rendah serta context switching yang lebih cepat dan hemat sumber daya di antara banyak koneksi aktif, sembari secara cermat mengelola risiko sinkronisasi yang tak terelakkan akibat berbagi akses memori di antara semua thread tersebut. Ada pula yang menggunakan model yang secara fundamental berbeda, yaitu model asinkron single-threaded berbasis event (bukan berbasis thread), yaitu arsitektur dasar yang menggerakkan platform seperti Node.js. Model ini menghindari masalah race condition pada memori bersama dengan cara yang sama sekali berbeda: dengan tidak pernah menjalankan lebih dari satu bagian logika aplikasi pada saat yang persis sama sejak awal. Meskipun demikian, model ini tetap mencapai konkurensi keseluruhan yang kuat untuk beban kerja yang bergantung pada I/O (I/O-bound) melalui operasi non-blocking berbasis callback, bukan melalui eksekusi yang benar-benar paralel ataupun multi-threaded secara harfiah. Beban kerja pemrosesan data dan komputasi ilmiah—yang secara karakteristik lebih sering dibatasi oleh kinerja CPU (CPU-bound) daripada I/O (I/O-bound)—kerap kali mengandalkan multiprosesing alih-alih multithreading. Pendekatan ini dipilih demi mencapai eksekusi paralel yang sesungguhnya pada seluruh inti fisik yang tersedia di mesin tersebut, sekaligus menghindari sepenuhnya batasan seperti GIL (Global Interpreter Lock) pada Python. Hal ini dilakukan dengan menerima konsekuensi berupa kebutuhan untuk melakukan serialisasi dan transfer data secara eksplisit di antara proses-proses terpisah yang terbentuk, mengingat memori bersama yang bersifat langsung dan sejati tidak tersedia secara bawaan dalam arsitektur tersebut. Aplikasi desktop sangat umum menggunakan multithreading untuk menjaga responsivitas antarmuka penggunanya: operasi latar belakang yang memakan waktu lama (seperti memuat file berukuran besar atau melakukan komputasi kompleks yang panjang) sengaja dialihkan untuk dijalankan pada thread pekerja yang benar-benar terpisah. Tujuannya adalah agar thread UI utama aplikasi tetap bebas dan siap merespons input pengguna dengan sigap serta memperbarui tampilan layar secara mulus. Dengan demikian, pengalaman menjengkelkan—di mana antarmuka tampak membeku atau sama sekali tidak merespons akibat adanya proses lain yang tidak terkait namun memakan waktu lama—dapat dihindari. Advertisement:
Jadi, perbedaan antara process dan thread bukan sekadar trivia akademis yang abstrak mengenai sistem operasi; ini adalah konsep yang benar-benar mendasar. Konsekuensi praktisnya secara langsung dan nyata membentuk cara software dibangun, seberapa andal kinerjanya dalam kondisi produksi yang sesungguhnya, serta seberapa efisien pemanfaatan sumber daya perangkat keras yang terbatas—tempat software tersebut pada akhirnya dijalankan.
Artikel Terkait:
|