ระเหยกับลูกโซ่กับล็อค


671

สมมติว่าคลาสมีpublic int counterฟิลด์ที่เข้าถึงได้โดยหลายเธรด นี่intเป็นเพียงการเพิ่มหรือลดเท่านั้น

หากต้องการเพิ่มฟิลด์นี้ควรใช้วิธีการใดและเพราะเหตุใด

  • lock(this.locker) this.counter++;,
  • Interlocked.Increment(ref this.counter);,
  • เปลี่ยนการปรับปรุงการเข้าถึงการcounterpublic volatile

ตอนนี้ที่ผมได้ค้นพบvolatileฉันได้รับการลบหลายงบและการใช้งานของlock Interlockedแต่มีเหตุผลที่จะไม่ทำเช่นนี้หรือไม่?


อ่านการอ้างอิงเธรดใน C # ครอบคลุมคำถามและข้อสงสัยของคุณ แต่ละคนมีวัตถุประสงค์และผลข้างเคียงที่แตกต่างกัน
spoulson

1
simple-talk.com/blogs/2012/01/24/… คุณสามารถเห็นการใช้งานของ volitable ในอาร์เรย์ฉันไม่เข้าใจอย่างสมบูรณ์ แต่มันเป็นการอ้างอิงถึงสิ่งที่มันทำ
eran otzap

50
นี่เหมือนกับการพูดว่า "ฉันค้นพบว่าระบบสปริงเกอร์ไม่เคยเปิดใช้งานดังนั้นฉันจะลบออกและแทนที่ด้วยสัญญาณเตือนควัน" เหตุผลที่จะไม่ทำเช่นนี้เป็นเพราะมันเป็นสิ่งที่อันตรายอย่างไม่น่าเชื่อและช่วยให้คุณเกือบจะไม่มีประโยชน์ หากคุณมีเวลาใช้การเปลี่ยนรหัสลองหาวิธีที่จะทำให้มันแบบมัลติเธรดน้อยลง ! อย่าหาวิธีที่จะทำให้รหัสแบบมัลติเธรดอันตรายและเสียง่ายกว่า!
Eric Lippert

1
บ้านของฉันมีทั้งหัวฉีดน้ำและสัญญาณเตือนควัน เมื่อเพิ่มตัวนับในหนึ่งเธรดและอ่านบนอีกเธรดดูเหมือนว่าคุณต้องการทั้งล็อค (หรืออินเตอร์ล็อค) และคำสำคัญที่ระเหยได้ ความจริง?
yoyo

2
@yoyo ไม่คุณไม่ต้องการทั้งสองอย่าง
David Schwartz

คำตอบ:


859

แย่ที่สุด (ใช้งานไม่ได้จริง)

เปลี่ยน accessifier ของcounterเป็นpublic volatile

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

หากไม่ใช่ volatileและ CPU A เพิ่มค่าหน่วยความจำ CPU B อาจไม่เห็นค่าที่เพิ่มขึ้นจริงจนกระทั่งบางครั้งในภายหลังซึ่งอาจทำให้เกิดปัญหา

หากเป็นvolatileเช่นนี้จะช่วยให้มั่นใจว่าทั้งสองซีพียูสามารถดูข้อมูลเดียวกันได้ในเวลาเดียวกัน มันไม่ได้หยุดพวกเขาจากการสอดแทรกการอ่านและการเขียนซึ่งเป็นปัญหาที่คุณพยายามหลีกเลี่ยง

อันดับสอง:

lock(this.locker) this.counter++;

วิธีนี้ปลอดภัยที่จะทำ (หากคุณจำได้ในlockทุกที่ที่คุณเข้าถึงthis.counter) จะป้องกันไม่ให้กระทู้อื่น ๆ lockerจากการดำเนินการรหัสอื่นใดที่จะรักษาโดย การใช้ตัวล็อคยังช่วยป้องกันปัญหาการเรียงลำดับใหม่ของ CPU แบบหลายจุดซึ่งดีมาก

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

ดีที่สุด

Interlocked.Increment(ref this.counter);

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

ฉันไม่แน่ใจทั้งหมด แต่ถ้ามันได้รับรอบ CPU อื่น ๆ เรียงลำดับสิ่งต่าง ๆ หรือถ้าคุณยังต้องรวมความผันผวนกับการเพิ่มขึ้น

InterlockedNotes:

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

เชิงอรรถ: ความผันผวนที่ดีจริงๆ

