ความแตกต่างระหว่างตัวเลือกการซิงโครไนซ์เธรดต่าง ๆ ใน C # คืออะไร


164

บางคนสามารถอธิบายความแตกต่างระหว่าง:

  • ล็อค (someobject) {}
  • ใช้ Mutex
  • ใช้เซมาฟอร์
  • ใช้การตรวจสอบ
  • ใช้คลาสการซิงโครไนซ์. Net อื่น ๆ

ฉันแค่คิดออกไม่ได้ ดูเหมือนว่าฉันสองคนแรกเหมือนกันหรือไม่


ลิงค์นี้ช่วยฉันได้มาก: albahari.com/threading
Raphael

คำตอบ:


135

เป็นคำถามที่ดีมาก ฉันอาจผิด .. ให้ฉันลอง .. แก้ไข # 2 ของคำตอบต้นกำเนิดของฉัน .. ด้วยความเข้าใจเพิ่มขึ้นเล็กน้อย ขอบคุณที่ทำให้ฉันอ่าน :)

ล็อค (obj)

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

จอภาพ

  • lock (obj) ถูกนำไปใช้ภายในโดยใช้มอนิเตอร์ คุณควรจะชอบล็อค (obj) เพราะมันจะป้องกันไม่ให้คุณทำผิดพลาดเช่นการลืมขั้นตอนการทำความสะอาด มันเป็นหลักฐานการตรวจสอบงี่เง่าสร้างถ้าคุณจะ
    โดยทั่วไปแล้วการใช้จอภาพเป็นที่ต้องการมากกว่าการใช้ mutexes เนื่องจากจอภาพได้รับการออกแบบมาโดยเฉพาะสำหรับ. NET Framework และทำให้ใช้ทรัพยากรได้ดีขึ้น

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

mutex:

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

Semaphores (ทำร้ายสมองฉัน)

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

