Threadsafe เทียบกับ re-entrant


89

เมื่อเร็ว ๆ นี้ฉันถามคำถามโดยมีหัวข้อว่า"เธรด Malloc ปลอดภัยหรือไม่" และในนั้นฉันถามว่า "malloc re-entrant หรือไม่"

ฉันรู้สึกว่าผู้เข้าใหม่ทั้งหมดปลอดภัยต่อเธรด

สมมติฐานนี้ผิดหรือไม่?

คำตอบ:


42

ฟังก์ชัน 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 หรือกลไกการล็อกอะตอมอื่น ๆ กระนั้นพวกเขาก็ไม่จำเป็นต้องกลับมาอีกครั้งหากไม่ถูกขัดจังหวะ

กล่าวคือ:

ดังที่คุณเห็นการมีเธรดหลายเธรดใช้โดยไม่มีการล็อกบางประเภทอาจเป็นหายนะ .. แต่ก็ไม่มีจุดประสงค์ที่จะเข้าใหม่ คุณจะพบว่าเมื่อหน่วยความจำที่จัดสรรแบบไดนามิกเป็นสิ่งต้องห้ามบนแพลตฟอร์มฝังตัวบางตัว

ในการเขียนโปรแกรมที่ใช้งานได้อย่างหมดจด reentrant มักไม่ได้หมายความว่าเธรดปลอดภัย แต่จะขึ้นอยู่กับลักษณะการทำงานของฟังก์ชันที่กำหนดหรือไม่ระบุชื่อที่ส่งผ่านไปยังจุดเข้าใช้งานฟังก์ชันการเรียกซ้ำ ฯลฯ

วิธีที่ดีกว่าในการใส่ "เธรดปลอดภัย" คือปลอดภัยสำหรับการเข้าถึงพร้อมกันซึ่งแสดงให้เห็นถึงความจำเป็นได้ดีกว่า


2
Reentrant ไม่ได้หมายความว่าเธรดปลอดภัย ฟังก์ชั่นบริสุทธิ์บ่งบอกถึงความปลอดภัยของด้าย
Julio Guerra

คำตอบที่ดีทิม เพื่อชี้แจงฉันเข้าใจจาก "บ่อยครั้ง" ของคุณคือความปลอดภัยของเธรดไม่ได้บ่งบอกถึงการส่งกลับอีกครั้ง คุณจะสามารถหาตัวอย่างของฟังก์ชัน reentrant ที่ไม่ปลอดภัยต่อเธรดได้หรือไม่?
Riccardo

@ Tim Post "ในระยะสั้น reentrant มักจะหมายถึง thread safe (เช่นเดียวกับ" use the reentrant version of that function if you're using threads ") แต่ thread safe ไม่ได้หมายถึง re-entrant เสมอไป" qt พูดตรงข้าม: "ดังนั้นฟังก์ชัน thread-safe จึง reentrant เสมอ แต่ฟังก์ชัน reentrant ไม่ปลอดภัยต่อเธรดเสมอไป"
4pie0

และวิกิพีเดียยังกล่าวอีกว่า "คำจำกัดความของการย้อนกลับนี้แตกต่างจากคำจำกัดความของความปลอดภัยของเธรดในสภาพแวดล้อมแบบมัลติเธรดรูทีนย่อย reentrant สามารถบรรลุความปลอดภัยของเธรด [1] แต่การส่งกลับเพียงอย่างเดียวอาจไม่เพียงพอที่จะปลอดภัยต่อเธรด ในทุกสถานการณ์ตรงกันข้ามรหัส thread-safe ไม่จำเป็นต้อง reentrant (... ) "
4pie0

@Riccardo: ฟังก์ชันที่ซิงโครไนซ์ผ่านตัวแปรระเหย แต่ไม่ใช่อุปสรรคหน่วยความจำเต็มสำหรับใช้กับตัวจัดการสัญญาณ / อินเทอร์รัปต์มักจะเข้ามาใหม่ แต่เธรดปลอดภัย
doynax

