Semaphore: คืออะไร หลักการทำงาน และการประยุกต์ใช้ในการซิงโครไนซ์เธรด

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-19 เวลาอ่าน: 8 นาที

Semaphore คือพรีมิทีฟการซิงโครไนซ์ที่ควบคุมการเข้าถึงทรัพยากรที่ใช้ร่วมกันผ่านตัวนับและคิวของเธรดที่รออยู่ ตามข้อมูลจาก Wikipedia, 2024 เซมาฟอร์ถูกเสนอโดย Edsger Dijkstra ในปี 1965 เพื่อแก้ปัญหา การโต้ตอบแบบมัลติเธรด เครื่องมือนี้ช่วยให้จำกัดจำนวนเธรดที่ทำงานพร้อมกันกับส่วนวิกฤตได้

ประเด็นสำคัญ

  • Semaphore คือพรีมิทีฟการซิงโครไนซ์ที่ควบคุมการเข้าถึงผ่านตัวนับสิทธิ์
  • เซมาฟอร์แบบไบนารี รับค่า 0 และ 1 ทำหน้าที่เป็นธงบล็อก
  • เซมาฟอร์แบบนับ อนุญาตให้เธรดตามจำนวนที่ระบุเข้าถึงพร้อมกันได้
  • แตกต่างจากมิวเท็กซ์ เซมาฟอร์ไม่ได้ผูกติดกับเธรดเจ้าของ
  • Deadlock เป็นหนึ่งในอันตรายหลักเมื่อใช้เซมาฟอร์อย่างไม่ถูกต้อง

Semaphore คืออะไร?

Semaphore คือพรีมิทีฟการซิงโครไนซ์ที่ใช้ตัวนับเพื่อควบคุมการเข้าถึงทรัพยากรที่ใช้ร่วมกัน แนวคิดนี้ถูกเสนอโดย Edsger Dijkstra ในปี 1965 และกลายเป็นรากฐานสำหรับกลไกการซิงโครไนซ์สมัยใหม่ทั้งหมดในระบบปฏิบัติการ

คำจำกัดความและวัตถุประสงค์

เซมาฟอร์คือตัวแปรจำนวนเต็มที่มีการดำเนินการอะตอมมิกสองอย่าง: wait (acquire) และ signal (release) การดำเนินการ wait จะลด ตัวนับ ในขณะที่ signal จะเพิ่มขึ้น เมื่อตัวนับถึงศูนย์ เธรดที่เรียก wait จะถูกบล็อกจนกว่าเธรดอื่นจะดำเนินการ signal

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

ประวัติและพื้นฐานทางทฤษฎี

แนวคิดของเซมาฟอร์เกิดขึ้นในบริบทของระบบปฏิบัติการ THE ที่พัฒนาขึ้นที่ Technische Hogeschool Eindhoven Dijkstra ทำให้เซมาฟอร์เป็นนามธรรมทางคณิตศาสตร์ โดยพิสูจน์ความเพียงพอในการ implement พรีมิทีฟการซิงโครไนซ์ใดๆ

เซมาฟอร์ทำงานอย่างไร?

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

ตัวนับและการดำเนินการอะตอมมิก

เมื่อสร้างเซมาฟอร์ ค่าเริ่มต้นของ ตัวนับสิทธิ์ จะถูกกำหนด การเรียก acquire แต่ละครั้งจะลดตัวนับลง 1 หากหลังจากนี้ตัวนับกลายเป็นลบ เธรดจะถูกบล็อก การดำเนินการ release จะเพิ่มตัวนับและปลุกหนึ่งในเธรดที่รออยู่

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} กำลังทำงาน")
    } finally {
        semaphore.release()
    }
}

คิวรอและการจัดตารางงาน

เมื่อเธรดเรียก acquire โดยที่ตัวนับเป็นศูนย์ ระบบปฏิบัติการจะวางไว้ใน คิว FIFO ของเซมาฟอร์ เธรดจะเปลี่ยนเป็นสถานะ BLOCKED โดยไม่ใช้เวลา CPU หลังจากการเรียก release เธรดแรกในคิวจะเปลี่ยนเป็นสถานะ RUNNABLE และได้รับสิทธิ์เข้าถึงทรัพยากร

