ปัญหาการแย่งข้อมูล
ทำความเข้าใจว่าเหตุใดการเปลี่ยนแปลงพร้อมกันจึงไม่ปลอดภัย
ปัญหาการแย่งข้อมูล เป็นบทเรียน Swift Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Swift Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Swift Academy มีบทเรียนทั้งหมด 4 บทเรียน
ภาวะข้อมูลแข่งกันคืออะไร
ภาวะข้อมูลแข่งกัน เกิดขึ้นเมื่อเธรดตั้งแต่สองเธรดขึ้นไปเข้าถึงตำแหน่งหน่วยความจำเดียวกันพร้อมกัน โดยมีอย่างน้อยหนึ่งการเข้าถึงที่เป็นการเขียน และไม่มีการประสานงานระหว่างกัน
ผลลัพธ์คือ พฤติกรรมที่ไม่กำหนด ซึ่งอาจเป็นค่าที่เสียหาย โปรแกรมหยุดทำงาน หรือข้อผิดพลาดที่ปรากฏเฉพาะเมื่อมีโหลดสูง
สถานะที่เปลี่ยนแปลงได้ร่วมกัน
สาเหตุหลักของภาวะข้อมูลแข่งกันคือ สถานะที่เปลี่ยนแปลงได้ร่วมกัน หากงานหลายงานสามารถอ่านและเขียนตัวแปรเดียวกันได้ ลำดับการทำงานจะคาดเดาไม่ได้
ตัวนับด้านล่างอาจเพิ่มค่าไม่ครบ เพราะ count += 1 เป็นการอ่าน-แก้ไข-เขียน ไม่ใช่การทำงานแบบอะตอมิก
final class Counter {
var count = 0
func increment() {
count += 1 // read, add, write: not atomic
}
}เหตุใดค่าที่เพิ่มจึงหายไป
คำสั่ง count += 1 ถูกคอมไพล์เป็นสามขั้นตอน ได้แก่ โหลดค่า บวกหนึ่ง แล้วเขียนค่ากลับ
หากเธรดสองเธรดโหลดค่า 5 พร้อมกัน ทั้งคู่จะเขียนค่า 6 กลับ และการเพิ่มค่าหนึ่งครั้งจะหายไป
// Thread A loads 5
// Thread B loads 5
// Thread A stores 6
// Thread B stores 6 <-- lost updateการฉีกขาดของค่าขนาดใหญ่
นอกจากการอัปเดตที่หายไปแล้ว การเขียนค่าหลายคำพร้อมกัน เช่น โครงสร้างหรือค่าขนาด 64 บิตบนบางแพลตฟอร์ม อาจเกิด การฉีกขาด ได้ กล่าวคือผู้อ่านเห็นข้อมูลครึ่งหนึ่งจากการเขียนครั้งหนึ่งและอีกครึ่งหนึ่งจากอีกครั้งหนึ่ง
struct Point { var x: Double; var y: Double }
var p = Point(x: 0, y: 0)
// Concurrent writes may leave x from one write and y from anotherวิธีแก้แบบเดิม: lock
ก่อนมีการทำงานพร้อมกันของ Swift วิธีแก้แบบดั้งเดิมคือ lock หรือมิวเทกซ์ โดยในแต่ละครั้งจะมีเธรดเพียงหนึ่งเธรดที่ถือ lock ทำให้การเข้าถึงเกิดขึ้นทีละรายการ
lock ใช้งานได้ แต่ใช้ผิดได้ง่าย เช่น ลืม unlock เกิดเดดล็อก หรือเกิดการกลับลำดับความสำคัญ
import Foundation
final class SafeCounter {
private let lock = NSLock()
private var count = 0
func increment() {
lock.lock()
defer { lock.unlock() }
count += 1
}
}คิวส่งงานแบบอนุกรม
อีกแนวทางแบบดั้งเดิมคือ คิวส่งงานแบบอนุกรม การเปลี่ยนแปลงทั้งหมดจะถูกรวมส่งไปยังคิวเดียว จึงไม่เกิดขึ้นพร้อมกัน
import Foundation
final class QueueCounter {
private let queue = DispatchQueue(label: "counter")
private var count = 0
func increment() {
queue.async { self.count += 1 }
}
}เหตุใดการประสานงานด้วยตนเองจึงเปราะบาง
lock และคิวต่างต้องอาศัย วินัย คอมไพเลอร์จะไม่ตรวจสอบว่าการเข้าถึงทุกครั้งได้รับการป้องกันหรือไม่
เพียงมีการอ่านที่ไม่ได้ป้องกันหลุดรอดไป ภาวะข้อมูลแข่งกันก็จะกลับมา และไม่มีการรับประกันขณะคอมไพล์
// Nothing stops a careless reader from doing this:
// let value = counter.count // unsynchronized read = raceการทำงานพร้อมกันในสวิฟต์เปลี่ยนแนวทาง
การทำงานพร้อมกันของ Swift ทำให้ความปลอดภัยจากภาวะข้อมูลแข่งกันกลายเป็น ฟีเจอร์ของภาษา แทนที่จะเป็นเพียงข้อตกลงในการเขียนโค้ด
เครื่องมือสามอย่างทำงานร่วมกัน ได้แก่ actor สำหรับปกป้องสถานะที่เปลี่ยนแปลงได้ Sendable สำหรับชนิดข้อมูลที่แบ่งปันได้อย่างปลอดภัย และคอมไพเลอร์สำหรับบังคับใช้ทั้งสองอย่าง
actor Counter {
private var count = 0
func increment() { count += 1 }
}แอกเตอร์ทำให้การเข้าถึงเป็นลำดับ
แอกเตอร์ รับประกันว่าจะมีงานเพียงหนึ่งงานที่ทำงานกับโค้ดซึ่งเปลี่ยนแปลงสถานะของตนได้ในแต่ละครั้ง รันไทม์จะจัดลำดับการเข้าถึงให้โดยอัตโนมัติ
คุณไม่ต้องเขียน lock เอง เพราะรูปแบบแอกเตอร์จัดการการประสานงานให้
actor BankAccount {
private(set) var balance = 0
func deposit(_ amount: Int) { balance += amount }
}การบังคับใช้ระหว่างคอมไพล์
คอมไพเลอร์จะไม่ยอมให้คุณเข้าถึงสถานะที่แยกไว้โดยแอกเตอร์โดยไม่ผ่านแอกเตอร์ การเรียกข้ามแอกเตอร์จะกลายเป็นการเรียกแบบอะซิงก์ (await)
วิธีนี้เปลี่ยนภาวะข้อมูลแข่งกันขณะรันไทม์ให้กลายเป็น ข้อผิดพลาดขณะคอมไพล์
let account = BankAccount()
// Must await: balance is actor-isolated
// let b = await account.balanceSendable กำหนดสิ่งที่ข้ามเธรด
โปรโตคอล Sendable ใช้ระบุชนิดข้อมูลที่ส่งผ่านขอบเขตการทำงานพร้อมกันได้อย่างปลอดภัย
คอมไพเลอร์จะป้องกันไม่ให้ส่งสถานะที่เปลี่ยนแปลงได้และไม่ใช่ Sendable ไปยังงานอื่น ซึ่งช่วยปิดช่องทางการเกิดภาวะข้อมูลแข่งกันในระดับชนิดข้อมูล
struct Money: Sendable {
let amount: Int
let currency: String
}ตรวจสอบความเข้าใจ: ภาวะข้อมูลแข่งกัน
ทดสอบความเข้าใจของคุณเกี่ยวกับสาเหตุของภาวะข้อมูลแข่งกัน
ทบทวน: ปัญหาภาวะข้อมูลแข่งกัน
ภาวะข้อมูลแข่งกันเกิดจาก สถานะที่เปลี่ยนแปลงได้ร่วมกัน ซึ่งถูกเข้าถึงพร้อมกันโดยไม่มีการประสานงาน ทำให้เกิดการอัปเดตที่หายไป การฉีกขาด และพฤติกรรมที่ไม่กำหนด
วิธีแก้แบบเดิม เช่น lock และคิวแบบอนุกรม ใช้งานได้แต่ไม่มีการตรวจสอบและเปราะบาง การทำงานพร้อมกันของ Swift เปลี่ยนการอาศัยข้อตกลงให้เป็นการบังคับใช้จริง โดย actor แยกสถานะออกจากกัน Sendable กำหนดสิ่งที่ข้ามขอบเขตได้ และคอมไพเลอร์ตรวจสอบความปลอดภัย หลักสูตรส่วนที่เหลือจะสำรวจเครื่องมือเหล่านี้อย่างละเอียด
คำถามที่พบบ่อย
บทเรียน “ปัญหาการแย่งข้อมูล” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ปัญหาการแย่งข้อมูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Swift Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Swift Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ปัญหาการแย่งข้อมูล”
ทำความเข้าใจว่าเหตุใดการเปลี่ยนแปลงพร้อมกันจึงไม่ปลอดภัย คุณปฏิบัติ Swift Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Swift Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Swift Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “ปัญหาการแย่งข้อมูล” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Swift Academy นี้ได้ไหม
ได้ บทเรียน Swift Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ปัญหาการแย่งข้อมูล
- โพรโทคอล Sendable
- การแยกส่วนของแอกเตอร์และ nonisolated
- การย้ายไปใช้การทำงานพร้อมกันแบบเข้มงวด