คำว่า "thread-safe" หมายถึงอะไร?


367

หมายความว่าสองเธรดไม่สามารถเปลี่ยนข้อมูลพื้นฐานพร้อมกันได้หรือไม่ หรือหมายความว่าส่วนรหัสที่กำหนดจะทำงานด้วยผลลัพธ์ที่สามารถคาดการณ์ได้เมื่อมีหลายเธรดที่กำลังเรียกใช้งานส่วนรหัสนั้น


8
เพิ่งเห็นการสนทนาที่น่าสนใจที่นี่เกี่ยวกับเรื่องนี้: blogs.msdn.com/ericlippert/archive/2009/10/19/…
เซบาสเตียน

คำตอบ:


256

จาก Wikipedia:

Thread safety เป็นแนวคิดการเขียนโปรแกรมคอมพิวเตอร์ที่ใช้ในบริบทของโปรแกรมแบบมัลติเธรด ชิ้นส่วนของรหัสนั้นปลอดภัยสำหรับเธรดหากทำงานอย่างถูกต้องระหว่างดำเนินการหลายเธรดพร้อมกัน โดยเฉพาะอย่างยิ่งต้องตอบสนองความต้องการของหลาย ๆ เธรดในการเข้าถึงข้อมูลที่ใช้ร่วมกันแบบเดียวกันและความต้องการในการเข้าถึงข้อมูลที่แบ่งปันโดยใช้เพียงหนึ่งเธรดในเวลาใดก็ตาม

มีหลายวิธีในการบรรลุความปลอดภัยของเธรด:

Re-entrancy:

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

การยกเว้นซึ่งกันและกัน:

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

การจัดเก็บภายในเธรด:

ตัวแปรถูกแปลเป็นภาษาท้องถิ่นเพื่อให้แต่ละเธรดมีสำเนาส่วนตัวของตัวเอง ตัวแปรเหล่านี้เก็บค่าข้ามรูทีนย่อยและขอบเขตรหัสอื่น ๆ และมีความปลอดภัยของเธรดเนื่องจากเป็นโลคัลสำหรับแต่ละเธรดแม้ว่าโค้ดที่เข้าถึงพวกเขาอาจเป็น reentrant

การดำเนินงานปรมาณู:

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

อ่านเพิ่มเติม:

http://en.wikipedia.org/wiki/Thread_safety



4
ลิงก์นี้ในทางเทคนิคแล้วไม่มีจุดวิกฤติหลายจุด หน่วยความจำที่ใช้ร่วมกันในคำถามต้องไม่แน่นอน (หน่วยความจำแบบอ่านอย่างเดียวไม่สามารถเป็นเธรดที่ไม่ปลอดภัย) และต้องมีหลายเธรด a) ทำการเขียนหลาย ๆ ครั้งในหน่วยความจำในระหว่างที่หน่วยความจำไม่สอดคล้องกัน (ผิด) สถานะและ b) อนุญาตให้เธรดอื่นขัดจังหวะเธรดนั้นในขณะที่หน่วยความจำไม่สอดคล้องกัน
ชาร์ลส์เบรตานา

20
เมื่อค้นหาผลลัพธ์แรกของ Google คือ wiki ไม่มีจุดที่จะทำให้ซ้ำซ้อนที่นี่
Ranvir

คุณหมายถึง "โค้ดที่เข้าถึงฟังก์ชัน" หมายความว่าอย่างไร ฟังก์ชั่นที่ถูกประมวลผลนั้นเป็นโค้ดใช่ไหม?
Koray Tugay

82

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

http://mindprod.com/jgloss/threadsafe.html


34
ภายในกระบวนการเดียวกัน!
Ali Afshar

อันที่จริงในกระบวนการเดียวกัน :)
มาเร็ค Blotny

4
"ในการเขียนรหัสที่จะทำงานได้เสถียรนานหลายสัปดาห์ต้องใช้ความหวาดระแวงอย่างมาก" นั่นเป็นคำพูดที่ผมชอบ :)
จิม T