79

TL; DR: ฟังก์ชันสามารถ reentrant, thread-safe ทั้งสองอย่างหรือไม่ก็ได้

บทความ Wikipedia เพื่อความปลอดภัยของเธรดและการจัดระเบียบใหม่เป็นสิ่งที่ควรค่าแก่การอ่าน นี่คือการอ้างอิงบางส่วน:

ฟังก์ชันจะปลอดภัยต่อเธรดหาก:

จัดการเฉพาะโครงสร้างข้อมูลที่แชร์ในลักษณะที่รับประกันการดำเนินการอย่างปลอดภัยโดยหลายเธรดในเวลาเดียวกัน

ฟังก์ชันreentrantถ้า:

สามารถถูกขัดจังหวะเมื่อใดก็ได้ในระหว่างการดำเนินการและเรียกอีกครั้งอย่างปลอดภัย ("ป้อนใหม่") ก่อนที่การเรียกใช้ก่อนหน้าจะเสร็จสมบูรณ์

จากตัวอย่างของการย้อนกลับที่เป็นไปได้ Wikipedia จะให้ตัวอย่างของฟังก์ชันที่ออกแบบมาเพื่อเรียกใช้โดยการขัดจังหวะระบบ: สมมติว่ามันทำงานอยู่แล้วเมื่อมีการขัดจังหวะอื่นเกิดขึ้น แต่อย่าคิดว่าคุณปลอดภัยเพียงเพราะคุณไม่ได้เขียนโค้ดด้วยการขัดจังหวะระบบคุณอาจมีปัญหาในการย้อนกลับในโปรแกรมเธรดเดียวหากคุณใช้การเรียกกลับหรือฟังก์ชันเรียกซ้ำ

กุญแจสำคัญในการหลีกเลี่ยงความสับสนคือ reentrant หมายถึงการดำเนินการเธรดเดียวเท่านั้น เป็นแนวคิดตั้งแต่สมัยที่ยังไม่มีระบบปฏิบัติการมัลติทาสก์

ตัวอย่าง

(แก้ไขเล็กน้อยจากบทความ Wikipedia)

ตัวอย่างที่ 1: not thread-safe, not reentrant

ตัวอย่างที่ 2: thread-safe, not reentrant

ตัวอย่างที่ 3: not thread-safe, reentrant

ตัวอย่างที่ 4: thread-safe, reentrant


10
ฉันรู้ว่าฉันไม่ควรแสดงความคิดเห็นเพื่อกล่าวขอบคุณ แต่นี่เป็นหนึ่งในภาพประกอบที่ดีที่สุดที่บอกถึงความแตกต่างระหว่างฟังก์ชัน re-entrant และ thread safe โดยเฉพาะอย่างยิ่งคุณได้ใช้คำที่กระชับชัดเจนและเลือกฟังก์ชันตัวอย่างที่ดีเพื่อแยกความแตกต่างระหว่าง 4 หมวด ขอบคุณมาก!
ryyker

11
สำหรับฉันแล้วดูเหมือนว่าตัวอย่างที่ 3 จะไม่ reentrant: หากตัวจัดการสัญญาณขัดจังหวะการt = *xโทรswap()หลังจากนั้นtจะถูกแทนที่ซึ่งนำไปสู่ผลลัพธ์ที่ไม่คาดคิด
rom1v

1
@ SandBag_1996 ลองพิจารณาการโทรที่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จัดสรรในสแตก
urnonav

