

เมื่อโปรแกรมเริ่มมีหลาย task เราอาจมีตัวแปรกลางไว้ให้ส่วนหนึ่งเขียนและอีกส่วนอ่าน เช่น จำนวนเหตุการณ์หรือชุดสถานะเซนเซอร์ พอพบค่าหายหรือข้อมูลคนละรอบ การเติม volatile อาจดูเหมือนเป็นวิธีบอก compiler ว่าตัวแปรนี้เปลี่ยนจากที่อื่นได้
แต่การกำหนดวิธีเข้าถึงตัวแปรของ compiler กับการประสานงานหลายผู้ใช้เป็นคนละเรื่อง แม้ทุกฝ่ายอ่านเขียนตัวแปรจริงตามที่ต้องการ ก็ยังมีจังหวะที่สองงานแทรกกันระหว่างขั้นย่อยได้
ควรแยกสิ่งที่ต้องรักษาให้เป็นชุดเดียวก่อนครับ แล้วค่อยเลือกว่าจะให้ใครเป็นเจ้าของหรือใช้กลไกประสานแบบใด ไม่เริ่มจากใส่ keyword เพิ่มจน error ดูเกิดน้อยลง
ในเชิงแนวคิด การเพิ่มค่าตัวนับเกี่ยวข้องกับการอ่านค่า คำนวณ และเขียนกลับ หากสอง task ใช้ข้อมูลร่วมโดยไม่มีกลไกที่ถูกต้อง อาจเกิดการอ่านค่าเดิมแล้วต่างฝ่ายต่างเขียนผลที่ทับกันได้ การที่โค้ดใช้เพียงหนึ่งบรรทัดไม่ได้รับรองว่าทั้งการเปลี่ยนแปลงเป็น atomic
รายละเอียดจริงขึ้นกับภาษา compiler ชนิดข้อมูลและเครื่องเป้าหมาย จึงไม่ควรอ้างลำดับ instruction เฉพาะโดยยังไม่ตรวจ แต่ข้อกำหนดของโปรแกรมยังต้องระบุว่ามีการป้องกันข้อมูลร่วมตาม memory model และ runtime ที่ใช้หรือไม่
GCC อธิบายว่า volatile ไม่ใช่ memory barrier สำหรับสั่งลำดับการเข้าถึงข้อมูลทั่วไป จึงไม่ควรใช้ธง volatile หนึ่งตัวเป็นหลักประกันว่าข้อมูลอีกโครงหนึ่งจะถูกเผยแพร่ให้ทุก task เห็นครบตามลำดับที่เราคิดเอง
volatile ยังมีหน้าที่ตามเงื่อนไขภาษาและการเข้าถึงบางประเภท เช่น register ของอุปกรณ์ที่ driver กำหนด แต่บทบาทนั้นไม่ได้ทำให้มันแทน mutex atomic หรือวิธีส่งข้อมูลระหว่าง task ได้ทั้งหมด

ข้อมูลร่วมอาจเป็นตัวเลขเดียว แต่ในงานเซนเซอร์มักมีค่า เวลาและสถานะคุณภาพอยู่ด้วยกัน หากล็อกเฉพาะการเขียนค่าหลักแล้วเขียนเวลาอีกช่วง ผู้รับก็ยังอาจเห็นค่ารอบใหม่กับเวลารอบเก่าได้
จึงควรเริ่มจาก invariant ที่งานต้องรักษา เช่น ทุก field ที่ผู้รับใช้ตัดสินใจต้องมาจาก sample เดียวกัน แล้วเลือกจุดส่งมอบหรือช่วงป้องกันให้ครอบความหมายนี้ ไม่เลือกขอบเขตจากจำนวน byte เพียงอย่างเดียว
อีกตัวอย่างคือรายการคิวกับตัวนับจำนวนค้าง หากสองสิ่งนี้ต้องตรงกัน การใช้ atomic เพิ่มตัวนับเพียงตัวเดียวก็ยังไม่ทำให้การเปลี่ยนรายการที่เกี่ยวข้องเป็น transaction เดียวไปด้วย ต้องดูความสัมพันธ์ทั้งชุดและทางที่ error อาจทำให้เปลี่ยนไม่ครบ
เมื่อรู้ขอบเขตแล้ว มักลดความซับซ้อนได้ด้วยการให้ task หนึ่งเป็นเจ้าของข้อมูลและรับคำขอจากคนอื่น แทนการให้ทุก task แก้โครงกลางเอง แม้ต้องมีทางส่งข้อความเพิ่ม แต่จุดที่ตัดสินการเปลี่ยนจะชัดขึ้น
หากหลาย task ต้องเข้าใช้ทรัพยากรร่วม อาจใช้ mutex ที่ runtime รองรับเพื่อให้เข้าใช้ตามลำดับ แต่ทุกทางที่แตะข้อมูลนั้นต้องยึดกติกาเดียวกัน การล็อกเฉพาะฝั่งเขียนโดยปล่อยผู้อ่านเข้าตรงๆ ไม่ได้ให้ขอบเขตที่หวังไว้เสมอ
ช่วงถือ mutex ควรสั้นและมีเหตุผล ไม่เอา network call ที่รอนานมารวมเพียงเพราะมันเกิดในฟังก์ชันเดียวกันกับการแก้ข้อมูล หากต้องส่งข้อมูลออก ควรเก็บ snapshot ที่สอดคล้องแล้วปล่อย lock ก่อนทำงานที่ไม่จำเป็นต้องถือร่วมตามสัญญา
กรณีต้องรับหลาย lock ต้องมีลำดับที่สอดคล้องกัน เพื่อไม่ให้สอง task ถือคนละส่วนแล้วต่างฝ่ายรออีกส่วน การเพิ่ม mutex ทุกจุดโดยไม่มีภาพของลำดับจึงอาจเปลี่ยนจากข้อมูลชนเป็นการรอไม่จบแทน
Mutex ไม่ใช่คำตอบสำหรับทุก context โดยเฉพาะ ISR ซึ่งไม่ควรรอแบบ task ต้องเลือก API และรูปแบบส่งต่อที่เอกสารอนุญาต ไม่ใช้ฟังก์ชันเดิมเพียงเพราะมันมีชื่อว่าป้องกันข้อมูลเหมือนกัน

