ความจำเป็นในการปรับเปลี่ยนการระเหยในการล็อกที่ตรวจสอบซ้ำใน. NET


85

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

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

ทำไม "ล็อค (syncRoot)" ไม่บรรลุความสอดคล้องของหน่วยความจำที่จำเป็น เป็นความจริงหรือไม่ว่าหลังจากคำสั่ง "ล็อก" ทั้งอ่านและเขียนจะมีความผันผวนดังนั้นความสอดคล้องที่จำเป็นจึงจะสำเร็จ?


2
นี่เคี้ยวมาหลายรอบแล้ว yoda.arachsys.com/csharp/singleton.html
Hans Passant

1
น่าเสียดายที่ในบทความนั้น Jon อ้างถึง 'volatile' สองครั้งและการอ้างอิงโดยตรงไม่ได้อ้างอิงถึงตัวอย่างโค้ดที่เขาให้มา
Dan Esparza

ดูบทความนี้เพื่อทำความเข้าใจกับข้อกังวล: igoro.com/archive/volatile-keyword-in-c-memory-model-explained โดยทั่วไปแล้ว JIT จะใช้การลงทะเบียน CPU สำหรับตัวแปรอินสแตนซ์โดยเฉพาะอย่างยิ่งหากคุณต้องการ รหัสพิเศษเล็กน้อยในนั้น ดังนั้นการทำคำสั่ง if สองครั้งอาจส่งคืนค่าเดียวกันได้โดยไม่คำนึงถึงการเปลี่ยนแปลงในเธรดอื่น ในความเป็นจริงคำตอบนั้นซับซ้อนเล็กน้อยคำสั่งล็อคอาจหรือไม่รับผิดชอบในการทำให้สิ่งต่างๆดีขึ้นที่นี่ (ต่อ)
user2685937

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

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

คำตอบ:


60

ระเหยไม่จำเป็น เรียงลำดับจาก **

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

โจเซฟอัลบาฮารีอธิบายเรื่องนี้ได้ดีกว่าที่ฉันเคยทำได้

และอย่าลืมดูคู่มือของ Jon Skeet ในการปรับใช้ Singletonใน C #


update :
* volatileทำให้การอ่านตัวแปรเป็นVolatileReads และเขียนเป็นVolatileWrites ซึ่งบน x86 และ x64 บน CLR จะถูกนำไปใช้กับMemoryBarrier. อาจมีรายละเอียดปลีกย่อยในระบบอื่น ๆ

** คำตอบของฉันถูกต้องก็ต่อเมื่อคุณใช้ CLR บนโปรเซสเซอร์ x86 และ x64 มันอาจจะเป็นจริงในรูปแบบหน่วยความจำอื่น ๆ เช่นในโมโน (และการใช้งานอื่น ๆ ) Itanium64 และฮาร์ดแวร์ในอนาคต นี่คือสิ่งที่จอนอ้างถึงในบทความของเขาใน "gotchas" สำหรับการล็อกแบบตรวจสอบซ้ำ

การทำอย่างใดอย่างหนึ่งใน {ทำเครื่องหมายตัวแปรเป็นvolatileอ่านด้วยThread.VolatileReadหรือแทรกคำเรียกไปThread.MemoryBarrier} อาจจำเป็นเพื่อให้โค้ดทำงานได้อย่างถูกต้องในสถานการณ์โมเดลหน่วยความจำที่อ่อนแอ

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

ฉันพบว่าบทความเหล่านี้มีประโยชน์:
http://www.codeproject.com/KB/tips/MemoryBarrier.aspx
บทความของ vance morrison (ทุกอย่างเชื่อมโยงไปถึงสิ่งนี้พูดถึงการล็อกแบบตรวจสอบสองครั้ง)
บทความของ chris brumme (ทุกอย่างเชื่อมโยงไปยังสิ่งนี้ )
โจดัฟฟี่: รูปแบบที่แตกต่างของการล็อกที่ถูกตรวจสอบสองครั้ง

ซีรีส์ของ luis abreu เกี่ยวกับมัลติเธรดให้ภาพรวมที่ดีของแนวคิดเช่นกัน
http://msmvps.com/blogs/luisabreu/archive/2009/06/29/multithreading-load-and-store-reordering.aspx
http://msmvps com / blogs / luisabreu / archive / 2009/07/03 / มัลติเธรดแนะนำหน่วยความจำ fences.aspx


จริง ๆ แล้ว Jon Skeet กล่าวว่าจำเป็นต้องมีตัวปรับเปลี่ยนแบบระเหยเพื่อสร้าง Barier หน่วยความจำที่เหมาะสมในขณะที่ผู้เขียนลิงค์แรกบอกว่าล็อค (Monitor.Enter) ก็เพียงพอแล้ว ใครถูกจริง ???
Konstantin

