เป็นบูลอ่าน / เขียนอะตอมใน C #


84

กำลังเข้าถึงBool field atomic ใน C # หรือไม่ โดยเฉพาะอย่างยิ่งฉันต้องใส่กุญแจล็อค:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}


1
ใช่ แต่ (อาจ) ก็ใช่ ใช่การเข้าถึง / การตั้งค่าฟิลด์บูลเป็นแบบปรมาณู แต่ถ้าการดำเนินการไม่ใช่ (ดูคำตอบของ Dror Helper ด้านล่าง) ดังนั้นคุณอาจต้องล็อกด้วยเช่นกัน
JPProgrammer

คำตอบ:


119

ใช่.

การอ่านและเขียนประเภทข้อมูลต่อไปนี้ ได้แก่ ประเภท atomic: bool, char, byte, sbyte, short, ushort, uint, int, float และประเภทอ้างอิง

ที่พบในC # ภาษา Spec

แก้ไข: ควรทำความเข้าใจกับคำหลักที่มีความผันผวนด้วยเช่นกัน


10
ตัวชี้เองกำหนดมันใหม่คืออะตอม (เช่น Foo foo1 = foo2;
user142350

4
@configurator: คำถามคือคุณต้องการอะไรกันแน่ ง่ายต่อการรับโปรแกรมที่ไม่มีการล็อคผิด ดังนั้นหากคุณไม่จำเป็นต้องใช้จริงๆควรใช้เฟรมเวิร์กที่ง่ายกว่านี้ (เช่น TPL) 'ระเหย' เป็นคำอื่นที่ไม่ผิด แต่เป็นสัญญาณของรหัสที่ยุ่งยาก (เช่นควรหลีกเลี่ยง) OP ไม่ได้บอกว่าเขาต้องการอะไรจริงๆฉันแค่ลังเลที่จะแนะนำโดยเจตนาที่ผันผวน
Eamon Nerbonne

4
ว. นี่เป็นถ้อยคำที่อันตรายสำหรับคน C ++ ปรมาณูหมายความว่าการอ่านเขียนใด ๆ จะถูกล้อมรอบด้วยรั้วหน่วยความจำที่เกี่ยวข้อง ซึ่งไม่เป็นเช่นนั้นอย่างแน่นอนใน C # เพราะมิฉะนั้นประสิทธิภาพจะแย่มากเนื่องจากมีผลบังคับใช้สำหรับตัวแปรทั้งหมด <long. อะตอมที่นี่ใน C # พูดจาน่าจะหมายถึงกว่าเมื่ออ่านหรือเขียนในที่สุดก็เกิดขึ้นพวกเขาจะรับประกันว่าจะไม่อยู่ในสถานะที่เสีย แต่มันไม่ได้บอกว่าเมื่อไหร่ "ในที่สุด"
v.oddou

4
ถ้าเขียนถึง int และ long เป็น atomic แล้วเมื่อใช้Interlocked.Add(ref myInt);เช่น?
Mike de Klerk

6
@MikedeKlerk การอ่านและการเขียนเป็นแบบปรมาณู แต่แยกกัน i++เท่ากับi=i+1หมายความว่าคุณอ่านอะตอมแล้วบวกแล้วเขียนอะตอม เธรดอื่นสามารถแก้ไขได้iหลังจากอ่าน แต่ก่อนเขียน ตัวอย่างเช่นสองเธรดที่ทำi++พร้อมกันบนเดียวกันฉันสามารถอ่านพร้อมกันได้ (และอ่านค่าเดียวกัน) เพิ่มหนึ่งเธรดจากนั้นทั้งสองเขียนค่าเดียวกันเพิ่มเพียงครั้งเดียว Interlocked.Add ป้องกันสิ่งนี้ ตามกฎทั่วไปความจริงที่ว่าประเภทคืออะตอมจะมีประโยชน์ก็ต่อเมื่อมีการเขียนเธรดเพียงชุดเดียว แต่มีการอ่านหลายเธรด
Jannis Froese

49

ตามที่ระบุไว้ข้างต้นบูลเป็นปรมาณู แต่คุณยังต้องจำไว้ว่ามันขึ้นอยู่กับสิ่งที่คุณต้องการทำด้วย

if(b == false)
{
    //do something
}

ไม่ใช่การทำงานของอะตอมซึ่งหมายความว่าค่า b สามารถเปลี่ยนแปลงได้ก่อนที่เธรดปัจจุบันจะรันโค้ดหลังจากคำสั่ง if


30

การเข้าถึงบูลเป็นปรมาณู แต่นั่นไม่ใช่เรื่องราวทั้งหมด

คุณไม่ต้องกังวลเกี่ยวกับการอ่านค่าที่ 'เขียนไม่สมบูรณ์' - ยังไม่ชัดเจนว่าสิ่งที่อาจหมายถึงบูลในกรณีใด ๆ - แต่คุณต้องกังวลเกี่ยวกับแคชของโปรเซสเซอร์อย่างน้อยถ้ารายละเอียดของ เวลาเป็นปัญหา หากเธรด # 1 ทำงานบนคอร์ A มี_barแคชของคุณและ_barได้รับการอัปเดตโดยเธรด # 2 ที่ทำงานบนคอร์อื่นเธรด # 1 จะไม่เห็นการเปลี่ยนแปลงทันทีเว้นแต่คุณจะเพิ่มการล็อกประกาศ_barเป็นvolatileหรือแทรกการโทรอย่างชัดเจนThread.MemoryBarrier()เพื่อทำให้ไม่ถูกต้อง ค่าแคช


1
"ยังไม่ชัดเจนว่าอาจหมายถึงอะไรสำหรับบูลในกรณีใด ๆ " รายการที่มีอยู่ในอะตอมหน่วยความจำเพียงหนึ่งไบต์เนื่องจากไบต์ทั้งหมดถูกเขียนในเวลาเดียวกัน เมื่อเทียบกับรายการเช่น double ซึ่งมีอยู่ในหลายไบต์สามารถเขียนได้หนึ่งไบต์ก่อนไบต์อื่นและคุณสามารถสังเกตตำแหน่งหน่วยความจำที่เขียนได้ครึ่งหนึ่ง
MindStalker

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

1
อุปสรรคของหน่วยความจำมีประโยชน์หากคุณสร้างวัตถุไขมันและเปลี่ยนการอ้างอิงไปยังสิ่งที่อาจอ่านได้จากเธรดอื่น อุปสรรครับประกันว่าข้อมูลอ้างอิงจะไม่อัปเดตในหน่วยความจำหลักก่อนส่วนที่เหลือของวัตถุไขมัน เธรดอื่น ๆ ได้รับการรับรองว่าจะไม่เห็นการอัปเดตข้อมูลอ้างอิงก่อนที่วัตถุไขมันจะพร้อมใช้งานจริงในหน่วยความจำหลัก var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch

1

แนวทางที่ฉันใช้และฉันคิดว่าถูกต้องคือ

volatile bool b = false;

.. rarely signal an update with a large state change...

lock b_lock
{
  b = true;
  //other;
}

... another thread ...

if(b)
{
    lock b_lock
    {
       if(b)
       {
           //other stuff
           b = false;
       }
     }
}

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


4
นี่เป็นแนวทางที่ถูกต้องในการล็อคโดยทั่วไป แต่ถ้าบูลเป็นอะตอมก็จะง่ายกว่า (และเร็วกว่า) ที่จะละเว้นการล็อค
dbkk

2
หากไม่มีการล็อก "การเปลี่ยนแปลงสถานะขนาดใหญ่" จะไม่เกิดขึ้นในทางอะตอม ล็อค -> ตั้ง | check -> lock -> วิธีการตรวจสอบจะทำให้แน่ใจว่าโค้ด "// other" ถูกเรียกใช้งานก่อนที่จะใช้รหัส "// other stuff" สมมติว่าส่วน "เธรดอื่น" วนซ้ำหลายครั้ง (ซึ่งเป็นกรณีของฉัน) เพียงแค่ตรวจสอบบูลโดยส่วนใหญ่แล้ว แต่ไม่ได้รับการล็อก (อาจมีการโต้แย้งกันจริง) เป็นการชนะประสิทธิภาพที่สำคัญ
stux

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