คำว่า "ระเหย" ใช้สำหรับอะไร


130

ฉันอ่านบทความเกี่ยวกับvolatileคำหลัก แต่ฉันไม่สามารถเข้าใจการใช้งานที่ถูกต้อง คุณช่วยบอกฉันทีว่ามันควรใช้กับอะไรใน C # และใน Java?


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

คำตอบ:


93

สำหรับทั้ง C # และ Java "volatile" จะบอกคอมไพเลอร์ว่าค่าของตัวแปรต้องไม่ถูกแคชเนื่องจากค่าอาจเปลี่ยนแปลงนอกขอบเขตของโปรแกรม คอมไพเลอร์จะหลีกเลี่ยงการปรับให้เหมาะสมใด ๆ ที่อาจทำให้เกิดปัญหาหากตัวแปรเปลี่ยนแปลง "นอกการควบคุม"


@Tom - ตั้งข้อสังเกตอย่างถูกต้องครับ - และแก้ไขเพิ่มเติม
จะ

11
ก็ยังคงมากมากขึ้นที่ลึกซึ้งกว่านั้น
Tom Hawtin - tackline

1
ไม่ถูกต้อง. ไม่ป้องกันการแคช ดูคำตอบของฉัน
doug65536

168

ลองพิจารณาตัวอย่างนี้:

int i = 5;
System.out.println(i);

คอมไพเลอร์อาจปรับให้เหมาะกับการพิมพ์ 5 เช่นนี้:

System.out.println(5);

อย่างไรก็ตามหากมีเธรดอื่นที่สามารถเปลี่ยนแปลงiได้นี่เป็นพฤติกรรมที่ผิด หากเธรดอื่นเปลี่ยนiเป็น 6 เวอร์ชันที่ปรับให้เหมาะสมจะยังคงพิมพ์ 5

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


3
ผมเชื่อว่าการเพิ่มประสิทธิภาพจะยังคงสามารถใช้ร่วมกับการทำเครื่องหมายเป็นi volatileใน Java มันคือทั้งหมดที่เกี่ยวกับความสัมพันธ์ที่เกิดขึ้นก่อน
Tom Hawtin - tackline

ขอบคุณสำหรับการโพสต์ดังนั้นการระเหยมีการเชื่อมต่อกับการล็อกตัวแปรอย่างไร
Mircea

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

5
@Sererd: ฉันไม่แน่ใจว่าฉันเข้าใจตัวอย่างนี้ หากiเป็นตัวแปรโลคัลไม่มีเธรดอื่นสามารถเปลี่ยนได้ finalถ้ามันฟิลด์คอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการโทรเว้นแต่จะเป็น ฉันไม่คิดว่าคอมไพเลอร์สามารถทำการปรับให้เหมาะสมโดยสมมติว่าฟิลด์ "ดู" finalเมื่อไม่ได้ประกาศอย่างชัดเจน
polygenelubricants

1
C # และ java ไม่ใช่ C ++ สิ่งนี้ไม่ถูกต้อง มันไม่ได้ป้องกันการแคชและไม่ได้ป้องกันการปรับให้เหมาะสม มันเกี่ยวกับการอ่าน - ได้รับและซีแมนทิกส์การวางจำหน่ายซึ่งจำเป็นสำหรับสถาปัตยกรรมหน่วยความจำที่มีคำสั่งน้อย มันเกี่ยวกับการดำเนินการเก็งกำไร
doug65536

40

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

  • ตัวแปรไม่แปรปรวน

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

  • ตัวแปรมีความผันผวน

เมื่อตัวแปรถูกประกาศว่ามีความผันผวนหมายความว่าเธรดไม่ควรแคชตัวแปรดังกล่าวหรือในคำอื่น ๆ เธรดไม่ควรเชื่อถือค่าของตัวแปรเหล่านี้เว้นแต่ว่าพวกเขาจะถูกอ่านโดยตรงจากหน่วยความจำหลัก

ดังนั้นเมื่อใดที่จะทำให้ตัวแปรมีความผันผวน

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


2
ไม่ถูกต้อง. ไม่มีส่วนเกี่ยวข้องกับ "การป้องกันการแคช" มันเกี่ยวกับการจัดลำดับใหม่โดยคอมไพเลอร์หรือฮาร์ดแวร์ CPU ผ่านการดำเนินการเก็งกำไร
doug65536

37

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

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

ลองพิจารณาตัวอย่างต่อไปนี้:

something.foo = new Thing();