ประเภทของเซมาฟอร์

ในทฤษฎีการซิงโครไนซ์ มีการแยกแยะ เซมาฟอร์ สองประเภทหลัก: ไบนารีและการนับ การเลือกประเภทขึ้นอยู่กับงานเฉพาะของการจัดการการเข้าถึงทรัพยากร

เซมาฟอร์แบบไบนารี (Binary Semaphore)

เซมาฟอร์แบบไบนารีรับเฉพาะค่า 0 และ 1 ในพฤติกรรมมันคล้ายกับมิวเท็กซ์ แต่ไม่มี ข้อกำหนดความเป็นเจ้าของ — เธรดใดๆ ก็สามารถดำเนินการ release ได้ เซมาฟอร์เหล่านี้สะดวกสำหรับการ implement แฟล็กความพร้อมและเหตุการณ์ระหว่างเธรด

kotlin
val ready = Semaphore(0)

fun producer() {
    Thread.sleep(1000)
    ready.release()
}

fun consumer() {
    ready.acquire()
    println("ข้อมูลพร้อมแล้ว")
}

เซมาฟอร์แบบนับ (Counting Semaphore)

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

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

พารามิเตอร์เซมาฟอร์ไบนารีเซมาฟอร์แบบนับ
ช่วง0 หรือ 10 ถึง N
เธรดพร้อมกัน1สูงสุด N
การประยุกต์ใช้การส่งสัญญาณ, แฟล็กพูลทรัพยากร, การจำกัดอัตรา

Semaphore กับ Mutex

นักพัฒนามักสับสนระหว่าง เซมาฟอร์ และมิวเท็กซ์ แม้ว่าจะมีความแตกต่างพื้นฐานระหว่างทั้งสอง การเข้าใจความแตกต่างเหล่านี้มีความสำคัญอย่างยิ่งต่อการเลือกกลไกการซิงโครไนซ์ที่ถูกต้องในโปรเจกต์

หลักการความเป็นเจ้าของ

ความแตกต่างหลักคือ แนวคิดความเป็นเจ้าของ มิวเท็กซ์รู้เสมอว่าเธรดใดได้ครอบครองมัน และมีเพียงเธรดนั้นเท่านั้นที่สามารถปล่อยมันได้ เซมาฟอร์ไม่มีเจ้าของ: เธรดใดๆ ก็สามารถเรียก release ได้โดยไม่ต้องเรียก acquire สิ่งนี้ทำให้มิวเท็กซ์ปลอดภัยกว่าสำหรับการป้องกันข้อมูล และเซมาฟอร์มีความยืดหยุ่นมากกว่าสำหรับการประสานงาน

ประสิทธิภาพและกรณีการใช้งาน

ในทางปฏิบัติ มิวเท็กซ์ เร็วกว่าสำหรับการแยกส่วนร่วมกันอย่างง่ายเนื่องจากการปรับให้เหมาะสมสำหรับสถานการณ์ทั่วไป เซมาฟอร์ต้องการค่าใช้จ่ายเพิ่มเติมในการบำรุงรักษาตัวนับ อย่างไรก็ตาม สำหรับการจำกัดความขนานหรือ implement รูปแบบผู้ผลิต-ผู้บริโภค เซมาฟอร์เป็นสิ่งที่ขาดไม่ได้

ลักษณะSemaphoreMutex
ความเป็นเจ้าของไม่มีเจ้าของมีเจ้าของ
การปล่อยเธรดใดก็ได้เฉพาะเธรดเจ้าของ
ตัวนับ0 ถึง Nไบนารี
กรณีการใช้งานการจำกัดความขนานและการส่งสัญญาณการป้องกันส่วนวิกฤต
การเรียกซ้ำไม่ใช่ (เข้าซ้ำได้)

Semaphore ในการพัฒนามือถือ

ในการพัฒนาแอปพลิเคชันมือถือ Semaphore ใช้สำหรับจัดการการเข้าถึงทรัพยากรที่มีจำกัด: การเชื่อมต่อเครือข่าย ไฟล์ ฐานข้อมูล และส่วนประกอบฮาร์ดแวร์ แพลตฟอร์มสมัยใหม่มี built-in implementation ที่สะดวก

