ฉันอ่านบทความเกี่ยวกับvolatile
คำหลัก แต่ฉันไม่สามารถเข้าใจการใช้งานที่ถูกต้อง คุณช่วยบอกฉันทีว่ามันควรใช้กับอะไรใน C # และใน Java?
ฉันอ่านบทความเกี่ยวกับvolatile
คำหลัก แต่ฉันไม่สามารถเข้าใจการใช้งานที่ถูกต้อง คุณช่วยบอกฉันทีว่ามันควรใช้กับอะไรใน C # และใน Java?
คำตอบ:
สำหรับทั้ง C # และ Java "volatile" จะบอกคอมไพเลอร์ว่าค่าของตัวแปรต้องไม่ถูกแคชเนื่องจากค่าอาจเปลี่ยนแปลงนอกขอบเขตของโปรแกรม คอมไพเลอร์จะหลีกเลี่ยงการปรับให้เหมาะสมใด ๆ ที่อาจทำให้เกิดปัญหาหากตัวแปรเปลี่ยนแปลง "นอกการควบคุม"
ลองพิจารณาตัวอย่างนี้:
int i = 5;
System.out.println(i);
คอมไพเลอร์อาจปรับให้เหมาะกับการพิมพ์ 5 เช่นนี้:
System.out.println(5);
อย่างไรก็ตามหากมีเธรดอื่นที่สามารถเปลี่ยนแปลงi
ได้นี่เป็นพฤติกรรมที่ผิด หากเธรดอื่นเปลี่ยนi
เป็น 6 เวอร์ชันที่ปรับให้เหมาะสมจะยังคงพิมพ์ 5
volatile
คำหลักที่จะช่วยป้องกันการเพิ่มประสิทธิภาพดังกล่าวและแคชและจึงจะเป็นประโยชน์เมื่อตัวแปรสามารถเปลี่ยนแปลงได้โดยหัวข้ออื่น
i
volatile
ใน Java มันคือทั้งหมดที่เกี่ยวกับความสัมพันธ์ที่เกิดขึ้นก่อน
i
เป็นตัวแปรโลคัลไม่มีเธรดอื่นสามารถเปลี่ยนได้ final
ถ้ามันฟิลด์คอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการโทรเว้นแต่จะเป็น ฉันไม่คิดว่าคอมไพเลอร์สามารถทำการปรับให้เหมาะสมโดยสมมติว่าฟิลด์ "ดู" final
เมื่อไม่ได้ประกาศอย่างชัดเจน
เพื่อให้เข้าใจสิ่งที่เปลี่ยนแปลงได้กับตัวแปรสิ่งสำคัญคือต้องเข้าใจว่าเกิดอะไรขึ้นเมื่อตัวแปรไม่ระเหย
เมื่อเธรด A & B สองเธรดเข้าถึงตัวแปรที่ไม่ลบเลือนแต่ละเธรดจะเก็บสำเนาโลคัลของตัวแปรในแคชโลคัล การเปลี่ยนแปลงใด ๆ ที่กระทำโดยเธรด A ที่อยู่ในแคชภายในจะไม่ปรากฏต่อเธรด B
เมื่อตัวแปรถูกประกาศว่ามีความผันผวนหมายความว่าเธรดไม่ควรแคชตัวแปรดังกล่าวหรือในคำอื่น ๆ เธรดไม่ควรเชื่อถือค่าของตัวแปรเหล่านี้เว้นแต่ว่าพวกเขาจะถูกอ่านโดยตรงจากหน่วยความจำหลัก
ดังนั้นเมื่อใดที่จะทำให้ตัวแปรมีความผันผวน
เมื่อคุณมีตัวแปรซึ่งสามารถเข้าถึงได้โดยเธรดจำนวนมากและคุณต้องการให้เธรดทุกอันได้รับค่าที่อัพเดตล่าสุดของตัวแปรนั้นแม้ว่าค่านั้นจะถูกอัพเดตโดยเธรด / กระบวนการ / อื่น ๆ นอกโปรแกรม
อ่านของเขตระเหยมีความหมายซื้อ ซึ่งหมายความว่าจะรับประกันได้ว่าหน่วยความจำที่อ่านจากตัวแปรระเหยจะเกิดขึ้นก่อนที่หน่วยความจำต่อไปนี้จะอ่าน จะบล็อกคอมไพเลอร์ไม่ให้ทำการเรียงลำดับใหม่และหากฮาร์ดแวร์ต้องการ (สั่ง 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
ไม่เคยป้องกันการแคช สิ่งที่ทำคือรับประกันการสั่งซื้อซึ่งโปรเซสเซอร์อื่น ๆ "เห็น" เขียน การเปิดตัวร้านค้าจะชะลอการจัดเก็บจนกว่าการเขียนที่ค้างอยู่ทั้งหมดจะเสร็จสมบูรณ์และมีการออกรอบรถบัสซึ่งบอกให้โปรเซสเซอร์อื่น ๆ ยกเลิก / เขียนกลับแคชของพวกเขาหากพวกเขามีแคชที่เกี่ยวข้อง การรับโหลดจะล้างการอ่านที่มีการคาดเดาใด ๆ เพื่อให้แน่ใจว่าจะไม่เป็นค่าเก่าจากอดีต
head
และtail
จำเป็นต้องมีความผันผวนเพื่อป้องกันผู้ผลิตจากการสมมติว่าtail
จะไม่เปลี่ยนแปลงและเพื่อป้องกันผู้บริโภคจากการสมมติว่าhead
จะไม่เปลี่ยนแปลง นอกจากนี้head
จะต้องมีความผันผวนเพื่อให้แน่ใจว่าการเขียนข้อมูลคิวสามารถมองเห็นได้ทั่วโลกก่อนที่ร้านค้าจะhead
มองเห็นได้ทั่วโลก
คำระเหยที่มีความหมายที่แตกต่างกันทั้งใน Java และ C #
จากข้อกำหนดภาษา Java :
ฟิลด์อาจถูกประกาศให้ระเหยได้ซึ่งในกรณีนี้โมเดลหน่วยความจำ Java จะทำให้แน่ใจได้ว่าเธรดทั้งหมดจะเห็นค่าที่สอดคล้องกันสำหรับตัวแปร
จากการอ้างอิง C # กับคำหลักระเหย :
คำสำคัญระเหยแสดงว่าฟิลด์สามารถแก้ไขได้ในโปรแกรมโดยบางสิ่งเช่นระบบปฏิบัติการฮาร์ดแวร์หรือเธรดที่กำลังดำเนินการพร้อมกัน
ใน Java "volatile" ใช้เพื่อบอก JVM ว่าอาจใช้ตัวแปรหลายเธรดในเวลาเดียวกันดังนั้นจึงไม่สามารถใช้การปรับให้เหมาะสมร่วมกันได้
สถานการณ์ที่เธรดสองตัวที่เข้าถึงตัวแปรเดียวกันกำลังทำงานบน CPU ที่แยกกันในเครื่องเดียวกัน เป็นเรื่องปกติมากสำหรับ CPU ที่จะแคชข้อมูลอย่างจริงจังเพราะการเข้าถึงหน่วยความจำช้ากว่าการเข้าถึงแคชมาก ซึ่งหมายความว่าหากข้อมูลได้รับการปรับปรุงใน CPU1 นั้นจะต้องผ่านแคชทั้งหมดและหน่วยความจำหลักทันทีเมื่อแคชตัดสินใจล้างตัวเองเพื่อให้ CPU2 สามารถดูค่าที่อัพเดตได้ (อีกครั้งโดยไม่สนใจแคชทั้งหมดระหว่างทาง)
เมื่อคุณกำลังอ่านข้อมูลที่ไม่ลบเลือนเธรดการเรียกใช้งานอาจหรืออาจไม่ได้รับค่าที่อัพเดตเสมอไป แต่ถ้าวัตถุมีความผันผวนเธรดจะได้รับค่าที่ทันสมัยที่สุดเสมอ
ระเหยคือการแก้ปัญหาการเกิดพร้อมกัน เพื่อทำให้ค่านั้นซิงค์ คำหลักนี้ส่วนใหญ่ใช้ในเธรด เมื่อมีหลายเธรดที่อัพเดตตัวแปรเดียวกัน