ถ้าfooเป็นตัวแปรสมาชิกในคลาสและ CPU อื่นสามารถเข้าถึงอินสแตนซ์ของวัตถุที่อ้างอิงโดยsomethingพวกเขาอาจเห็นการfooเปลี่ยนแปลงค่าก่อนที่หน่วยความจำเขียนในตัวThingสร้างจะมองเห็นได้ทั่วโลก! นี่คือความหมาย "หน่วยความจำที่สั่งอย่างอ่อน" fooซึ่งอาจเกิดขึ้นได้แม้ว่าคอมไพเลอร์มีทั้งหมดของร้านค้าในคอนสตรัคก่อนร้านที่จะ หากfooเป็นvolatileที่เก็บfooจะมีการปล่อยซีแมนทิกส์และฮาร์ดแวร์รับประกันว่าการเขียนทั้งหมดก่อนที่การเขียนfooจะปรากฏแก่โปรเซสเซอร์อื่นก่อนที่จะอนุญาตให้เขียนfooได้

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

สถาปัตยกรรม Itanium (แย่มาก) จาก Intel ได้สั่งหน่วยความจำอย่างอ่อน โปรเซสเซอร์ที่ใช้ใน XBox 360 เดิมได้สั่งหน่วยความจำอย่างอ่อน หน่วยประมวลผล ARM หลายรุ่นรวมถึง ARMv7-A ที่ได้รับความนิยมสูงได้สั่งซื้อหน่วยความจำน้อย

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

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

ติดขัดจาก Wikipedia:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking
                    // mySingleton with 'volatile', which inserts the necessary memory
                    // barriers between the constructor call and the write to mySingleton.
                    // The barriers created by the lock are not sufficient because
                    // the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads
        // will acquire the lock. A fence for read-acquire semantics is needed between
        // the test of mySingleton (above) and the use of its contents. This fence
        // is automatically inserted because mySingleton is marked as 'volatile'.
        return mySingleton;
    }
}

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

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


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

ทั้งสองheadและtailจำเป็นต้องมีความผันผวนเพื่อป้องกันผู้ผลิตจากการสมมติว่าtailจะไม่เปลี่ยนแปลงและเพื่อป้องกันผู้บริโภคจากการสมมติว่าheadจะไม่เปลี่ยนแปลง นอกจากนี้headจะต้องมีความผันผวนเพื่อให้แน่ใจว่าการเขียนข้อมูลคิวสามารถมองเห็นได้ทั่วโลกก่อนที่ร้านค้าจะheadมองเห็นได้ทั่วโลก
doug65536

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

34

คำระเหยที่มีความหมายที่แตกต่างกันทั้งใน Java และ C #

ชวา

จากข้อกำหนดภาษา Java :

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

ค#

จากการอ้างอิง C # กับคำหลักระเหย :

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


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

@Mircea ใน Java ไม่มีการล็อคที่เกี่ยวข้องเพียงแค่ช่วยให้มั่นใจว่าค่าตัวแปร volatile ที่ทันสมัยที่สุดจะถูกนำมาใช้
krock

Java สัญญากับหน่วยความจำกั้นอุปสรรคบางอย่างหรือว่าเป็น C ++ และ C # ในสัญญาเท่านั้นที่จะไม่เพิ่มประสิทธิภาพการอ้างอิงออกไป?
Steven Sudit

อุปสรรคหน่วยความจำคือรายละเอียดการใช้งาน สิ่งที่ Java สัญญาไว้จริง ๆ คือผู้อ่านทุกคนจะเห็นคุณค่าที่เขียนโดยการเขียนล่าสุด
Stephen C

1
@StevenSudit ใช่หากฮาร์ดแวร์ต้องการสิ่งกีดขวางหรือโหลด / รับหรือเก็บ / ปล่อยมันจะใช้คำแนะนำเหล่านั้น ดูคำตอบของฉัน
doug65536

9

ใน Java "volatile" ใช้เพื่อบอก JVM ว่าอาจใช้ตัวแปรหลายเธรดในเวลาเดียวกันดังนั้นจึงไม่สามารถใช้การปรับให้เหมาะสมร่วมกันได้

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


1

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


1
คุณสามารถเรียบเรียงคำตอบใหม่ได้หรือไม่?
Anirudha Gupta

คำหลักที่เปลี่ยนแปลงได้จะให้คุณค่าที่ทันสมัยที่สุดแก่คุณมากกว่าค่าที่เก็บไว้
Subhash Saini

0

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


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