Lock Convoy Mengubah Critical Section Singkat Menjadi Antrean Panjang
Mutex dapat melindungi critical section yang sangat kecil dan tetap menjadi pusat masalah latensi yang besar. Kode di dalam lock mungkin hanya memerlukan beberapa mikrodetik pada kondisi normal, tetapi satu holder yang tertunda dapat membuat beberapa thread menumpuk di belakangnya. Setelah antrean terbentuk, lock dapat terus berada dalam kondisi contention ketika ownership berpindah dari satu thread yang menunggu ke thread berikutnya.
Pola itu disebut lock convoy. Biayanya tidak berhenti pada operasi lambat yang memulai antrean. Aktivitas scheduler, wakeup, perpindahan cache, context switch, dan handoff yang terserialisasi dapat menahan throughput di bawah tingkat sebelum contention terbentuk.
Stall singkat dapat membentuk antrean yang bertahan
Pertimbangkan mutex bersama yang melindungi struktur kecil di memori:
lock(m)
update_shared_state()
unlock(m)Jika update_shared_state() biasanya cepat, contention dapat tetap rendah. Sekarang anggap owner saat ini terkena deschedule ketika masih memegang m, mengalami page fault, atau masuk ke jalur yang tidak terduga lambat. Thread lain datang lalu terblokir.
Ketika owner akhirnya melepas mutex, satu waiter berjalan. Selama handoff tersebut, pekerjaan baru dapat terus datang. Jika arrival terjadi setidaknya secepat waiter selesai, mutex tidak pernah kembali ke kondisi tanpa contention. Stall sementara telah mengubah bentuk eksekusi dari arrival yang sebagian besar independen menjadi antrean yang dilayani satu holder pada satu waktu.
Delay awal dapat hilang sementara convoy tetap bertahan.
Jalur lock berubah saat terjadi contention
Mutex tanpa contention sering dapat diambil melalui atomic fast path yang singkat. Akuisisi dengan contention dapat memerlukan mekanisme yang jauh lebih banyak. Bergantung pada runtime dan sistem operasi, thread dapat melakukan spin, masuk ke wait berbantuan kernel, park, bangun kemudian, bersaing untuk mendapat waktu eksekusi, dan menyentuh state sinkronisasi yang baru saja dimodifikasi core lain.
Operasi tersebut menambah overhead di sekitar pekerjaan yang dilindungi. Critical section belum tentu membesar, tetapi service time efektif untuk setiap akuisisi yang mengantre kini mencakup biaya koordinasi.
Perbedaan ini penting saat profiling. Fungsi di dalam critical section dapat terlihat murah secara terpisah, sementara caller tetap menghabiskan wall-clock time yang besar untuk menunggu lock dan melewati jalur handoff.
Fairness dapat mempertahankan convoy
Fairness pada lock adalah pilihan kebijakan, bukan peningkatan performa tanpa syarat. Mutex yang sangat fair cenderung memberikan ownership kepada waiter yang sudah mengantre daripada membiarkan thread yang baru datang langsung mengambil lock.
Kebijakan tersebut dapat membatasi starvation, tetapi juga dapat mempertahankan antrean. Owner berikutnya mungkin harus dibangunkan dan dijadwalkan sebelum pekerjaan berguna berlanjut. Pada saat yang sama, thread yang sudah berjalan di CPU bisa saja memasuki critical section dengan overhead scheduling lebih rendah jika barging diizinkan.
Mutex unfair atau adaptive kadang dapat memutus convoy dengan membiarkan thread yang sedang berjalan mengambil lock selama jendela handoff. Konsekuensinya, barging berulang dapat menunda waiter yang sudah ada. Kebijakan yang tepat bergantung pada target latensi, batas starvation, perilaku runtime, dan bentuk workload.
Tekanan antrean lebih penting daripada durasi lock saja
Aturan review yang umum adalah menjaga critical section tetap singkat. Itu berguna, tetapi durasi saja tidak menentukan contention. Arrival rate dan variasi juga berpengaruh.
Mutex yang dipegang selama 20 mikrodetik hanya dapat melayani satu critical section pada satu waktu. Ketika demand mendekati kapasitas serial tersebut, perubahan kecil pada service time atau burst arrival dapat menghasilkan waktu tunggu yang jauh lebih besar. Hold yang jarang tetapi panjang sangat mengganggu karena membentuk backlog yang harus dikuras oleh holder berikutnya.
Pengukuran sebaiknya mencakup lebih dari average hold time. Sinyal yang berguna meliputi:
- distribusi acquisition wait time;
- distribusi hold time, termasuk persentil tinggi;
- jumlah contention atau blocked acquisition;
- jumlah thread runnable dan blocked;
- tingkat context switch;
- throughput ketika concurrency meningkat.
Nilai rata-rata dapat menyembunyikan peristiwa yang memicu antrean.
Jangan memegang mutex saat menjalankan pekerjaan yang sulit diprediksi
Pencegahan terkuat sering bersifat struktural. Critical section sebaiknya hanya memuat pekerjaan yang memang memerlukan mutual exclusion. Blocking I/O, network call, operasi filesystem, jalur allocation dengan latensi tidak pasti, callback, dan komputasi yang tidak terkait dapat mengubah lock hold yang terbatas menjadi variabel.
Alih-alih bentuk ini:
lock(m)
read shared state
call slow dependency
write shared state
unlock(m)pilih desain yang menyalin atau mereservasi state yang diperlukan di bawah lock, menjalankan pekerjaan independen di luar lock, lalu masuk kembali hanya saat update yang dilindungi diperlukan. Transformasi ini hanya valid jika transisi state tetap benar selama interval tanpa lock; version check, retry, atau model sinkronisasi lain mungkin diperlukan.
Mengurangi scope lock tidak boleh menukar contention dengan race.
Partisi menghapus serialisasi yang tidak diperlukan
Jika operasi yang tidak saling terkait berbagi satu mutex, sharding state yang dilindungi dapat mengurangi jumlah thread yang bersaing untuk token ownership yang sama. Lock per-key, per-bucket, atau per-resource merupakan bentuk umum pendekatan ini.
Partisi membawa batasan tersendiri. Operasi yang melintasi beberapa partisi memerlukan aturan ordering atau mekanisme koordinasi lain agar tidak terjadi deadlock. Hot key masih dapat memusatkan contention pada satu shard. Jumlah lock yang lebih banyak juga menambah kompleksitas lifecycle dan diagnosis.
Manfaatnya berasal dari penyesuaian scope sinkronisasi dengan consistency boundary yang sebenarnya, bukan menempatkan pekerjaan independen di belakang satu gate global.
Lebih banyak worker thread dapat memperburuk antrean
Menambah thread tidak meningkatkan kapasitas critical section yang terserialisasi. Setelah lock jenuh, worker tambahan dapat berubah menjadi waiter tambahan. Mereka memakai perhatian scheduler dan dapat meningkatkan traffic cache tanpa menambah jumlah operasi terlindungi yang selesai.
Karena itu, pengujian throughput sebaiknya menyapu beberapa tingkat concurrency, bukan hanya satu jumlah worker. Sistem dapat membaik sampai titik tertentu, mendatar ketika mutex jenuh, lalu menurun ketika overhead koordinasi meningkat.
Kurva tersebut lebih informatif daripada satu hasil benchmark karena memperlihatkan transisi dari paralelisme yang berguna menuju serialisasi yang mengantre.
Perbaiki titik serialisasi sebelum menyetel handoff
Spin count, pengaturan fairness, mode adaptive mutex, dan parameter scheduler dapat mengubah biaya contention. Semua itu merupakan kontrol sekunder. Jika workload terus meminta pekerjaan serial lebih banyak daripada yang dapat dilayani critical section, tuning lock tidak dapat menciptakan kapasitas paralel di dalam section tersebut.
Mulailah dari ownership boundary: keluarkan pekerjaan dengan latensi tidak pasti, persingkat transisi state yang dilindungi, partisi state independen, kurangi akuisisi yang tidak perlu, atau ganti desain shared mutable jika model lain lebih sesuai. Setelah itu, evaluasi kebijakan lock memakai pengukuran dari runtime dan workload target.
Lock convoy adalah masalah antrean yang muncul melalui sinkronisasi. Sudut pandang tersebut memindahkan perhatian dari instruksi mutex itu sendiri menuju tekanan arrival, variasi service time, scheduling, dan jumlah pekerjaan yang dipaksa melewati satu jalur serial.