4
@ SandBag_1996 สมมติฐานก็คือถ้าฟังก์ชันถูกขัดจังหวะ (ณ จุดใดก็ได้) มันจะถูกเรียกอีกครั้งเท่านั้นและเรารอจนกว่าจะเสร็จสิ้นก่อนที่จะทำการเรียกเดิมต่อ หากมีสิ่งอื่นเกิดขึ้นแสดงว่าเป็นมัลติเธรดโดยทั่วไปและฟังก์ชันนี้ไม่ปลอดภัยต่อเธรด สมมติว่าฟังก์ชันทำ ABCD เรายอมรับเฉพาะสิ่งต่างๆเช่น AB_ABCD_CD หรือ A_ABCD_BCD หรือแม้แต่ A__AB_ABCD_CD__BCD ดังที่คุณตรวจสอบได้ตัวอย่างที่ 3 จะทำงานได้ดีภายใต้สมมติฐานเหล่านี้ดังนั้นจึงกลับเข้าที่ หวังว่านี่จะช่วยได้
MiniQuark

1
@ SandBag_1996, mutex จะทำให้มันไม่กลับมาอีกครั้ง การเรียกใช้ครั้งแรกจะล็อก mutex การร้องขอครั้งที่สองเข้ามา - การหยุดชะงัก
urnonav

56

มันขึ้นอยู่กับคำจำกัดความ ตัวอย่างเช่นQt ใช้สิ่งต่อไปนี้:

  • สามารถเรียกใช้ฟังก์ชัน thread-safe * พร้อมกันได้จากหลายเธรดแม้ว่าการเรียกใช้จะใช้ข้อมูลที่แชร์เนื่องจากการอ้างอิงทั้งหมดไปยังข้อมูลที่แชร์จะถูกทำให้เป็นอนุกรม

  • reentrantฟังก์ชั่นยังสามารถเรียกว่าพร้อมกันจากหลายหัวข้อ แต่ถ้าแต่ละภาวนาใช้ข้อมูลของตัวเอง

ดังนั้นฟังก์ชันthread-safeจึง reentrant เสมอ แต่ฟังก์ชันreentrantไม่ปลอดภัยต่อเธรดเสมอไป

โดยขยายชั้นเรียนกล่าวจะreentrantถ้าฟังก์ชั่นสมาชิกสามารถเรียกได้อย่างปลอดภัยจากหลายหัวข้อตราบใดที่แต่ละหัวข้อใช้อินสแตนซ์ที่แตกต่างกันของการเรียน คลาสนี้ปลอดภัยต่อเธรดหากฟังก์ชันสมาชิกสามารถถูกเรียกได้อย่างปลอดภัยจากหลายเธรดแม้ว่าเธรดทั้งหมดจะใช้อินสแตนซ์ของคลาสเดียวกันก็ตาม

แต่พวกเขายังเตือน:

หมายเหตุ:คำศัพท์ในโดเมนมัลติเธรดไม่ได้เป็นมาตรฐานทั้งหมด POSIX ใช้คำจำกัดความของ reentrant และ thread-safe ซึ่งแตกต่างกันบ้างสำหรับ C APIs เมื่อใช้ไลบรารีคลาส C ++ เชิงวัตถุอื่นกับ Qt ต้องแน่ใจว่าเข้าใจนิยาม


2
คำจำกัดความของ reentrant นี้แรงเกินไป
qweruiop

ฟังก์ชันมีทั้ง reentrant และ thread-safe หากไม่ใช้ global / static var เธรด - ปลอดภัย: เมื่อเธรดจำนวนมากเรียกใช้ฟังก์ชันของคุณในเวลาเดียวกันมีการแข่งขันหรือไม่? หากคุณใช้ global var ให้ใช้การล็อกเพื่อป้องกัน ดังนั้นจึงปลอดภัยต่อด้าย reentrant: หากสัญญาณเกิดขึ้นระหว่างการเรียกใช้ฟังก์ชันของคุณและเรียกใช้ฟังก์ชันของคุณในสัญญาณอีกครั้งจะปลอดภัยหรือไม่ ??? ในกรณีนี้จะไม่มีเธรดหลายเธรด เป็นการดีที่สุดที่คุณจะไม่ใช้ตัวแปรแบบคงที่ / โกลบอลเพื่อทำให้กลับเข้าที่หรือเหมือนในตัวอย่างที่ 3
keniee van
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.