เช่นเดียวกับที่volatileไม่ได้ป้องกันปัญหาการมัลติเธรดเหล่านี้มันมีไว้เพื่ออะไร? ตัวอย่างที่ดีคือการพูดว่าคุณมีสองกระทู้หนึ่งซึ่งมักจะเขียนถึงตัวแปร (พูดqueueLength) และหนึ่งที่มักจะอ่านจากตัวแปรเดียวกันนั้น

หากqueueLengthไม่ระเหยเธรด A อาจเขียนห้าครั้ง แต่เธรด B อาจเห็นการเขียนเหล่านั้นว่าล่าช้า (หรืออาจเกิดขึ้นในลำดับที่ไม่ถูกต้อง)

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


29
"ฉันไม่แน่ใจทั้งหมด ... หากคุณต้องการรวมความผันผวนกับการเพิ่มขึ้น" ไม่สามารถรวม AFAIK ได้เนื่องจากเราไม่สามารถผ่านการระเหยได้โดยการอ้างอิง คำตอบที่ดีโดยวิธีการ
Hosam Aly

41
ขอบคุณมาก! เชิงอรรถของคุณเกี่ยวกับ "สิ่งที่ระเหยได้ดีจริง ๆ " คือสิ่งที่ฉันกำลังมองหาและยืนยันว่าฉันต้องการใช้สารระเหยอย่างไร
Jacques Bosch

7
กล่าวอีกนัยหนึ่งถ้า var ถูกประกาศว่ามีความผันผวนคอมไพเลอร์จะคิดว่าค่าของ var จะไม่เหมือนเดิม (เช่น volatile) ในแต่ละครั้งที่โค้ดของคุณเจอ ดังนั้นในลูปเช่น: ในขณะที่ (m_Var) {} และ m_Var ถูกตั้งค่าเป็นเท็จในเธรดอื่นคอมไพเลอร์จะไม่ตรวจสอบสิ่งที่อยู่ในการลงทะเบียนที่โหลดค่า m_Var ไว้ก่อนหน้านี้แล้ว แต่อ่านค่าจาก m_Var อีกครั้ง อย่างไรก็ตามมันไม่ได้หมายความว่าการไม่ประกาศความผันผวนจะทำให้วนรอบดำเนินไปเรื่อย ๆ - การระบุความผันผวนจะรับประกันได้ว่าจะไม่เกิดขึ้นหาก m_Var ถูกตั้งค่าเป็นเท็จในเธรดอื่น
Zach Saw