@ คอนสแตนตินดูเหมือนว่าจอนจะอ้างถึงโมเดลหน่วยความจำบนโปรเซสเซอร์ Itanium 64 ดังนั้นในกรณีนี้อาจจำเป็นต้องใช้การระเหย อย่างไรก็ตามการระเหยไม่จำเป็นในโปรเซสเซอร์ x86 และ x64 ฉันจะอัปเดตเพิ่มเติมในอีกเล็กน้อย
แดน

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

2
คำตอบนี้ดูผิดสำหรับฉัน หากvolatileไม่จำเป็นบนแพลตฟอร์มใด ๆก็จะหมายความว่า JIT ไม่สามารถปรับการโหลดหน่วยความจำobject s1 = syncRoot; object s2 = syncRoot;ให้เหมาะสมobject s1 = syncRoot; object s2 = s1;บนแพลตฟอร์มนั้นได้ นั่นดูไม่น่าเป็นไปได้สำหรับฉัน
user541686

1
แม้ว่า CLR จะไม่เรียงลำดับการเขียนใหม่ (ฉันสงสัยว่าเป็นเรื่องจริง แต่การเพิ่มประสิทธิภาพที่ดีหลายอย่างสามารถทำได้โดยการทำเช่นนั้น) มันก็ยังคงเป็นบั๊กตราบเท่าที่เราสามารถแทรกการเรียกตัวสร้างและสร้างวัตถุในตำแหน่งได้ (เราเห็นวัตถุเริ่มต้นครึ่งหนึ่ง) ไม่ขึ้นกับหน่วยความจำรุ่นใด ๆ ที่ CPU ใช้อยู่! ตามที่ Eric Lippert CLR บน Intel อย่างน้อยก็แนะนำ membarrier หลังจากตัวสร้างซึ่งปฏิเสธการปรับให้เหมาะสม แต่นั่นไม่จำเป็นสำหรับข้อมูลจำเพาะและฉันจะไม่นับสิ่งเดียวกันที่เกิดขึ้นบน ARM เช่น ..
Voo

34

มีวิธีการนำไปใช้โดยไม่ต้องลงvolatileสนาม ฉันจะอธิบายมัน ...

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

public sealed class Singleton
{
   private static Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         // very fast test, without implicit memory barriers or locks
         if (instance == null)
         {
            lock (syncRoot)
            {
               if (instance == null)
               {
                    var temp = new Singleton();

                    // ensures that the instance is well initialized,
                    // and only then, it assigns the static variable.
                    System.Threading.Thread.MemoryBarrier();
                    instance = temp;
               }
            }
         }

         return instance;
      }
   }
}

ทำความเข้าใจเกี่ยวกับรหัส

ลองนึกภาพว่ามีรหัสเริ่มต้นบางอย่างอยู่ในตัวสร้างของคลาส Singleton หากคำสั่งเหล่านี้ถูกจัดเรียงใหม่หลังจากที่ตั้งค่าฟิลด์ด้วยที่อยู่ของออบเจ็กต์ใหม่แสดงว่าคุณมีอินสแตนซ์ที่ไม่สมบูรณ์ ... ลองนึกภาพว่าคลาสมีรหัสนี้:

private int _value;
public int Value { get { return this._value; } }

private Singleton()
{
    this._value = 1;
}

ลองนึกภาพการโทรไปยังผู้สร้างโดยใช้ตัวดำเนินการใหม่:

instance = new Singleton();

สิ่งนี้สามารถขยายไปสู่การดำเนินการเหล่านี้ได้:

ptr = allocate memory for Singleton;
set ptr._value to 1;
set Singleton.instance to ptr;

จะเกิดอะไรขึ้นถ้าฉันเรียงลำดับคำแนะนำเหล่านี้ใหม่เช่นนี้:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;

มันสร้างความแตกต่างหรือไม่? ไม่ถ้าคุณคิดว่ามีเธรดเดียว ใช่ถ้าคุณคิดถึงหลายเธรด ... จะเกิดอะไรขึ้นถ้าเธรดถูกขัดจังหวะหลังจากset instance to ptr:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
-- thread interruped here, this can happen inside a lock --
set ptr._value to 1; -- Singleton.instance is not completelly initialized

นั่นคือสิ่งที่อุปสรรคหน่วยความจำหลีกเลี่ยงโดยไม่อนุญาตให้จัดลำดับการเข้าถึงหน่วยความจำใหม่:

ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
-- memory barrier... cannot reorder writes after this point, or reads before it --
-- Singleton.instance is still null --
set Singleton.instance to temp;

ขอให้สนุกกับการเขียนโค้ด!


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

