0Pricing
Swift Academy · บทเรียน

ปัญหาการแย่งข้อมูล

ทำความเข้าใจว่าเหตุใดการเปลี่ยนแปลงพร้อมกันจึงไม่ปลอดภัย

ปัญหาการแย่งข้อมูล เป็นบทเรียน 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.balance

Sendable กำหนดสิ่งที่ข้ามเธรด

โปรโตคอล 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ปัญหาการแย่งข้อมูล
  2. โพรโทคอล Sendable
  3. การแยกส่วนของแอกเตอร์และ nonisolated
  4. การย้ายไปใช้การทำงานพร้อมกันแบบเข้มงวด
← กลับไปที่ Swift Academy