5
duh! คำตอบนี้เพิ่งจะคืนคำถาม! --- และทำไมภายในกระบวนการเดียวกันเท่านั้น ??? หากรหัสล้มเหลวเมื่อหลายเธรดเรียกใช้งานจากกระบวนการที่แตกต่างกันดังนั้นเนื้อหา (หน่วยความจำที่ใช้ร่วมกันอาจอยู่ในไฟล์ดิสก์) มันไม่ปลอดภัยสำหรับเธรด !!
ชาร์ลส์เบรตานา

1
@ mg30rg บางทีความสับสนอาจเป็นผลมาจากการคิดอย่างใดอย่างหนึ่งว่าเมื่อบล็อกของโค้ดกำลังถูกดำเนินการโดยหลายกระบวนการ แต่เพียงหนึ่งเธรดต่อกระบวนการซึ่งอย่างนั้นก็ยังคงเป็นสถานการณ์ "เธรดเดี่ยว" ไม่ใช่สถานการณ์แบบมัลติเธรดแบบหลายเธรด . ความคิดนี้ไม่ผิดเลย มันเป็นเพียงความหมายที่ผิด เห็นได้ชัดว่ากระบวนการหลาย ๆ กระบวนการไม่ได้ดำเนินการบนเธรดเดียวกันในลักษณะที่ซิงโครไนซ์ (ยกเว้นในสถานการณ์ที่ไม่ค่อยเกิดขึ้นซึ่งกระบวนการโดยการประสานงานการออกแบบกับอีกกระบวนการหนึ่งและระบบปฏิบัติการแบ่งปันเธรดระหว่างกระบวนการ)
Charles Bretana

50

คำถามที่ให้ข้อมูลเพิ่มเติมคือสิ่งที่ทำให้โค้ดไม่ได้เป็นเธรดที่ปลอดภัยและคำตอบคือมีเงื่อนไขสี่ประการที่ต้องเป็นจริง ... ลองนึกภาพโค้ดต่อไปนี้ (และการแปลภาษาด้วยเครื่อง)

totalRequests = totalRequests + 1
MOV EAX, [totalRequests]   // load memory for tot Requests into register
INC EAX                    // update register
MOV [totalRequests], EAX   // store updated value back to memory
  1. เงื่อนไขแรกคือมีตำแหน่งหน่วยความจำที่สามารถเข้าถึงได้จากเธรดมากกว่าหนึ่งเธรด โดยทั่วไปตำแหน่งเหล่านี้เป็นตัวแปรโกลบอล / สแตติกหรือเป็นหน่วยความจำฮีปที่สามารถเข้าถึงได้จากตัวแปรโกลบอล / สแตติก แต่ละเธรดได้รับเป็นเฟรมสแต็คของตัวเองสำหรับฟังก์ชัน / เมธอดขอบเขตโลคอลตัวแปรโลคอลดังนั้นตัวแปรฟังก์ชัน / เมธอดโลคอลเหล่านี้ otoh (ซึ่งอยู่บนสแต็ก) สามารถเข้าถึงได้จากเธรดเดียวที่เป็นเจ้าของสแต็กนั้น
  2. เงื่อนไขที่สองคือมีคุณสมบัติ (มักเรียกว่าinvariant ) ซึ่งเชื่อมโยงกับตำแหน่งหน่วยความจำที่แบ่งใช้เหล่านี้ซึ่งต้องเป็นจริงหรือถูกต้องเพื่อให้โปรแกรมทำงานได้อย่างถูกต้อง ในตัวอย่างด้านบนคุณสมบัติคือ“ totalRequests ต้องแสดงถึงจำนวนครั้งที่เธรดใด ๆ ได้ดำเนินการส่วนใดส่วนหนึ่งของคำสั่งที่เพิ่มขึ้นอย่างถูกต้อง ” โดยทั่วไปแล้วคุณสมบัติที่ไม่เปลี่ยนแปลงนี้ต้องเก็บเป็นจริง (ในกรณีนี้ยอดรวมต้องมีจำนวนที่ถูกต้อง) ก่อนที่การอัปเดตจะเกิดขึ้นสำหรับการอัปเดตที่ถูกต้อง
  3. เงื่อนไขที่สามคือคุณสมบัติคงที่ไม่ได้เก็บไว้ในระหว่างการปรับปรุงจริงบางส่วน (มันไม่ถูกต้องชั่วคราวหรือเท็จในระหว่างการประมวลผลบางส่วน) ในกรณีนี้โดยเฉพาะตั้งแต่ดึงยอดรวมเวลามาจนถึงเวลาที่เก็บค่าที่อัปเดตแล้ว TotalRequests จะไม่ตอบสนองค่าคงที่
  4. เงื่อนไขที่สี่และสุดท้ายที่ต้องเกิดขึ้นสำหรับการแข่งขันที่จะเกิดขึ้น (และสำหรับรหัสที่จะไม่เป็น "เธรดที่ปลอดภัย") คือเธรดอื่นจะต้องสามารถเข้าถึงหน่วยความจำที่ใช้ร่วมกันในขณะที่ค่าคงที่ไม่ถูกต้อง พฤติกรรมที่ไม่ถูกต้อง