เพจที่จะอ่าน - การซิงโครไนซ์เธรด (C #)


18
คุณอ้างว่าMonitorไม่อนุญาตการสื่อสารที่ไม่ถูกต้อง คุณยังสามารถPulseฯลฯ ด้วยMonitor
Marc Gravell

3
ตรวจสอบรายละเอียดทางเลือกของการ Semaphores - stackoverflow.com/a/40473/968003 คิดว่าเซมาฟอเรสเป็นกระเด้งที่ไนท์คลับ มีผู้คนจำนวนมากที่ได้รับอนุญาตในสโมสรพร้อมกัน หากสโมสรเต็มไม่มีใครได้รับอนุญาตให้เข้า แต่ทันทีที่คนคนหนึ่งออกจากบุคคลอื่นอาจเข้าสู่
Alex Klaus

31

เรื่อง "การใช้คลาสการซิงโครไนซ์. Net อื่น ๆ " - อันอื่น ๆ ที่คุณควรทราบเกี่ยวกับ:

  • ReaderWriterLock - อนุญาตให้ผู้อ่านหลายคนหรือผู้เขียนคนเดียว (ไม่ใช่ในเวลาเดียวกัน)
  • ReaderWriterLockSlim - เหมือนด้านบนโอเวอร์เฮด
  • ManualResetEvent - เกตที่อนุญาตให้ใช้รหัสที่ผ่านมาเมื่อเปิด
  • AutoResetEvent - ดังกล่าวข้างต้น แต่ปิดโดยอัตโนมัติเมื่อเปิด

นอกจากนี้ยังมีโครงสร้างการล็อกเพิ่มเติม (ค่าโสหุ้ยต่ำ) ใน CCR / TPL ( Parallel Extensions CTP) - แต่ IIRC เหล่านี้จะพร้อมใช้งานใน. NET 4.0


ดังนั้นหากฉันต้องการการสื่อสารสัญญาณอย่างง่าย (พูดว่า async op เสร็จสิ้น) - ฉันควร Monitor.Pulse หรือไม่ หรือใช้ SemaphoreSlim หรือ TaskCompletionSource
Vivek

ใช้ TaskCompletionSource สำหรับการดำเนินการ async โดยพื้นฐานแล้วหยุดคิดเกี่ยวกับเธรดและเริ่มคิดเกี่ยวกับงาน (หน่วยงาน) เธรดเป็นรายละเอียดการใช้งานและไม่เกี่ยวข้อง ด้วยการส่งคืน TCS คุณสามารถส่งคืนผลลัพธ์ข้อผิดพลาดหรือจัดการการยกเลิกและสามารถประกอบกับการดำเนินการ async อื่น ๆ ได้อย่างง่ายดาย (เช่น async กำลังรอหรือ ContinueWith)
Simon Gillbee

14

ตามที่ระบุไว้ใน ECMA และตามที่คุณสามารถสังเกตได้จากวิธีการที่สะท้อนคำสั่งล็อคนั้นเทียบเท่ากับ

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

จากตัวอย่างข้างต้นเราจะเห็นว่าจอภาพสามารถล็อควัตถุได้

Mutexe นั้นมีประโยชน์เมื่อคุณต้องการซิงโครไนซ์การประมวลผลเนื่องจากสามารถล็อกตัวระบุสตริงได้ ตัวระบุสตริงเดียวกันสามารถใช้โดยกระบวนการต่าง ๆ เพื่อรับการล็อก

Semaphores เหมือนกับ Mutexes บนเตียรอยด์พวกมันอนุญาตการเข้าถึงพร้อมกันโดยการนับจำนวนสูงสุดของการเข้าถึงพร้อมกัน ' เมื่อถึงขีด จำกัด สัญญาณจะเริ่มบล็อกการเข้าถึงทรัพยากรใด ๆ เพิ่มเติมจนกว่าจะมีผู้เรียกหนึ่งรายปล่อยสัญญาณ


5
น้ำตาล syntactic นี้มีการเปลี่ยนแปลงเล็กน้อยใน C # 4 ตรวจสอบblogs.msdn.com/ericlippert/archive/2009/03/06/…
Peter Gfader

14

ฉันได้เรียน & CLR เพื่อสนับสนุนเธรดใน DotGNU และฉันมีความคิดเล็กน้อย ...

นอกจากว่าคุณต้องการล็อคข้ามกระบวนการคุณควรหลีกเลี่ยงการใช้ Mutex & Semaphores คลาสเหล่านี้ใน. NET เป็นตัวล้อมรอบ Win32 Mutex และ Semaphores และมีน้ำหนักค่อนข้างมาก (พวกเขาต้องการสลับบริบทเป็นเคอร์เนลซึ่งมีราคาแพง - โดยเฉพาะอย่างยิ่งถ้าล็อคของคุณไม่อยู่ภายใต้การช่วงชิง)

ตามที่คนอื่นพูดถึงคำสั่ง C # lock นั้นเป็นเวทย์มนตร์คอมไพเลอร์สำหรับ Monitor.Enter และ Monitor.Exit (มีอยู่ภายในลอง / ในที่สุด)

จอภาพมีกลไกสัญญาณ / รอที่เรียบง่าย แต่ทรงพลังซึ่ง Mutexes ไม่มีผ่านทางวิธี Monitor.Pulse / Monitor.Wait Win32 ที่เทียบเท่ากันจะเป็นวัตถุเหตุการณ์ผ่าน CreateEvent ซึ่งจริง ๆ แล้วมีอยู่ใน. NET เป็น WaitHandles รูปแบบ Pulse / Wait นั้นคล้ายกับ pthread_signal และ pthread_wait ของ Unix แต่เร็วกว่าเพราะสามารถใช้งานในโหมดผู้ใช้ทั้งหมดในกรณีที่ไม่ได้โต้แย้ง

Monitor.Pulse / Wait นั้นใช้ง่าย ในหนึ่งเธรดเราล็อกวัตถุตรวจสอบค่าสถานะ / สถานะ / คุณสมบัติและถ้าไม่ใช่สิ่งที่เราคาดหวังเรียก Monitor.Wait ซึ่งจะปล่อยล็อกและรอจนกว่าจะส่งชีพจร เมื่อการรอกลับมาเราจะวนกลับและตรวจสอบการตั้งค่าสถานะ / สถานะ / อีกครั้ง ในเธรดอื่นเราล็อกวัตถุเมื่อใดก็ตามที่เราเปลี่ยนค่าสถานะ / สถานะ / คุณสมบัติแล้วเรียก PulseAll เพื่อปลุกเธรดการฟังใด ๆ

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

ฉันไม่แน่ใจเกี่ยวกับการใช้งานการล็อกของ Microsoft แต่ใน DotGNU และ Mono การตั้งค่าสถานะการล็อคถูกเก็บไว้ในส่วนหัวของวัตถุทุกรายการ วัตถุทุกชิ้นใน. NET (และ Java) สามารถล็อคได้ดังนั้นทุกวัตถุต้องรองรับสิ่งนี้ในส่วนหัว ในการปรับใช้ DotGNU จะมีการตั้งค่าสถานะที่อนุญาตให้คุณใช้ hashtable ทั่วโลกสำหรับทุกวัตถุที่ใช้เป็นตัวล็อกซึ่งจะเป็นประโยชน์ในการกำจัดโอเวอร์เฮด 4 ไบต์สำหรับทุกวัตถุ สิ่งนี้ไม่ดีสำหรับหน่วยความจำ (โดยเฉพาะอย่างยิ่งสำหรับระบบฝังตัวที่ไม่ได้มีเธรดมาก) แต่ได้รับความนิยมอย่างมากในประสิทธิภาพ

ทั้ง Mono และ DotGNU ใช้ mutexes อย่างมีประสิทธิภาพในการล็อก / รอ แต่ใช้การดำเนินการเปรียบเทียบและแลกเปลี่ยนสไตล์ spinlock เพื่อขจัดความจำเป็นในการใช้งานการล็อกแบบ hard จริง ๆ เว้นแต่จำเป็นจริงๆ:

คุณสามารถดูตัวอย่างวิธีการใช้งานจอภาพได้ที่นี่:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup


9

ข้อควรระวังเพิ่มเติมสำหรับการล็อค Mutex ที่คุณระบุด้วย ID สตริงนั้นจะเป็นค่าเริ่มต้นเป็น Mutex "Local \" และจะไม่ถูกแชร์ข้ามเซสชันในสภาพแวดล้อมของเทอร์มินัลเซิร์ฟเวอร์

คำนำหน้าตัวระบุสตริงของคุณด้วย "Global \" เพื่อให้แน่ใจว่ามีการควบคุมการเข้าถึงทรัพยากรระบบที่ใช้ร่วมกันอย่างเหมาะสม ฉันเพิ่งพบเจอกับปัญหาทั้งหมดที่ประสานการสื่อสารกับบริการที่ทำงานภายใต้บัญชี SYSTEM ก่อนที่ฉันจะรู้ตัว


5

ฉันจะพยายามหลีกเลี่ยง "lock ()", "Mutex" และ "Monitor" หากคุณสามารถ ...

ลองใช้เนมสเปซใหม่ System.Collections.Concurrent ใน. NET 4
มันมีคลาสคอลเลกชันเซฟเธรดที่ดี

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

หินพร้อมกันพจนานุกรม! ไม่มีการล็อคด้วยตนเองอีกต่อไปสำหรับฉัน!


2
หลีกเลี่ยงการล็อค แต่ใช้จอภาพหรือไม่ ทำไม?
mafu

@mafutrct เพราะคุณต้องดูแลการซิงโครไนซ์ด้วยตัวเอง
Peter Gfader

โอ้ตอนนี้ฉันเข้าใจแล้วคุณตั้งใจจะหลีกเลี่ยงความคิดทั้งสามข้อที่กล่าวถึง ฟังดูเหมือนคุณจะใช้ Monitor แต่ไม่ได้ใช้ lock / Mutex
mafu

ไม่เคยใช้ System.Collections.Concurrent พวกเขาเป็นแหล่งที่มาหลักของเงื่อนไขการแข่งขันและยังบล็อกเธรดผู้โทร
Alexander Danilov

-2

ในกรณีส่วนใหญ่คุณไม่ควรใช้ล็อค (= จอภาพ) หรือ mutexes / semaphores พวกเขาทั้งหมดบล็อกเธรดปัจจุบัน

และคุณไม่ควรใช้ System.Collections.Concurrentคลาสแน่นอน- มันเป็นแหล่งที่มาหลักของเงื่อนไขการแข่งขันเพราะไม่รองรับธุรกรรมระหว่างหลายคอลเลกชันและยังบล็อกเธรดปัจจุบัน

น่าแปลกที่. NET ไม่มีกลไกที่มีประสิทธิภาพสำหรับการซิงโครไนซ์

ฉันใช้คิวอนุกรมจาก GCD ( Objc/Swiftโลก) ใน C # - มีน้ำหนักเบามากไม่บล็อกเครื่องมือซิงโครไนซ์ที่ใช้เธรดพูลพร้อมการทดสอบ

เป็นวิธีที่ดีที่สุดในการซิงโครไนซ์ทุกอย่างในกรณีส่วนใหญ่ - จากการเข้าถึงฐานข้อมูล (สวัสดี sqlite) ถึงตรรกะทางธุรกิจ

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