1
@MichaelGG: ในกรณีที่คุณอธิบายถ้าคลาสนั้นรองรับหลายเธรดเพื่อเข้าถึงมันก็เป็นปัญหา หากการเรียกใช้คอนสตรัคเตอร์ถูกแทรกโดย jitter ก็เป็นไปได้ที่ CPU จะเรียงลำดับคำแนะนำใหม่ในลักษณะที่ข้อมูลอ้างอิงที่จัดเก็บไว้ชี้ไปยังอินสแตนซ์ที่ไม่ได้เริ่มต้นอย่างสมบูรณ์ นี่ไม่ใช่ปัญหาด้านความปลอดภัย CLR เนื่องจากสามารถหลีกเลี่ยงได้เป็นความรับผิดชอบของโปรแกรมเมอร์ในการใช้: เชื่อมต่อกันอุปสรรคหน่วยความจำล็อคและ / หรือฟิลด์ที่ระเหยได้ภายในตัวสร้างของคลาสดังกล่าว
Miguel Angelo

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

นี่คือ "รูปแบบทางเลือก" ที่ ReSharper 2016/2017 แนะนำในกรณีของ DCL ใน C # OTOH, Java ไม่รับประกันว่าผลจากการnewเริ่มต้นได้อย่างเต็มที่ ..
user2864740

ฉันรู้ว่าการใช้งาน MS .net วางอุปสรรคหน่วยความจำไว้ที่ส่วนท้ายของ construtor ...
Miguel Angelo

7

ฉันไม่คิดว่าจะมีใครตอบคำถามจริงๆฉันจะลองดู

สิ่งที่ผันผวนและครั้งแรกif (instance == null)ไม่ "จำเป็น" การล็อกจะทำให้รหัสนี้ปลอดภัย

คำถามคือทำไมคุณถึงเพิ่มครั้งแรกif (instance == null)?

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

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

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

ดังนั้นคุณจึงทำให้ซิงเกิลตันมีความผันผวนเพื่อให้แน่ใจว่าคุณอ่านค่าล่าสุดโดยไม่จำเป็นต้องใช้ตัวล็อก

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

ตอนนี้สิ่งนี้มีประโยชน์จริงหรือ

ฉันจะบอกว่า "ในกรณีส่วนใหญ่ไม่"

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

กรณีเดียวที่ฉันคิดได้ว่าการแคชนี้จะเป็นไปไม่ได้เมื่อคุณมีเธรดจำนวนมาก (เช่นเซิร์ฟเวอร์ที่ใช้เธรดใหม่ในการประมวลผลทุกคำขออาจสร้างเธรดที่ทำงานสั้นมากนับล้านเธรด ซึ่งจะต้องเรียก Singleton.Instance ครั้งเดียว)

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


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

ฮะ? ฉันไม่เห็นว่าคำพูดของคุณไม่เห็นด้วยกับสิ่งที่ฉันพูดไปตรงไหนและฉันไม่ได้บอกว่าการระเหยนั้นเกี่ยวข้องกับความหมายของการล็อก
Jason Williams

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

1
จะทำให้ไม่ปลอดภัยได้อย่างไรหากinstanceถูกทำเครื่องหมายvolatile?
UserControl

5

คุณควรใช้ volatile กับรูปแบบ double check lock

คนส่วนใหญ่ชี้ว่าบทความนี้เป็นข้อพิสูจน์ว่าคุณไม่จำเป็นต้องมีความผันผวน: https://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10

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

หากคุณต้องการความน่าเชื่อถือมากขึ้นโปรดอ่านบทความนี้เกี่ยวกับข้อมูลจำเพาะ ECMA ที่จะใช้สำหรับแพลตฟอร์มอื่น ๆ : msdn.microsoft.com/en-us/magazine/jj863136.aspx

หากคุณต้องการความน่าเชื่อถือเพิ่มเติมโปรดอ่านบทความที่ใหม่กว่านี้ซึ่งอาจมีการเพิ่มประสิทธิภาพเพื่อป้องกันไม่ให้ทำงานโดยไม่ระเหย: msdn.microsoft.com/en-us/magazine/jj883956.aspx

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


1
คำตอบที่ดี. คำตอบเดียวที่ถูกต้องสำหรับคำถามนี้คือ "ไม่" ตามนี้คำตอบที่ยอมรับนั้นผิด
Dennis Kassel

3

AFAIK (และ - โปรดใช้ความระมัดระวังฉันไม่ได้ทำหลายอย่างพร้อมกัน) การล็อคจะช่วยให้คุณซิงโครไนซ์ระหว่างคู่แข่งหลายราย (เธรด)

ในทางกลับกันความผันผวนจะบอกให้เครื่องของคุณประเมินค่าใหม่ทุกครั้งเพื่อที่คุณจะได้ไม่สะดุดกับค่าแคช (และผิด)

โปรดดูhttp://msdn.microsoft.com/en-us/library/ms998558.aspxและสังเกตคำพูดต่อไปนี้:

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