6
สิ่งนี้ครอบคลุมเฉพาะสิ่งที่เรียกว่าการแข่งขันทางข้อมูลและมีความสำคัญอย่างแน่นอน ยังมีอีกหลายวิธีที่โค้ดไม่สามารถเป็นเธรดที่ปลอดภัยได้ตัวอย่างเช่นการล็อกไม่ดีที่อาจนำไปสู่การหยุดชะงัก แม้แต่สิ่งง่าย ๆ เช่นการเรียก System.exit () ที่ใดที่หนึ่งในเธรด java ทำให้รหัสนั้นไม่ปลอดภัย
Ingo

2
ฉันเดาระดับนี้เป็นความหมาย แต่ฉันจะยืนยันว่ารหัสล็อคไม่ดีที่อาจทำให้เกิดการหยุดชะงักไม่ทำให้รหัสไม่ปลอดภัย ประการแรกไม่จำเป็นต้องล็อครหัสในสถานที่แรกเว้นแต่สภาพการแข่งขันตามที่อธิบายไว้ข้างต้นเป็นไปได้ จากนั้นถ้าคุณเขียนรหัสล็อคในลักษณะที่ทำให้เกิดการหยุดชะงักนั่นไม่ใช่กระทู้ที่ไม่ปลอดภัยมันเป็นเพียงรหัสที่ไม่ดี
Charles Bretana

1
แต่โปรดทราบว่าการหยุดชะงักจะไม่เกิดขึ้นเมื่อใช้งานเธรดเดี่ยวดังนั้นสำหรับพวกเราส่วนใหญ่สิ่งนี้จะตกอยู่ภายใต้ความหมายที่เข้าใจง่ายของ (ไม่) "ปลอดภัยต่อเธรด"
Jon Coombs

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

34

ฉันชอบความหมายจาก Java Concurrency ในทางปฏิบัติของ Brian Goetz สำหรับความครอบคลุม

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


คำจำกัดความนี้ไม่สมบูรณ์และไม่เฉพาะเจาะจงและไม่ครอบคลุมอย่างแน่นอน ต้องทำงานอย่างปลอดภัยกี่ครั้งเพียงครั้งเดียว สิบครั้ง? ทุกเวลา? 80% ของเวลา? และไม่ได้ระบุว่าอะไรทำให้ "ไม่ปลอดภัย" ถ้ามันล้มเหลวในการทำงานอย่างปลอดภัย แต่ความล้มเหลวเป็นเพราะมีข้อผิดพลาดโดยแบ่งเป็นศูนย์นั่นทำให้เธรด - "ไม่ปลอดภัย" หรือไม่
Charles Bretana

เป็นพลเมืองที่มากขึ้นในครั้งต่อไปและเราอาจจะพูดคุยกัน นี่ไม่ใช่ Reddit และฉันไม่ได้อยู่ในอารมณ์ที่จะพูดคุยกับคนหยาบคาย
Buu Nguyen

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