พูลการเชื่อมต่อเซิร์ฟเวอร์

กรณีการใช้งานทั่วไปคือ พูลการเชื่อมต่อ HTTP แอปพลิเคชันสามารถส่งคำขอพร้อมกันสูงสุด 4 คำขอไปยังเซิร์ฟเวอร์ เนื่องจาก API ของผู้ให้บริการจำกัดความขนาน เซมาฟอร์ที่มีค่าเริ่มต้น 4 รับประกันว่าภายใต้โหลดใดๆ จำนวนคำขอพร้อมกันจะไม่เกินขีดจำกัด ในขณะที่เธรดอื่นรอในคิว

หากไม่มีเซมาฟอร์ การเพิ่มขึ้นอย่างรวดเร็วของกิจกรรมผู้ใช้อาจทำให้โครงสร้างพื้นฐานของเซิร์ฟเวอร์โอเวอร์โหลดกะทันหัน นำไปสู่การหมดเวลาและข้อผิดพลาด 429 Too Many Requests Semaphore ทำหน้าที่เหมือนฟิวส์ อนุญาตให้มีการเรียกพร้อมกันตามจำนวนที่กำหนดอย่างเคร่งครัด โดยไม่ขึ้นกับจำนวนเธรดที่ทำงานอยู่

Semaphore ใน Kotlin สำหรับ Android

Android มีคลาส Semaphore จากแพ็คเกจ java.util.concurrent มาดูตัวอย่างการจำกัดคำขอเครือข่ายพร้อมกันเป็นสองเธรดเพื่อป้องกันการโอเวอร์โหลดของเซิร์ฟเวอร์

kotlin
class ApiClient {
    private val throttle = Semaphore(2)

    suspend fun fetch(url: String): Result {
        throttle.acquire()
        return try {
            httpGet(url)
        } finally {
            throttle.release()
        }
    }
}

DispatchSemaphore ใน Swift สำหรับ iOS

ใน iOS DispatchSemaphore จาก GCD แก้ปัญหาเดียวกัน นักพัฒนาใช้มันเพื่อซิงโครไนซ์การเข้าถึงทรัพยากรในโค้ดแบบอะซิงโครนัสโดยไม่บล็อกเธรดหลัก

swift
let semaphore = DispatchSemaphore(value: 3)

func processBatch(_ items: [UIImage]) {
    for img in items {
        semaphore.wait()
        DispatchQueue.global().async {
            applyFilter(to: img)
            semaphore.signal()
        }
    }
}

ข้อผิดพลาดทั่วไปเมื่อทำงานกับเซมาฟอร์

ข้อผิดพลาดที่พบบ่อยที่สุดคือ การลืม release เมื่อเกิดข้อยกเว้น หากเธรดสิ้นสุดด้วยข้อผิดพลาดก่อนเรียก release เซมาฟอร์จะถูกบล็อกถาวรสำหรับเธรดอื่น ใช้ try/finally หรือ defer เพื่อรับประกันการปล่อย ปัญหาที่สองคือ deadlock เมื่อครอบครองหลายเซมาฟอร์ในลำดับที่แตกต่างกันโดยเธรดที่แตกต่างกัน

รูปแบบการใช้เซมาฟอร์

เซมาฟอร์ไม่ได้ใช้เฉพาะสำหรับการป้องกันข้อมูล แต่ยังใช้สำหรับ การประสานงานเธรด ในสถานการณ์มัลติเธรดที่ซับซ้อน การรู้รูปแบบทั่วไปช่วยเร่งการพัฒนาและลดโอกาสเกิดข้อผิดพลาดในการซิงโครไนซ์

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

ตัวจำกัดอัตรา (Rate Limiter)

เซมาฟอร์ที่มีค่าเริ่มต้น N และการปล่อยเป็นระยะผ่านตัวจับเวลา implement การจำกัดอัตรา ของคำขอ API ตัวอย่างเช่น บริการอนุญาต 10 คำขอต่อวินาที: เซมาฟอร์เริ่มที่ 10 แต่ละคำขอจะลดตัวนับ และ TimerTask แยกต่างหากจะคืนค่าตัวนับเป็นค่าเริ่มต้นทุกวินาที สิ่งนี้ปกป้องทั้งแอปพลิเคชันและเซิร์ฟเวอร์จากการโอเวอร์โหลด