35
@Zach Saw: ภายใต้โมเดลหน่วยความจำสำหรับ C ++, ความผันผวนเป็นวิธีที่คุณอธิบาย (โดยทั่วไปมีประโยชน์สำหรับหน่วยความจำที่แมปอุปกรณ์และไม่มาก) ภายใต้รูปแบบหน่วยความจำสำหรับCLR (คำถามนี้ถูกแท็ก C #) คือความผันผวนจะแทรกอุปสรรคหน่วยความจำรอบการอ่านและเขียนไปยังที่เก็บข้อมูล อุปสรรคหน่วยความจำ (และรูปแบบการล็อคพิเศษของคำแนะนำการประกอบบางส่วน) เป็นคุณที่คุณบอกโปรเซสเซอร์ไม่เรียงลำดับสิ่งต่าง ๆ และพวกเขาค่อนข้างสำคัญ ...
Orion Edwards

19
@ZachSaw: เขตข้อมูลระเหยใน C # ป้องกันไม่ให้คอมไพเลอร์ C # และคอมไพเลอร์ jit ทำการปรับให้เหมาะสมที่จะแคชค่า นอกจากนี้ยังรับประกันบางอย่างเกี่ยวกับสิ่งที่คำสั่งการอ่านและการเขียนอาจสังเกตได้ว่าอยู่ในหลายเธรด ในฐานะที่เป็นรายละเอียดการใช้งานมันอาจทำเช่นนั้นโดยการแนะนำอุปสรรคหน่วยความจำในการอ่านและเขียน ความหมายที่แน่นอนรับประกันถูกอธิบายไว้ในข้อมูลจำเพาะ; โปรดทราบว่าสเปคไม่ได้รับประกันว่าการเรียงลำดับที่สอดคล้องกันของการเขียนและอ่านแบบระเหยทั้งหมดจะถูกตรวจสอบโดยเธรดทั้งหมด
Eric Lippert

147

แก้ไข:ตามที่ระบุไว้ในความคิดเห็นวันนี้ฉันยินดีที่จะใช้Interlockedสำหรับกรณีของตัวแปรเดียวที่เห็นได้ชัดว่าโอเค เมื่อมันซับซ้อนมากขึ้นฉันจะยังคงล็อค ...

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

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


16
+1 ที่ทำให้ฉันลืมการเข้ารหัสที่ไม่มีการล็อคนับจากนี้
Xaqron

5
รหัสล็อคฟรีนั้นไม่ล็อคฟรีอย่างแน่นอนเพราะพวกเขาล็อคในบางช่วง - ไม่ว่าจะเป็นที่รถบัส (FSB) หรือระดับ InterCPU ยังมีค่าปรับที่คุณต้องจ่าย อย่างไรก็ตามการล็อคในระดับที่ต่ำกว่าเหล่านี้โดยทั่วไปจะเร็วกว่าตราบใดที่คุณไม่อิ่มตัวกับแบนด์วิดท์ที่เกิดล็อค
Zach Saw

2
ไม่มีอะไรผิดปกติกับ Interlocked เป็นสิ่งที่คุณกำลังมองหาและเร็วกว่าการล็อคเต็มรูปแบบ ()
Jaap

5
@ Jaa: ใช่วันนี้ฉันจะใช้ interlocked สำหรับเคาน์เตอร์เดียวของแท้ ผมแค่ไม่อยากจะเริ่มต้น messing รอบพยายามที่จะทำงานออกจากการมีปฏิสัมพันธ์ระหว่างหลายอัปเดตล็อคฟรีกับตัวแปร
Jon Skeet

6
@ ZachSaw: ความคิดเห็นที่สองของคุณบอกว่าการดำเนินการ interlocked "ล็อค" ในบางช่วง คำว่า "ล็อค" โดยทั่วไปแสดงว่างานหนึ่งสามารถรักษาการควบคุมทรัพยากรแบบเอกสิทธิ์เฉพาะบุคคลสำหรับระยะเวลาที่ไม่ จำกัด ข้อได้เปรียบหลักของการเขียนโปรแกรมฟรีล็อคคือมันหลีกเลี่ยงอันตรายจากทรัพยากรกลายเป็นใช้ไม่ได้เป็นผลมาจากการเป็นเจ้าของงานรับ waylaid การซิงโครไนซ์บัสที่ใช้โดยคลาส interlocked ไม่ใช่เพียง "เร็วกว่าปกติ" - ในระบบส่วนใหญ่จะมีเวลาที่เลวร้ายที่สุดในกรณีที่ จำกัด ในขณะที่ล็อคไม่ได้
supercat

44

" volatile" ไม่ได้แทนที่Interlocked.Increment! มันทำให้แน่ใจว่าตัวแปรไม่ได้ถูกแคช แต่ใช้โดยตรง

การเพิ่มตัวแปรต้องใช้การปฏิบัติการจริงสามอย่าง:

  1. อ่าน
  2. การเพิ่มขึ้น
  3. เขียน

Interlocked.Increment ดำเนินการทั้งสามส่วนเป็นการดำเนินการปรมาณูเดียว


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

1
ที่จริงแล้วvolatileไม่ได้ตรวจสอบให้แน่ใจตัวแปรที่ไม่ได้เก็บไว้ชั่วคราว มันเพียงแค่วางข้อ จำกัด เกี่ยวกับวิธีการแคช ตัวอย่างเช่นมันยังคงถูกแคชในสิ่งที่แคช L2 ของ CPU เพราะพวกเขาทำกันในฮาร์ดแวร์ มันยังคงสามารถสร้างได้ล่วงหน้า การเขียนยังสามารถโพสต์ไปยังแคชและอื่น ๆ (ซึ่งฉันคิดว่าเป็นสิ่งที่ Zach กำลังทำอยู่)
David Schwartz

42

การล็อคหรือการเพิ่มอินเตอร์ล็อคคือสิ่งที่คุณกำลังมองหา

ความผันผวนไม่ได้เป็นอย่างที่คุณต้องการ - เพียงแค่บอกคอมไพเลอร์ให้จัดการกับตัวแปรที่เปลี่ยนแปลงตลอดเวลาแม้ว่าเส้นทางรหัสปัจจุบันจะอนุญาตให้คอมไพเลอร์ปรับการอ่านจากหน่วยความจำให้เหมาะสม

เช่น

while (m_Var)
{ }

หาก m_Var ถูกตั้งค่าเป็น false ในเธรดอื่น แต่ไม่ได้ประกาศว่า volatile คอมไพเลอร์มีอิสระที่จะทำให้วนรอบไม่สิ้นสุด (แต่ไม่ได้หมายความว่ามันจะเสมอ) โดยการตรวจสอบกับ CPU register (เช่น EAX เพราะนั่นคือ สิ่งที่ m_Var ถูกดึงเข้ามาตั้งแต่ต้น) แทนที่จะปล่อยให้อ่านอีกครั้งไปยังตำแหน่งหน่วยความจำของ m_Var (ซึ่งอาจถูกแคช - เราไม่รู้และไม่สนใจและนั่นคือจุดเชื่อมโยงแคชของ x86 / x64) โพสต์ทั้งหมดก่อนหน้านี้โดยคนอื่น ๆ ที่กล่าวถึงการจัดเรียงคำสั่งใหม่เพียงแค่แสดงว่าพวกเขาไม่เข้าใจสถาปัตยกรรม x86 / x64 สารระเหยไม่ได้ปัญหาการอ่าน / เขียนอุปสรรคตามที่ระบุไว้โดยโพสต์ก่อนหน้าว่า 'มันป้องกันการเรียงลำดับใหม่' ในความเป็นจริงแล้วขอบคุณอีกครั้งสำหรับโปรโตคอล MESI เราจึงมั่นใจได้ว่าผลลัพธ์ที่เราอ่านจะเหมือนกันตลอดทั้งซีพียูโดยไม่คำนึงว่าผลลัพธ์ที่แท้จริงได้ถูกยกเลิกไปยังหน่วยความจำทางกายภาพหรือเพียงแค่อยู่ในแคชของ CPU ท้องถิ่น ฉันจะไม่เข้าไปดูรายละเอียดมากไปกว่านี้ แต่มั่นใจได้เลยว่าถ้ามันผิดพลาด Intel / AMD น่าจะออกตัวประมวลผล! นอกจากนี้ยังหมายความว่าเราไม่ต้องสนใจเรื่องการดำเนินการตามคำสั่ง ฯลฯ ผลลัพธ์จะรับประกันว่าจะออกตามลำดับเสมอ - ไม่เช่นนั้นเราจะถูกยัดไว้!

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

ด้วยความผันผวนคุณจะยังคงจบลงด้วยการเรียนการสอนเพียง 1 ครั้ง (สมมติว่า JIT นั้นมีประสิทธิภาพอย่างที่ควรจะเป็น) - inc dword ptr [m_Var] อย่างไรก็ตามตัวประมวลผล (cpuA) ไม่ขอความเป็นเจ้าของเฉพาะของบรรทัดแคชในขณะที่ทำทุกอย่างที่ทำกับเวอร์ชันที่เชื่อมต่อกัน ดังที่คุณสามารถจินตนาการได้นั่นหมายความว่าโปรเซสเซอร์อื่น ๆ สามารถเขียนค่าที่ปรับปรุงแล้วกลับไปที่ m_Var หลังจาก cpuA อ่านแล้ว ดังนั้นแทนที่จะเพิ่มค่าสองครั้งคุณจะได้เพียงครั้งเดียว

หวังว่าจะช่วยแก้ปัญหานี้ได้

สำหรับข้อมูลเพิ่มเติมดู 'ทำความเข้าใจกับผลกระทบของเทคนิคล็อคต่ำในแอพแบบมัลติเธรด' - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

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

pps ฉันสมมติว่าเป้าหมายคือ x86 / x64 และไม่ใช่ IA64 (มีรูปแบบหน่วยความจำที่แตกต่างกัน) โปรดทราบว่ารายละเอียด ECMA ของ Microsoft นั้นถูกทำให้แน่นด้วยการระบุรุ่นหน่วยความจำที่อ่อนแอที่สุดแทนที่จะเป็นรุ่นที่แข็งแกร่งที่สุด (จะดีกว่าเสมอเมื่อเทียบกับรุ่นหน่วยความจำที่แข็งแกร่งที่สุดดังนั้นจึงสอดคล้องกันในทุกแพลตฟอร์ม - รหัสอื่น ๆ x64 อาจไม่ทำงานเลยใน IA64 แม้ว่า Intel ได้ใช้โมเดลหน่วยความจำที่แข็งแกร่งเช่นเดียวกันสำหรับ IA64) - Microsoft ยอมรับด้วยตัวเอง - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx .


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

หากคุณสามารถชี้ส่วนที่คุณต้องการอ้างอิงฉันยินดีที่จะขุดบางสิ่งจากที่ไหนสักแห่ง (ฉันสงสัยอย่างมากว่าฉันได้ให้ความลับทางการค้าของผู้จำหน่าย x86 / x64 ไปดังนั้นสิ่งเหล่านี้ควรจะพร้อมใช้งานได้อย่างง่ายดาย wiki, Intel PRMs (คู่มืออ้างอิงของโปรแกรมเมอร์), บล็อก MSFT, MSDN หรือบางอย่างที่คล้ายกัน) ...
Zach Saw

2
ทำไมทุกคนต้องการป้องกัน CPU จากการแคชอยู่นอกเหนือฉัน อสังหาริมทรัพย์ทั้งหมด (แน่นอนว่าไม่ได้มีขนาดและค่าใช้จ่ายน้อยมาก) ที่ทุ่มเทให้กับการเชื่อมโยงกันของแคชจะสูญเปล่าถ้าเป็นกรณีนี้ ... เว้นแต่คุณจะไม่ต้องการการเชื่อมโยงกันของแคชเช่นการ์ดกราฟิก, อุปกรณ์ PCI เป็นต้น บรรทัดแคชเพื่อเขียนข้อมูล
Zach Saw

4
ใช่ทุกอย่างที่คุณพูดคือถ้าไม่ใช่ 100% อย่างน้อย 99% บนเครื่องหมาย ไซต์นี้ (ส่วนใหญ่) มีประโยชน์พอสมควรเมื่อคุณเร่งรีบในการพัฒนางาน แต่น่าเสียดายที่ความแม่นยำของคำตอบที่สอดคล้องกับการโหวต (เกม) นั้นไม่ได้มีอยู่ ดังนั้นโดยทั่วไปใน stackoverflow คุณจะได้รับความรู้สึกของความเข้าใจที่เป็นที่นิยมของผู้อ่านไม่ใช่สิ่งที่มันเป็น บางครั้งคำตอบอันดับต้นเป็นเพียงพูดพล่อยๆที่บริสุทธิ์ - ตำนานของชนิด และน่าเสียดายที่นี่คือสิ่งที่แพร่กระจายไปสู่คนที่เจอปัญหาการอ่านขณะที่แก้ไขปัญหา แม้ว่าจะเข้าใจได้ แต่ก็ไม่มีใครรู้ทุกอย่าง
user1416420

1
@ BenVoigt ฉันสามารถไปและตอบเกี่ยวกับสถาปัตยกรรมทั้งหมด. NET ทำงานบน แต่ที่จะใช้เวลาไม่กี่หน้าและแน่นอนไม่เหมาะสำหรับ SO เป็นการดีกว่าที่จะให้การศึกษาแก่ผู้ใช้แบบจำลองฮาร์ดแวร์ mem. NET พื้นฐานที่ใช้กันอย่างแพร่หลายมากกว่ารุ่นที่ไม่มีกฎเกณฑ์ และด้วยความคิดเห็นของฉัน 'ทุกหนทุกแห่ง' ฉันได้แก้ไขข้อผิดพลาดที่ผู้คนทำขึ้นโดยสมมติว่าการล้าง / ตรวจสอบแคชเป็นต้นพวกเขาตั้งสมมติฐานเกี่ยวกับฮาร์ดแวร์พื้นฐานโดยไม่ระบุฮาร์ดแวร์ใด
Zach Saw

16

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

ฉันจะบอกว่าคุณควรจะชอบมันเพื่อล็อคและเพิ่มขึ้น

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

นี่เป็นบทความที่ดีมากหากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับรหัสล็อคฟรีและวิธีที่ถูกต้องในการเขียนมัน

http://www.ddj.com/hpc-high-performance-computing/210604448


11

การล็อก (... ) ใช้งานได้ แต่อาจบล็อกเธรดและอาจทำให้เกิดการหยุดชะงักหากรหัสอื่นกำลังใช้การล็อกเดียวกันในวิธีที่เข้ากันไม่ได้

Interlocked. * เป็นวิธีที่ถูกต้องในการทำ ... ค่าโสหุ้ยน้อยลงเนื่องจาก CPU ที่ทันสมัยรองรับสิ่งนี้เป็นแบบดั้งเดิม

การระเหยด้วยตัวมันเองนั้นไม่ถูกต้อง เธรดที่พยายามดึงข้อมูลและเขียนกลับมาแก้ไขค่าอาจขัดแย้งกับเธรดอื่นที่ทำเช่นเดียวกัน


8

ผมได้ทดสอบบางอย่างเพื่อดูว่าทฤษฎีที่ใช้งานได้จริง: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html การทดสอบของฉันมุ่งเน้นที่ CompareExchnage มากขึ้น แต่ผลลัพธ์สำหรับการเพิ่มจะคล้ายกัน Interlocked ไม่จำเป็นต้องเร็วขึ้นในสภาพแวดล้อมแบบ multi-cpu นี่คือผลการทดสอบสำหรับการเพิ่มขึ้นในเซิร์ฟเวอร์ซีพียูอายุ 16 ปี 2 ปี อย่าลืมว่าการทดสอบนั้นเกี่ยวข้องกับการอ่านอย่างปลอดภัยหลังจากเพิ่มมากขึ้นซึ่งเป็นเรื่องปกติในโลกแห่งความเป็นจริง

D:\>InterlockVsMonitor.exe 16
Using 16 threads:
          InterlockAtomic.RunIncrement         (ns):   8355 Average,   8302 Minimal,   8409 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):   7077 Average,   6843 Minimal,   7243 Maxmial