28

ตามที่คนอื่น ๆ ได้ระบุไว้ความปลอดภัยของเธรดหมายความว่าชิ้นส่วนของรหัสจะทำงานได้อย่างไม่มีข้อผิดพลาดหากมีการใช้เธรดมากกว่าหนึ่งรายการในคราวเดียว

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

ยกตัวอย่างเช่น Java มีสองชั้นเรียนที่มีค่าเท่ากันเกือบและStringBuffer StringBuilderความแตกต่างคือStringBufferความปลอดภัยของเธรดดังนั้นStringBufferอาจใช้หลายอินสแตนซ์ของเธรดเดียวในคราวเดียว StringBuilderไม่ปลอดภัยสำหรับเธรดและได้รับการออกแบบมาเพื่อทดแทนประสิทธิภาพที่สูงขึ้นสำหรับเคสเหล่านั้น (ส่วนใหญ่) เมื่อสร้างสตริงโดยเธรดเดียวเท่านั้น


22

รหัส thread-safe-code ทำงานตามที่ระบุไว้แม้ว่าจะถูกป้อนพร้อมกันโดยเธรดที่แตกต่างกัน บ่อยครั้งหมายความว่าโครงสร้างข้อมูลภายในหรือการดำเนินการที่ควรรันอย่างต่อเนื่องจะได้รับการปกป้องจากการดัดแปลงต่าง ๆ ในเวลาเดียวกัน


21

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

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

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


9

ใช่และไม่.

ความปลอดภัยของเธรดนั้นมากกว่าการทำให้แน่ใจว่าการเข้าถึงข้อมูลที่คุณแบ่งปันมีเพียงหนึ่งเธรดในแต่ละครั้ง คุณต้องให้แน่ใจเข้าถึงลำดับข้อมูลที่ใช้ร่วมกันในขณะที่ในเวลาเดียวกันหลีกเลี่ยงสภาพการแข่งขัน , การติดตาย , livelocksและความอดอยากทรัพยากร

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


9

ในสาระสำคัญสิ่งต่าง ๆ อาจผิดพลาดในสภาพแวดล้อมแบบมัลติเธรด (คำสั่งการเรียงลำดับใหม่วัตถุที่สร้างขึ้นบางส่วนตัวแปรเดียวกันมีค่าแตกต่างกันในเธรดที่แตกต่างกันเนื่องจากการแคชที่ระดับ CPU เป็นต้น)

ฉันชอบคำนิยามที่กำหนดโดยJava Concurrency ในทางปฏิบัติ :

[ส่วนของรหัส] เป็นเธรดที่ปลอดภัยหากทำงานอย่างถูกต้องเมื่อเข้าถึงจากหลายเธรดไม่ว่าการกำหนดเวลาหรือการสอดแทรกการดำเนินการของเธรดเหล่านั้นโดยสภาพแวดล้อมรันไทม์และไม่มีการซิงโครไนซ์เพิ่มเติมหรือการประสานงานอื่น ๆ รหัสโทร

โดยถูกต้องพวกเขาหมายความว่าพฤติกรรมโปรแกรมในการปฏิบัติตามข้อกำหนดของ

ตัวอย่างที่คาดหวัง

ลองนึกภาพว่าคุณใช้ตัวนับ คุณสามารถพูดได้ว่ามันทำงานอย่างถูกต้องหาก:

  • counter.next() ไม่ส่งคืนค่าที่มีการส่งคืนมาก่อน (เราถือว่าไม่มีการโอเวอร์โฟลว์และอื่น ๆ เพื่อความง่าย)
  • ค่าทั้งหมดจาก 0 ถึงค่าปัจจุบันถูกส่งคืนในบางช่วง (ไม่มีค่าถูกข้าม)

ตัวนับความปลอดภัยของเธรดจะทำงานตามกฎเหล่านั้นโดยไม่คำนึงถึงจำนวนเธรดที่เข้าถึงพร้อมกัน

