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?
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?
Jawaban:
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:
itu hanya memanipulasi struktur data bersama dengan cara yang menjamin eksekusi yang aman oleh banyak utas secara bersamaan.
Suatu fungsi reentrant jika:
itu dapat diinterupsi pada titik mana pun selama eksekusi dan kemudian dipanggil kembali dengan aman ("masuk kembali") sebelum pemanggilan sebelumnya menyelesaikan eksekusi.
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.
Kunci untuk menghindari kebingungan adalah reentrant merujuk ke hanya satu thread yang dieksekusi. Ini adalah konsep sejak tidak ada sistem operasi multitasking.
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;
}
t = *x
, menelepon swap()
, maka t
akan diganti, yang mengarah ke hasil yang tidak diharapkan.
swap(5, 6)
diinterupsi oleh swap(1, 2)
. Setelah t=*x
, s=t_original
dan t=5
. Sekarang, setelah gangguan, s=5
dan t=1
. Namun, sebelum swap
pengembalian kedua itu akan mengembalikan konteks, membuat t=s=5
. Sekarang, kita kembali ke yang pertama swap
dengan t=5 and s=t_original
dan berlanjut setelah t=*x
. Jadi, fungsinya memang tampak sebagai peserta ulang. Ingatlah bahwa setiap panggilan mendapatkan salinannya sendiri dari yang s
dialokasikan di tumpukan.
Itu tergantung definisi. Misalnya Qt menggunakan yang berikut:
Fungsi thread-safe * dapat dipanggil secara bersamaan dari beberapa utas, meskipun pemanggilan menggunakan data bersama, karena semua referensi ke data bersama dibuat berseri.
Sebuah reentrant fungsi juga dapat disebut secara bersamaan dari beberapa thread, tetapi hanya jika setiap permintaan menggunakan data sendiri.
Karenanya, fungsi thread-safe selalu reentrant, tetapi fungsi reentrant tidak selalu thread-safe.
Dengan ekstensi, sebuah kelas dikatakan reentrant jika fungsi anggotanya dapat dipanggil dengan aman dari beberapa thread, selama setiap thread menggunakan instance kelas yang berbeda. Kelas ini aman untuk thread jika fungsi anggotanya dapat dipanggil dengan aman dari beberapa thread, bahkan jika semua thread menggunakan instance kelas yang sama.
tetapi mereka juga memperingatkan:
Catatan: Terminologi di domain multithreading tidak sepenuhnya standar. POSIX menggunakan definisi reentrant dan thread-safe yang agak berbeda untuk C API-nya. Saat menggunakan pustaka kelas C ++ berorientasi objek lainnya dengan Qt, pastikan definisinya dipahami.