D:\>InterlockVsMonitor.exe 4
Using 4 threads:
          InterlockAtomic.RunIncrement         (ns):   4319 Average,   4319 Minimal,   4321 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):    933 Average,    802 Minimal,   1018 Maxmial

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

@Zach การอภิปรายที่นี่เกี่ยวกับสถานการณ์ของการเพิ่มตัวนับในลักษณะที่ปลอดภัยของเธรดได้อย่างไร สถานการณ์การใช้งานอื่น ๆ ในใจของคุณหรือคุณจะทดสอบอย่างไร ขอบคุณสำหรับความคิดเห็น BTW
Kenneth Xu

ประเด็นก็คือมันเป็นการทดสอบประดิษฐ์ คุณจะไม่ตอกย้ำตำแหน่งเดิมที่มักเกิดขึ้นในโลกแห่งความเป็นจริง ถ้าคุณเป็นเช่นนั้นคุณกำลังติดขัดโดย FSB (ดังที่แสดงในกล่องเซิร์ฟเวอร์ของคุณ) อย่างไรก็ตามดูที่คำตอบของฉันในบล็อกของคุณ
Zach Saw

2
มองย้อนกลับไปอีกครั้ง หากคอขวดที่แท้จริงอยู่กับ FSB การใช้งานจอภาพควรสังเกตคอขวดที่เหมือนกัน ความแตกต่างที่แท้จริงคือ Interlocked กำลังยุ่งรอและลองใหม่ซึ่งกลายเป็นปัญหาจริงด้วยการนับประสิทธิภาพสูง อย่างน้อยฉันหวังว่าความคิดเห็นของฉันจะเพิ่มความสนใจว่าการใช้อินเตอร์ล็อคไม่ใช่ตัวเลือกที่เหมาะสมสำหรับการนับ ความจริงที่ผู้คนกำลังมองหาทางเลือกอื่นอธิบายได้ดี คุณต้องการ adder ยาวgee.cs.oswego.edu/dl/jsr166/dist/jsr166edocs/jsr166e/ …
Kenneth Xu

8

3

ฉันต้องการที่จะเพิ่มการกล่าวถึงในคำตอบอื่น ๆ ความแตกต่างระหว่างvolatile, Interlockedและlock:

คำสำคัญระเหยสามารถนำไปใช้กับสาขาประเภทเหล่านี้ :

  • ประเภทอ้างอิง
  • ประเภทตัวชี้ (ในบริบทที่ไม่ปลอดภัย) โปรดทราบว่าแม้ว่าตัวชี้สามารถผันผวนได้ แต่วัตถุที่ชี้ไปไม่สามารถทำได้ กล่าวอีกนัยหนึ่งคุณไม่สามารถประกาศ "ตัวชี้" เป็น "เปลี่ยนแปลงได้"
  • ประเภทง่ายๆเช่นsbyte, byte, short, ushort, int, uint, char, และfloatbool
  • ประเภท enum กับประเภทใดประเภทหนึ่งฐานต่อไปนี้: byte, sbyte, short, ushort, หรือintuint
  • พารามิเตอร์ประเภททั่วไปที่รู้จักกันว่าเป็นประเภทอ้างอิง
  • IntPtrUIntPtrและ

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

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