บนระบบหลาย core การปิด interrupt บน core ที่กำลังทำงานอยู่ไม่ได้หยุดอีก core จากการเข้าถึงข้อมูลร่วม ESP-IDF FreeRTOS จึงมีกลไก critical section ที่ใช้ spinlock ตามข้อกำหนดของ implementation แทนการอาศัยปิด interrupt อย่างเดียว
ทางเข้าออกที่เกี่ยวข้องต้องใช้ตัว lock ร่วมกันให้ถูก ไม่สร้าง lock คนละตัวในแต่ละฟังก์ชันแล้วถือว่ากำลังคุมข้อมูลเดียวกัน และต้องใช้รูปแบบ API ให้ตรง task หรือ ISR กับรุ่นของระบบที่ใช้
Critical section แบบนี้มีผลต่อจังหวะ interrupt จึงควรจำกัดไว้กับการอ่านหรือแก้โครงสั้นๆ ที่จำเป็น ตามเอกสารไม่ควรเรียกงานที่ block หรือ yield และไม่ใส่การรออุปกรณ์ทั้งรอบเข้าไปเพียงเพื่อให้ไม่มีใครแทรก
การป้องกันส่วนเล็กให้ถูกจึงต่างจากการหยุดระบบก้อนใหญ่จนข้อมูลไม่ชน หากช่วงป้องกันยาวเกินไป อาการอาจไปปรากฏเป็น callback ช้าหรือ watchdog แทน ต้องรักษาทั้งความถูกต้องและโอกาสทำงานของส่วนอื่นร่วมกัน
Atomic operation มีประโยชน์กับงานที่ขอบเขตตรงกับสิ่งที่กลไกรองรับ แต่รายละเอียด memory ordering และความพร้อมของ target ยังต้องอ่านให้ถูก ไม่เลือกใช้เพื่อหลีกเลี่ยง mutex โดยยังไม่รู้ว่าผู้รับต้องเห็นข้อมูลใดพร้อมกัน
สำหรับโครงหลาย field การส่งสำเนาที่ครบตามกติกาคิวหรือการคัด snapshot ภายใต้ช่วงป้องกันที่เหมาะสมอาจทำให้ความหมายชัดกว่า แต่ต้องตรวจด้วยว่าสิ่งที่ส่งมี pointer ไปยังข้อมูลที่ยังถูกเปลี่ยนภายหลังหรือไม่ การ copy โครงไม่ได้แปลว่า copy หน่วยความจำที่ pointer ชี้ไปทั้งหมด
ชุดตรวจควรมีการเข้าถึงพร้อมกันหลายทางและผลที่ยืนยันความสัมพันธ์ของข้อมูลได้ เช่น ลำดับกับเวลา sample ไม่ใช้เพียงการอ่านตัวเลขสุดท้ายว่าดูพอใช้ได้ และการทดสอบไม่พบอาการก็ยังไม่แทนเหตุผลว่ากลไกสอดคล้องกับกติกาภาษาและ context จริง
volatile บอกบางเรื่องของการเข้าถึงตัวแปร แต่ไม่ได้ประสานการเปลี่ยนข้อมูลร่วมให้เป็นชุดที่ปลอดภัยเอง เมื่อกำหนดเจ้าของ invariant และบริบทของผู้ใช้ข้อมูลแล้ว เราจะเลือก mutex atomic critical section หรือการส่ง snapshot ได้ตรงหน้าที่ พร้อมรักษาช่วงป้องกันให้สั้นและใช้กติกาเดียวกันทุกทางที่เข้าถึงข้อมูลนั้น