ผู้ผลิต-ผู้บริโภคผ่านเซมาฟอร์

ในปัญหาคลาสสิกของผู้ผลิต-ผู้บริโภค เซมาฟอร์สองตัว จัดการบัฟเฟอร์: empty (สิทธิ์การเขียน) และ full (สิทธิ์การอ่าน) ผู้ผลิตเรียก acquire ที่ empty และ release ที่ full ในขณะที่ผู้บริโภคทำตรงกันข้าม โครงร่างนี้รับประกันว่าผู้บริโภคจะไม่อ่านบัฟเฟอร์ว่าง และผู้ผลิตจะไม่ทำให้มันล้น

โครงร่างเดียวกันนี้เป็นพื้นฐานของ bounded buffer ในระบบปฏิบัติการ — บัฟเฟอร์แบบวงแหวน ที่มีขนาดคงที่ ในแอปพลิเคชันมือถือ รูปแบบนี้ใช้สำหรับประมวลผลคิวของรูปภาพ ไฟล์วิดีโอ และเหตุการณ์วิเคราะห์

การจำกัดคำขอเครือข่าย

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

คำถามที่พบบ่อย

ความแตกต่างระหว่าง Semaphore และตัวนับปกติคืออะไร?

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

เซมาฟอร์สามารถทำให้เกิด deadlock ได้หรือไม่?

ใช่ deadlock เป็นไปได้เมื่อครอบครองหลายเซมาฟอร์ในลำดับที่แตกต่างกันโดยเธรดที่แตกต่างกัน ตัวอย่างเช่น เธรด A ครอบครอง S1 แล้ว S2 ในขณะที่เธรด B ครอบครอง S2 แล้ว S1 กำหนดลำดับการครอบครองเดียวสำหรับเซมาฟอร์ทั้งหมดในโปรเจกต์

เกิดอะไรขึ้นเมื่อเรียก acquire โดยที่ตัวนับเป็นศูนย์?

เธรดจะ ถูกบล็อก และเข้าสู่สถานะรอ มันไม่ใช้เวลา CPU จนกว่าเธรดอื่นจะเรียก release ใน Java นี่คือสถานะ BLOCKED ใน Swift เธรดจะถูกระงับโดย GCD

Binary Semaphore แตกต่างจาก Mutex โดยพื้นฐานอย่างไร?

ความแตกต่างหลักคือ ความเป็นเจ้าของ Mutex สามารถปล่อยได้โดยเธรดเจ้าของเท่านั้น Binary Semaphore สามารถปล่อยได้โดยเธรดใดก็ได้ ซึ่งสะดวกสำหรับการส่งสัญญาณระหว่างเธรดแต่ปลอดภัยน้อยกว่าสำหรับการปกป้องความสมบูรณ์ของข้อมูล

ควรเลือกค่าเริ่มต้นใดสำหรับตัวนับ?

ค่าเริ่มต้นขึ้นอยู่กับ สถานการณ์ สำหรับการปกป้องทรัพยากรเดียว — 1 สำหรับพูลของการเชื่อมต่อ N — N สำหรับการส่งสัญญาณระหว่างเธรด ให้ใช้ 0 เพื่อให้เธรดผู้บริโภครอสัญญาณจากผู้ผลิต

สรุป

  • Semaphore คือพรีมิทีฟการซิงโครไนซ์ที่ใช้ตัวนับซึ่งเสนอโดย Dijkstra ในปี 1965
  • เซมาฟอร์ไบนารี รับค่า 0 และ 1; เซมาฟอร์แบบนับรับค่าที่ไม่เป็นลบใดๆ
  • การดำเนินการ acquire และ release เป็นแบบอะตอมมิกและปลอดภัยต่อเธรด
  • แตกต่างจากมิวเท็กซ์ เซมาฟอร์ไม่ได้ผูกติดกับเธรดเจ้าของ
  • เซมาฟอร์แบบนับ ใช้สำหรับจัดการพูลทรัพยากรและจำกัดความขนาน
  • การลืม release เป็นข้อผิดพลาดที่พบบ่อยที่สุดที่นำไปสู่การค้างของเธรด

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม