เมื่อเร็ว ๆ นี้ฉันถามคำถามโดยมีหัวข้อว่า"เธรด Malloc ปลอดภัยหรือไม่" และในนั้นฉันถามว่า "malloc re-entrant หรือไม่"
ฉันรู้สึกว่าผู้เข้าใหม่ทั้งหมดปลอดภัยต่อเธรด
สมมติฐานนี้ผิดหรือไม่?
เมื่อเร็ว ๆ นี้ฉันถามคำถามโดยมีหัวข้อว่า"เธรด Malloc ปลอดภัยหรือไม่" และในนั้นฉันถามว่า "malloc re-entrant หรือไม่"
ฉันรู้สึกว่าผู้เข้าใหม่ทั้งหมดปลอดภัยต่อเธรด
สมมติฐานนี้ผิดหรือไม่?
คำตอบ:
ฟังก์ชัน Re-entrant ไม่ขึ้นอยู่กับตัวแปรส่วนกลางที่เปิดเผยในส่วนหัวไลบรารี C .. ใช้ strtok () vs strtok_r () เช่นใน C
ฟังก์ชันบางอย่างจำเป็นต้องมีที่สำหรับจัดเก็บ 'งานระหว่างดำเนินการ' ฟังก์ชันการเข้าใหม่อนุญาตให้คุณระบุตัวชี้นี้ภายในที่เก็บข้อมูลของเธรดเองไม่ใช่ในส่วนกลาง เนื่องจากที่เก็บข้อมูลนี้เป็นเอกสิทธิ์ของฟังก์ชันการโทรจึงสามารถถูกขัดจังหวะและป้อนใหม่ได้ (re-entrant) และเนื่องจากในกรณีส่วนใหญ่การยกเว้นซึ่งกันและกันนอกเหนือจากสิ่งที่การใช้ฟังก์ชันไม่จำเป็นสำหรับการใช้งานจึงมักถูกพิจารณาว่าเป็นปลอดภัยด้าย อย่างไรก็ตามสิ่งนี้ไม่ได้รับประกันตามคำจำกัดความ
อย่างไรก็ตาม errno เป็นกรณีที่แตกต่างกันเล็กน้อยในระบบ POSIX (และมีแนวโน้มที่จะเป็น oddball ในคำอธิบายว่าทั้งหมดนี้ทำงานอย่างไร) :)
ในระยะสั้น reentrant มักจะหมายถึง thread safe (เช่นเดียวกับใน "use the reentrant version of that function if you're using threads") แต่ thread safe ไม่ได้หมายถึง re-entrant (หรือ reverse) เสมอไป เมื่อคุณกำลังดูเรื่องความปลอดภัยของเธรดการทำงานพร้อมกันคือสิ่งที่คุณต้องนึกถึง หากคุณต้องระบุวิธีการล็อกและการยกเว้นร่วมกันเพื่อใช้ฟังก์ชันฟังก์ชันนั้นจะไม่ปลอดภัยต่อเธรดโดยเนื้อแท้
แต่ไม่จำเป็นต้องตรวจสอบฟังก์ชันทั้งหมดสำหรับอย่างใดอย่างหนึ่ง malloc()
ไม่จำเป็นต้อง reentrant มันไม่ได้ขึ้นอยู่กับสิ่งใดนอกขอบเขตของจุดเริ่มต้นสำหรับเธรดใด ๆ ที่กำหนด (และเป็นเธรดที่ปลอดภัย)
ฟังก์ชันที่ส่งคืนค่าที่จัดสรรแบบคงที่จะไม่ปลอดภัยต่อเธรดหากไม่มีการใช้ mutex, futex หรือกลไกการล็อกอะตอมอื่น ๆ กระนั้นพวกเขาก็ไม่จำเป็นต้องกลับมาอีกครั้งหากไม่ถูกขัดจังหวะ
กล่าวคือ:
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;
}
ดังที่คุณเห็นการมีเธรดหลายเธรดใช้โดยไม่มีการล็อกบางประเภทอาจเป็นหายนะ .. แต่ก็ไม่มีจุดประสงค์ที่จะเข้าใหม่ คุณจะพบว่าเมื่อหน่วยความจำที่จัดสรรแบบไดนามิกเป็นสิ่งต้องห้ามบนแพลตฟอร์มฝังตัวบางตัว
ในการเขียนโปรแกรมที่ใช้งานได้อย่างหมดจด reentrant มักไม่ได้หมายความว่าเธรดปลอดภัย แต่จะขึ้นอยู่กับลักษณะการทำงานของฟังก์ชันที่กำหนดหรือไม่ระบุชื่อที่ส่งผ่านไปยังจุดเข้าใช้งานฟังก์ชันการเรียกซ้ำ ฯลฯ
วิธีที่ดีกว่าในการใส่ "เธรดปลอดภัย" คือปลอดภัยสำหรับการเข้าถึงพร้อมกันซึ่งแสดงให้เห็นถึงความจำเป็นได้ดีกว่า
TL; DR: ฟังก์ชันสามารถ reentrant, thread-safe ทั้งสองอย่างหรือไม่ก็ได้
บทความ Wikipedia เพื่อความปลอดภัยของเธรดและการจัดระเบียบใหม่เป็นสิ่งที่ควรค่าแก่การอ่าน นี่คือการอ้างอิงบางส่วน:
ฟังก์ชันจะปลอดภัยต่อเธรดหาก:
จัดการเฉพาะโครงสร้างข้อมูลที่แชร์ในลักษณะที่รับประกันการดำเนินการอย่างปลอดภัยโดยหลายเธรดในเวลาเดียวกัน
ฟังก์ชันreentrantถ้า:
สามารถถูกขัดจังหวะเมื่อใดก็ได้ในระหว่างการดำเนินการและเรียกอีกครั้งอย่างปลอดภัย ("ป้อนใหม่") ก่อนที่การเรียกใช้ก่อนหน้าจะเสร็จสมบูรณ์
จากตัวอย่างของการย้อนกลับที่เป็นไปได้ Wikipedia จะให้ตัวอย่างของฟังก์ชันที่ออกแบบมาเพื่อเรียกใช้โดยการขัดจังหวะระบบ: สมมติว่ามันทำงานอยู่แล้วเมื่อมีการขัดจังหวะอื่นเกิดขึ้น แต่อย่าคิดว่าคุณปลอดภัยเพียงเพราะคุณไม่ได้เขียนโค้ดด้วยการขัดจังหวะระบบคุณอาจมีปัญหาในการย้อนกลับในโปรแกรมเธรดเดียวหากคุณใช้การเรียกกลับหรือฟังก์ชันเรียกซ้ำ
กุญแจสำคัญในการหลีกเลี่ยงความสับสนคือ reentrant หมายถึงการดำเนินการเธรดเดียวเท่านั้น เป็นแนวคิดตั้งแต่สมัยที่ยังไม่มีระบบปฏิบัติการมัลติทาสก์
ตัวอย่าง
(แก้ไขเล็กน้อยจากบทความ Wikipedia)
ตัวอย่างที่ 1: not thread-safe, not 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;
}
ตัวอย่างที่ 2: thread-safe, not 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;
}
ตัวอย่างที่ 3: not 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;
}
ตัวอย่างที่ 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
โทรswap()
หลังจากนั้นt
จะถูกแทนที่ซึ่งนำไปสู่ผลลัพธ์ที่ไม่คาดคิด
swap(5, 6)
ถูกขัดจังหวะโดยswap(1, 2)
. หลังจากt=*x
, และs=t_original
t=5
ตอนนี้หลังจากหยุดชะงักs=5
และt=1
. อย่างไรก็ตามก่อนที่สองผลตอบแทนก็จะคืนค่าบริบททำให้swap
t=s=5
ตอนนี้เรากลับไปเป็นครั้งแรกswap
ที่มีและดำเนินการต่อหลังt=5 and s=t_original
t=*x
ดังนั้นฟังก์ชันจึงดูเหมือนว่าจะกลับเข้ามาใหม่ โปรดจำไว้ว่าทุกการโทรจะได้รับสำเนาของการs
จัดสรรในสแตก
มันขึ้นอยู่กับคำจำกัดความ ตัวอย่างเช่นQt ใช้สิ่งต่อไปนี้:
สามารถเรียกใช้ฟังก์ชัน thread-safe * พร้อมกันได้จากหลายเธรดแม้ว่าการเรียกใช้จะใช้ข้อมูลที่แชร์เนื่องจากการอ้างอิงทั้งหมดไปยังข้อมูลที่แชร์จะถูกทำให้เป็นอนุกรม
reentrantฟังก์ชั่นยังสามารถเรียกว่าพร้อมกันจากหลายหัวข้อ แต่ถ้าแต่ละภาวนาใช้ข้อมูลของตัวเอง
ดังนั้นฟังก์ชันthread-safeจึง reentrant เสมอ แต่ฟังก์ชันreentrantไม่ปลอดภัยต่อเธรดเสมอไป
โดยขยายชั้นเรียนกล่าวจะreentrantถ้าฟังก์ชั่นสมาชิกสามารถเรียกได้อย่างปลอดภัยจากหลายหัวข้อตราบใดที่แต่ละหัวข้อใช้อินสแตนซ์ที่แตกต่างกันของการเรียน คลาสนี้ปลอดภัยต่อเธรดหากฟังก์ชันสมาชิกสามารถถูกเรียกได้อย่างปลอดภัยจากหลายเธรดแม้ว่าเธรดทั้งหมดจะใช้อินสแตนซ์ของคลาสเดียวกันก็ตาม
แต่พวกเขายังเตือน:
หมายเหตุ:คำศัพท์ในโดเมนมัลติเธรดไม่ได้เป็นมาตรฐานทั้งหมด POSIX ใช้คำจำกัดความของ reentrant และ thread-safe ซึ่งแตกต่างกันบ้างสำหรับ C APIs เมื่อใช้ไลบรารีคลาส C ++ เชิงวัตถุอื่นกับ Qt ต้องแน่ใจว่าเข้าใจนิยาม