หมายเหตุ: cross-post บนโปรแกรมเมอร์


8

เพียง - รหัสจะทำงานได้ดีถ้ามีหลายเธรดที่กำลังเรียกใช้รหัสนี้ในเวลาเดียวกัน


5

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

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


5

ฉันต้องการเพิ่มข้อมูลเพิ่มเติมที่ด้านบนของคำตอบที่ดีอื่น ๆ

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

ดูคำถาม SE นี้สำหรับรายละเอียดเพิ่มเติม:

threadsafe หมายถึงอะไร

โปรแกรมความปลอดภัยด้ายรับประกันความสอดคล้องหน่วยความจำ

จากหน้าเอกสารของ Oracle ใน API พร้อมกันขั้นสูง:

คุณสมบัติความสอดคล้องของหน่วยความจำ:

บทที่ 17 ของข้อกำหนดภาษา Java ™กำหนดความสัมพันธ์ที่เกิดขึ้นก่อนการดำเนินงานหน่วยความจำเช่นการอ่านและเขียนของตัวแปรที่ใช้ร่วมกัน ผลลัพธ์ของการเขียนโดยหนึ่งกระทู้รับประกันว่าจะสามารถอ่านได้โดยเธรดอื่นเฉพาะเมื่อการเขียนเกิดขึ้นก่อนการดำเนินการอ่านผลที่ได้จากการเขียนโดยหนึ่งในหัวข้อที่มีการรับประกันที่จะมองเห็นการอ่านโดยด้ายเฉพาะในกรณีที่การดำเนินการเขียนเกิดขึ้นก่อนที่จะดำเนินการอ่านอีก

synchronizedและvolatileโครงสร้างเช่นเดียวกับThread.start()และThread.join()วิธีการรูปแบบที่สามารถเกิดขึ้นมาก่อนความสัมพันธ์

วิธีการของการเรียนทั้งหมดในjava.util.concurrentและแพคเกจย่อยของมันขยายการรับประกันเหล่านี้เพื่อการประสานในระดับที่สูงขึ้น โดยเฉพาะอย่างยิ่ง:

  1. การกระทำในเธรดก่อนที่จะวางวัตถุลงในคอลเลกชันที่เกิดขึ้นพร้อมกันใด ๆ ที่เกิดขึ้นก่อนการกระทำที่ตามมาหลังจากการเข้าถึงหรือลบองค์ประกอบจากคอลเลกชันในหัวข้ออื่น
  2. การดำเนินการในเธรดก่อนส่ง a RunnableไปยังExecutorเกิดขึ้นก่อนที่การดำเนินการจะเริ่มต้นขึ้น ในทำนองเดียวกันสำหรับ Callables ExecutorServiceส่งต่อไปยัง
  3. การกระทำที่ดำเนินการโดยการคำนวณแบบอะซิงโครนัสแสดงโดยการFutureกระทำที่เกิดขึ้นก่อนการดำเนินการหลังจากการดึงผลลัพธ์ผ่านทางFuture.get()ในเธรดอื่น
  4. การกระทำก่อนที่จะ "ปล่อย" วิธีการซิงโครไนซ์เช่นLock.unlock, Semaphore.release, and CountDownLatch.countDownเกิดขึ้นก่อนการกระทำที่ตามมาด้วยวิธี "การรับ" ที่ประสบความสำเร็จเช่นLock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.awaitบนวัตถุซิงโครไนซ์เดียวกันในเธรดอื่น
  5. สำหรับแต่ละคู่ของเธรดที่แลกเปลี่ยนวัตถุผ่านสำเร็จการExchangerกระทำก่อนหน้าexchange()ในแต่ละเธรดจะเกิดขึ้นก่อนที่จะเกิดขึ้นภายหลังจากการแลกเปลี่ยนที่สอดคล้องกัน () ในเธรดอื่น
  6. การดำเนินการก่อนที่จะมีการเรียกCyclicBarrier.awaitและPhaser.awaitAdvance(รวมถึงตัวแปร) เกิดขึ้นก่อนที่การกระทำที่ดำเนินการโดยสิ่งกีดขวางและการกระทำที่ดำเนินการโดยการกระทำสิ่งกีดขวางที่เกิดขึ้น - ก่อนการกระทำหลังจากการกลับมาประสบความสำเร็จจาก

