Baru-baru ini, saya mengajukan pertanyaan, dengan judul "Apakah malloc thread aman?" , dan di dalamnya saya bertanya, "Apakah malloc re-entrant?"
Saya mendapat kesan bahwa semua peserta kembali aman dari benang.
Apakah anggapan ini salah?
sumber
Baru-baru ini, saya mengajukan pertanyaan, dengan judul "Apakah malloc thread aman?" , dan di dalamnya saya bertanya, "Apakah malloc re-entrant?"
Saya mendapat kesan bahwa semua peserta kembali aman dari benang.
Apakah anggapan ini salah?
Fungsi re-entrant tidak bergantung pada variabel global yang diekspos di header C library .. ambil strtok () vs strtok_r () misalnya di C.
Beberapa fungsi memerlukan tempat untuk menyimpan 'pekerjaan yang sedang berlangsung', fungsi re-entrant memungkinkan Anda menentukan penunjuk ini dalam penyimpanan thread itu sendiri, bukan secara global. Karena penyimpanan ini eksklusif untuk fungsi pemanggilan, itu dapat diinterupsi dan dimasukkan kembali (re-entrant) dan karena dalam banyak kasus pengecualian timbal balik di luar apa yang tidak diimplementasikan oleh fungsi yang diperlukan agar ini berfungsi, mereka sering dianggap benang aman . Namun, ini tidak dijamin oleh definisi.
errno, bagaimanapun, adalah kasus yang sedikit berbeda pada sistem POSIX (dan cenderung menjadi aneh dalam penjelasan apa pun tentang bagaimana ini semua bekerja) :)
Singkatnya, reentrant sering kali berarti thread aman (seperti dalam "gunakan versi reentrant dari fungsi itu jika Anda menggunakan thread"), tetapi thread aman tidak selalu berarti peserta kembali (atau sebaliknya). Saat Anda melihat keamanan thread, konkurensi adalah hal yang perlu Anda pikirkan. Jika Anda harus menyediakan alat penguncian dan pengecualian timbal balik untuk menggunakan suatu fungsi, maka fungsi tersebut tidak secara inheren aman untuk thread.
Tapi, tidak semua fungsi perlu diperiksa dengan baik. malloc()
tidak perlu reentrant, tidak bergantung pada apa pun yang berada di luar cakupan titik masuk untuk thread tertentu (dan thread itu sendiri aman).
Fungsi yang mengembalikan nilai yang dialokasikan secara statis tidak aman untuk thread tanpa menggunakan mutex, futex, atau mekanisme penguncian atom lainnya. Namun, mereka tidak perlu masuk kembali jika mereka tidak akan diganggu.
yaitu:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Jadi, seperti yang Anda lihat, memiliki banyak utas yang menggunakannya tanpa semacam penguncian akan menjadi bencana .. tetapi tidak ada tujuan menjadi peserta kembali. Anda akan mengalami hal itu ketika memori yang dialokasikan secara dinamis adalah tabu pada beberapa platform tertanam.
Dalam pemrograman fungsional murni, reentrant sering tidak menyiratkan thread safe, itu akan bergantung pada perilaku fungsi yang ditentukan atau anonim yang diteruskan ke titik masuk fungsi, rekursi, dll.
Cara yang lebih baik untuk menempatkan 'thread safe' adalah aman untuk akses bersamaan , yang menggambarkan kebutuhan dengan lebih baik.
TL; DR: Suatu fungsi dapat berupa reentrant, thread-safe, keduanya atau tidak keduanya.
Artikel Wikipedia untuk keamanan thread dan reentrancy sangat layak untuk dibaca. Berikut beberapa kutipannya:
Suatu fungsi thread-safe jika:
Suatu fungsi reentrant jika:
Sebagai contoh kemungkinan masuk kembali, Wikipedia memberikan contoh fungsi yang dirancang untuk dipanggil oleh interupsi sistem: anggaplah sudah berjalan ketika interupsi lain terjadi. Tetapi jangan berpikir Anda aman hanya karena Anda tidak membuat kode dengan interupsi sistem: Anda dapat mengalami masalah masuk kembali dalam program single-threaded jika Anda menggunakan callback atau fungsi rekursif.
Contoh
(Sedikit diubah dari artikel Wikipedia)
Contoh 1: tidak thread-safe, bukan reentrant
/* As this function uses a non-const global variable without any precaution, it is neither reentrant nor thread-safe. */ int t; void swap(int *x, int *y) { t = *x; *x = *y; *y = t; }
Contoh 2: thread-safe, bukan reentrant
/* We use a thread local variable: the function is now thread-safe but still not reentrant (within the same thread). */ __thread int t; void swap(int *x, int *y) { t = *x; *x = *y; *y = t; }
Contoh 3: tidak thread-safe, reentrant
/* We save the global state in a local variable and we restore it at the end of the function. The function is now reentrant but it is not thread safe. */ int t; void swap(int *x, int *y) { int s; s = t; t = *x; *x = *y; *y = t; t = s; }
Contoh 4: thread-safe, reentrant
/* We use a local variable: the function is now thread-safe and reentrant, we have ascended to higher plane of existence. */ void swap(int *x, int *y) { int t; t = *x; *x = *y; *y = t; }
sumber
t = *x
, meneleponswap()
, makat
akan diganti, yang mengarah ke hasil yang tidak diharapkan.swap(5, 6)
diinterupsi olehswap(1, 2)
. Setelaht=*x
,s=t_original
dant=5
. Sekarang, setelah gangguan,s=5
dant=1
. Namun, sebelumswap
pengembalian kedua itu akan mengembalikan konteks, membuatt=s=5
. Sekarang, kita kembali ke yang pertamaswap
dengant=5 and s=t_original
dan berlanjut setelaht=*x
. Jadi, fungsinya memang tampak sebagai peserta ulang. Ingatlah bahwa setiap panggilan mendapatkan salinannya sendiri dari yangs
dialokasikan di tumpukan.Itu tergantung definisi. Misalnya Qt menggunakan yang berikut:
tetapi mereka juga memperingatkan:
sumber