Masalahnya adalah fork() hanya menyalin utas panggilan, dan setiap mutex yang disimpan di utas anak akan selamanya dikunci di anak bercabang. Solusi pthread adalah pthread_atfork()
penangan. Idenya adalah Anda dapat mendaftarkan 3 penangan:satu prefork, satu penangan induk, dan satu penangan anak. Ketika fork()
terjadi prefork dipanggil sebelum fork dan diharapkan untuk mendapatkan semua mutex aplikasi. Baik orang tua dan anak harus melepaskan semua mutex masing-masing dalam proses orang tua dan anak.
Ini bukan akhir dari cerita sekalipun! Perpustakaan memanggil pthread_atfork
untuk mendaftarkan penangan untuk mutex khusus perpustakaan, misalnya Libc melakukan ini. Ini adalah hal yang baik:aplikasi tidak mungkin mengetahui tentang mutex yang dipegang oleh perpustakaan pihak ke-3, jadi setiap perpustakaan harus memanggil pthread_atfork
untuk memastikan mutexnya sendiri dibersihkan jika terjadi fork()
.
Masalahnya adalah urutan yang pthread_atfork
penangan dipanggil untuk perpustakaan yang tidak terkait tidak ditentukan (tergantung pada urutan perpustakaan dimuat oleh program). Jadi ini berarti secara teknis kebuntuan bisa terjadi di dalam prefork handler karena kondisi balapan.
Misalnya, pertimbangkan urutan ini:
- Thread T1 memanggil
fork()
- penangan prefork libc dipanggil di T1 (mis. T1 sekarang memegang semua kunci libc)
- Selanjutnya, di Utas T2, perpustakaan pihak ke-3 A memperoleh mutex AM-nya sendiri, lalu melakukan panggilan libc yang memerlukan mutex. Ini diblokir, karena libc mutex dipegang oleh T1.
- Thread T1 menjalankan prefork handler untuk library A, yang memblokir menunggu untuk mendapatkan AM, yang dipegang oleh T2.
Ada kebuntuan Anda dan tidak terkait dengan mutex atau kode Anda sendiri.
Ini benar-benar terjadi pada proyek yang pernah saya kerjakan. Nasihat yang saya temukan saat itu adalah memilih garpu atau utas tetapi tidak keduanya. Tapi untuk beberapa aplikasi itu mungkin tidak praktis.
Aman untuk melakukan fork dalam program multithread selama Anda sangat hati-hati dengan kode antara fork dan exec. Anda hanya dapat membuat panggilan sistem masuk kembali (alias asynchronous-safe) dalam rentang itu. Secara teori, Anda tidak diizinkan untuk malloc atau gratis di sana, meskipun dalam praktiknya pengalokasi Linux default aman, dan perpustakaan Linux mengandalkannya Hasil akhirnya adalah Anda harus gunakan pengalokasi default.