4

หากต้องการคำตอบอื่น ๆ ให้สมบูรณ์:

การซิงโครไนซ์เป็นเรื่องที่กังวลเมื่อโค้ดในวิธีการของคุณทำหนึ่งในสองสิ่งต่อไปนี้:

  1. ทำงานกับทรัพยากรภายนอกบางอย่างที่ไม่ปลอดภัยสำหรับเธรด
  2. อ่านหรือเปลี่ยนวัตถุหรือฟิลด์คลาสถาวร

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

การตั้งเวลากระทู้ไม่ได้รับประกันว่าจะไปเรื่อย งานทั้งหมดอาจทำให้ CPU เสียค่าใช้จ่ายของเธรดที่มีลำดับความสำคัญเท่ากัน คุณสามารถใช้ Thread.yield () เพื่อมีมโนธรรม คุณสามารถใช้ (ใน java) Thread.setPriority (Thread.NORM_PRIORITY-1) เพื่อลดลำดับความสำคัญของเธรด

บวกกับระวัง:

  • ต้นทุนรันไทม์ขนาดใหญ่ (ที่ผู้อื่นกล่าวถึงแล้ว) ในแอปพลิเคชันที่วนซ้ำโครงสร้าง "เธรดที่ปลอดภัย" เหล่านี้
  • Thread.sleep (5000) ควรจะนอนเป็นเวลา 5 วินาที อย่างไรก็ตามหากใครบางคนเปลี่ยนเวลาของระบบคุณอาจเข้าสู่โหมดสลีปเป็นเวลานานมากหรือไม่มีเวลาเลยก็ได้ ระบบปฏิบัติการจะบันทึกเวลาปลุกในรูปแบบสมบูรณ์ไม่ใช่แบบสัมพัทธ์

2

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

โปรดทราบว่าผลลัพธ์ที่คาดการณ์ไม่ได้เป็นผลมาจาก 'เงื่อนไขการแข่งขัน' ซึ่งอาจส่งผลให้ข้อมูลถูกปรับเปลี่ยนในลำดับอื่นนอกเหนือจากที่คาดไว้


2

ลองตอบคำถามนี้ตามตัวอย่าง:

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

countTo10วิธีการหนึ่งที่จะเพิ่มเคาน์เตอร์และจากนั้นส่งกลับจริงถ้านับได้ถึง 10 มันควรกลับมาครั้งเดียวจริง

วิธีนี้จะใช้ได้ตราบใดที่มีเพียงเธรดเดียวเท่านั้นที่กำลังเรียกใช้รหัส หากเธรดสองตัวรันรหัสในเวลาเดียวกันอาจเกิดปัญหาต่าง ๆ ได้

ตัวอย่างเช่นหากการนับเริ่มต้นที่ 9 หนึ่งเธรดสามารถเพิ่ม 1 ถึงนับ (ทำ 10) แต่จากนั้นเธรดที่สองสามารถเข้าสู่วิธีการและเพิ่ม 1 อีกครั้ง (ทำ 11) ก่อนที่เธรดแรกจะมีโอกาสดำเนินการเปรียบเทียบกับ 10 จากนั้นทั้งสองเธรดจะทำการเปรียบเทียบและพบว่าจำนวนนั้นเป็น 11 และไม่ส่งคืนค่าจริง

ดังนั้นรหัสนี้ไม่ปลอดภัยเธรด

ในสาระสำคัญปัญหามัลติเธรดทั้งหมดเกิดจากการเปลี่ยนแปลงของปัญหาชนิดนี้

วิธีแก้ไขคือเพื่อให้แน่ใจว่าการเพิ่มและการเปรียบเทียบไม่สามารถแยกออกได้ (ตัวอย่างเช่นโดยการล้อมรอบคำสั่งทั้งสองด้วยรหัสการซิงโครไนซ์บางชนิด) หรือโดยการหาวิธีแก้ปัญหาที่ไม่ต้องการการดำเนินการสองอย่าง รหัสดังกล่าวจะปลอดภัยต่อเธรด


1

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

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

และนี่คือเหตุผลที่บางคนชอบที่จะใช้คำว่าตรงกันภายใน

ชุดคำศัพท์

มีคำศัพท์หลักสามชุดสำหรับแนวคิดเหล่านี้ที่ฉันได้พบ ครั้งแรกและเป็นที่นิยมในอดีต (แต่แย่กว่า) คือ:

  1. ด้ายปลอดภัย
  2. ด้ายไม่ปลอดภัย

ที่สอง (และดีกว่า) คือ:

  1. หลักฐานด้าย
  2. เข้ากันได้กับกระทู้
  3. ศัตรูด้าย

ที่สามคือ:

  1. ทำข้อมูลให้ตรงกันภายใน
  2. ซิงโครไนซ์ภายนอก
  3. unsynchronizeable

อุปมา

เธรดปลอดภัย ~ พิสูจน์เธรด ~ ซิงโครไนซ์ภายใน

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

ไม่ปลอดภัยสำหรับเธรด (แต่ดี) เข้ากันได้กับเธรด ~ ซิงโครไนซ์ภายนอก ~ ฟรีเธรด

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

สิ่งนี้เรียกอีกอย่างว่า "free threaded" โดยที่ "free" อยู่ใน "free lice" - หรือในกรณีนี้คือ lock การซิงโครไนซ์แบบดั้งเดิมถูกต้องแม่นยำยิ่งขึ้น แต่นั่นไม่ได้หมายความว่ารหัสสามารถทำงานได้หลายเธรดโดยไม่ต้องมีรหัสดั้งเดิม มันหมายความว่ามันไม่ได้มาพร้อมกับพวกเขาติดตั้งแล้วและมันก็ขึ้นอยู่กับคุณผู้ใช้รหัสเพื่อติดตั้งด้วยตัวเอง แต่คุณเห็นว่าเหมาะสม การติดตั้งการซิงโครไนซ์แบบดั้งเดิมของคุณเองอาจเป็นเรื่องยากและต้องใช้ความคิดอย่างหนักเกี่ยวกับโค้ด แต่ยังสามารถนำไปสู่โปรแกรมที่เร็วที่สุดที่เป็นไปได้ด้วยการอนุญาตให้คุณกำหนดวิธีที่โปรแกรม

เธรดไม่ปลอดภัย (และไม่ดี) ~ เธรดที่เป็นมิตร ~ ไม่ซิงโครไนซ์

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

ทำไมด้ายปลอดภัยและคณะ เป็นชุดคำศัพท์ที่ไม่ดี

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

หมายเหตุ: คู่มือซอฟต์แวร์จำนวนมากใช้คำว่า "thread-safe" จริง ๆ เพื่ออ้างถึง "thread-compatible" เพื่อเพิ่มความสับสนให้กับสิ่งที่เป็นระเบียบอยู่แล้ว! ฉันหลีกเลี่ยงคำว่า "thread-safe" และ "thread-unsafe" ด้วยเหตุผลทั้งหมดนี้เนื่องจากบางแหล่งจะเรียกบางอย่าง "thread-safe" ในขณะที่คนอื่น ๆ จะเรียกมันว่า "thread-unsafe" เพราะพวกเขาไม่เห็นด้วย ไม่ว่าคุณจะต้องทำตามมาตรฐานพิเศษเพื่อความปลอดภัย (การซิงโครไนซ์ดั้งเดิม) หรือไม่เป็นศัตรูที่จะถือว่า "ปลอดภัย" ดังนั้นหลีกเลี่ยงข้อกำหนดเหล่านั้นและใช้ข้อกำหนดที่ชาญฉลาดแทนเพื่อหลีกเลี่ยงการสื่อสารที่เป็นอันตรายกับวิศวกรรายอื่น

เตือนความจำถึงเป้าหมายของเรา

เป้าหมายหลักของเราคือทำลายความโกลาหล

เราทำเช่นนั้นโดยการสร้างระบบกำหนดขึ้นมาที่เราสามารถพึ่งพาได้ การกำหนดราคาแพงส่วนใหญ่เกิดจากค่าเสียโอกาสของการสูญเสียความเท่าเทียม, การวางท่อและการเรียงลำดับใหม่ เราพยายามที่จะลดจำนวนการกำหนดระดับที่เราต้องทำให้ต้นทุนของเราต่ำในขณะที่ยังหลีกเลี่ยงการตัดสินใจที่จะกัดกร่อนระดับที่เราสามารถหาได้

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


1

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

ตัวอย่างเช่นคอลเลกชันจำนวนมากเช่นรายการอาร์เรย์และชุดแฮชจะรับประกันได้ว่าหากพวกเขามีการเข้าถึง แต่เพียงผู้เดียวในตอนแรกและพวกเขาไม่เคยแก้ไขหลังจากการอ้างอิงจะปรากฏให้เห็นกระทู้อื่น ๆ พวกเขาอาจถูกอ่านโดยรวม ของหัวข้อโดยไม่มีการรบกวน

ที่น่าสนใจคือชุดของชุดแฮชบางชุดเช่นชุดที่ไม่ใช่แบบทั่วไปใน. NET อาจรับประกันได้ว่าตราบใดที่ไม่มีรายการใดถูกลบออกไปและหากว่ามีเพียงหนึ่งเธรดเท่านั้นที่เคยเขียนลงไป อ่านคอลเลกชันจะทำตัวราวกับว่ากำลังเข้าถึงคอลเลกชันที่การอัปเดตอาจล่าช้าและเกิดขึ้นตามลำดับโดยพลการ แต่จะดำเนินการตามปกติ หากเธรด # 1 เพิ่ม X จากนั้น Y และเธรด # 2 ค้นหาและเห็น Y และ X แล้วอาจเป็นไปได้ที่เธรด # 2 จะเห็นว่า Y มีอยู่ แต่ X ไม่ได้ พฤติกรรมดังกล่าวจะเป็น "thread-safe" หรือไม่นั้นขึ้นอยู่กับว่า thread # 2 พร้อมที่จะจัดการกับความเป็นไปได้นั้นหรือไม่

หมายเหตุสุดท้ายบางคลาส - โดยเฉพาะการบล็อกไลบรารีการสื่อสาร - อาจมีวิธี "ปิด" หรือ "กำจัด" ซึ่งเป็นเธรดที่ปลอดภัยสำหรับวิธีการอื่น ๆ ทั้งหมด แต่ไม่มีวิธีอื่นใดที่ปลอดภัยต่อเธรดสำหรับ ซึ่งกันและกัน หากเธรดทำการร้องขอการบล็อกการอ่านและผู้ใช้ของโปรแกรมคลิกที่ "ยกเลิก" จะไม่มีวิธีใดที่การร้องขอปิดจะถูกส่งโดยเธรดที่พยายามทำการอ่าน อย่างไรก็ตามคำขอปิด / กำจัดอาจตั้งค่าสถานะแบบอะซิงโครนัสซึ่งจะทำให้การร้องขอการอ่านถูกยกเลิกโดยเร็วที่สุด เมื่อทำการปิดในเธรดใด ๆ วัตถุก็จะไร้ประโยชน์และความพยายามทั้งหมดที่การกระทำในอนาคตจะล้มเหลวทันที


0

ด้วยคำที่ง่ายที่สุด: P หากมีความปลอดภัยในการรันหลายเธรดบนบล็อกของรหัสมันจะปลอดภัยเธรด *

* เป็นไปตามเงื่อนไข

เงื่อนไขที่กล่าวถึงโดยคำตอบอื่น ๆ เช่น 1 ผลลัพธ์ควรเหมือนกันถ้าคุณรันหนึ่งเธรดหรือหลายเธรด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.