คำอธิบายของสารระเหย: http://msdn.microsoft.com/en-us/library/x13ttww7%28VS.71%29.aspx


2
'ล็อค' ยังมีเกราะป้องกันหน่วยความจำเช่นเดียวกับ (หรือดีกว่า) ระเหย
Henk Holterman

2

ฉันคิดว่าฉันได้พบสิ่งที่ฉันกำลังมองหาแล้ว โดยมีรายละเอียดในบทความนี้ - http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10

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


1
ที่ด้านล่างสุดของบทความนั้นอ่านอย่างละเอียดโดยเฉพาะประโยคสุดท้ายที่ผู้เขียนระบุว่า: "คำเตือนคำสุดท้าย - ฉันคาดเดาเฉพาะโมเดลหน่วยความจำ x86 จากพฤติกรรมที่สังเกตได้ของโปรเซสเซอร์ที่มีอยู่ดังนั้นเทคนิคการล็อกต่ำจึงเปราะบางเช่นกันเพราะ ฮาร์ดแวร์และคอมไพเลอร์อาจมีความก้าวร้าวมากขึ้นเมื่อเวลาผ่านไปต่อไปนี้เป็นกลยุทธ์บางประการในการลดผลกระทบของความเปราะบางนี้ต่อรหัสของคุณขั้นแรกหลีกเลี่ยงเทคนิคการล็อคต่ำเมื่อใดก็ตามที่เป็นไปได้ (... ) สุดท้ายให้ถือว่าโมเดลหน่วยความจำที่อ่อนแอที่สุด โดยใช้การประกาศที่ผันผวนแทนที่จะพึ่งพาการค้ำประกันโดยปริยาย "
user2685937

1
หากคุณต้องการความน่าเชื่อถือมากขึ้นโปรดอ่านบทความนี้เกี่ยวกับข้อกำหนด ECMA ที่จะใช้สำหรับแพลตฟอร์มอื่น ๆ : msdn.microsoft.com/en-us/magazine/jj863136.aspx หากคุณต้องการความน่าเชื่อถือเพิ่มเติมโปรดอ่านบทความที่ใหม่กว่านี้ซึ่งอาจมีการเพิ่มประสิทธิภาพ ที่ป้องกันไม่ให้ทำงานโดยไม่ระเหย: msdn.microsoft.com/en-us/magazine/jj883956.aspx โดยสรุปมัน "อาจ" ใช้ได้กับคุณโดยไม่ระเหยในขณะนี้ แต่อย่าคิดว่ามันเขียนโค้ดที่ถูกต้องและใช้ ระเหยหรือวิธีการอ่าน / เขียนระเหย
user2685937

1

lockก็เพียงพอแล้ว ข้อมูลจำเพาะของภาษา MS (3.0) กล่าวถึงสถานการณ์ที่แน่นอนนี้ใน§8.12โดยไม่มีการกล่าวถึงvolatile:

แนวทางที่ดีกว่าคือการซิงโครไนซ์การเข้าถึงข้อมูลแบบคงที่โดยการล็อกวัตถุคงที่ส่วนตัว ตัวอย่างเช่น:

class Cache
{
    private static object synchronizationObject = new object();
    public static void Add(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
    public static void Remove(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
}

Jon Skeet ในบทความของเขา ( yoda.arachsys.com/csharp/singleton.html ) กล่าวว่าจำเป็นต้องมีการระเหยเพื่อป้องกันความจำที่เหมาะสมในกรณีนี้ Marc คุณสามารถแสดงความคิดเห็นเกี่ยวกับเรื่องนี้ได้หรือไม่?
Konstantin

อาฉันไม่ได้สังเกตเห็นล็อคที่ตรวจสอบสองครั้ง เพียงแค่: อย่าทำอย่างนั้น - p
Marc Gravell

จริงๆแล้วฉันคิดว่าการล็อกแบบตรวจสอบซ้ำเป็นสิ่งที่ดีในด้านประสิทธิภาพ นอกจากนี้หากจำเป็นต้องทำให้ฟิลด์มีความผันผวนในขณะที่เข้าถึงได้ภายในล็อคดังนั้นการล็อกแบบ double-cheked ก็ไม่ได้เลวร้ายไปกว่าล็อคอื่น ๆ ...
Konstantin

แต่มันดีอย่างที่จอนพูดถึงหรือไม่
Marc Gravell

-3

นี่เป็นโพสต์ที่ดีเกี่ยวกับการใช้สารระเหยด้วยการตรวจสอบการล็อกสองครั้ง:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

ใน Java หากจุดมุ่งหมายคือการปกป้องตัวแปรคุณไม่จำเป็นต้องล็อกหากถูกทำเครื่องหมายว่าผันผวน


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