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

Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน

ปกป้องสถานะที่เปลี่ยนแปลงได้และใช้ร่วมกันด้วย Mutex พร้อมจำกัดการทำงานพร้อมกันด้วย Semaphore

Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

ปัญหาสถานะที่ใช้ร่วมกัน

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    var counter = 0
    val jobs = List(1000) {
        launch(Dispatchers.Default) { counter++ } // race condition!
    }
    jobs.forEach { it.join() }
    println("Expected 1000, got: $counter") // likely wrong
}

พื้นฐาน Mutex

Mutex เป็นล็อกแบบกีดกันร่วมกันสำหรับโครูทีน โดยจะพักการทำงานแทนการบล็อกผู้เรียกจนกว่าล็อกจะพร้อมใช้งาน

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    var counter = 0
    val jobs = List(1000) {
        launch(Dispatchers.Default) {
            mutex.withLock { counter++ }  // safe
        }
    }
    jobs.forEach { it.join() }
    println("Counter: $counter") // 1000
}

ส่วนขยาย withLock

mutex.withLock { ... } จะยึดล็อก เรียกใช้บล็อก และปล่อยล็อก แม้จะเกิดข้อยกเว้นหรือการยกเลิกก็ตาม

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    var list = mutableListOf<Int>()
    List(10) {
        launch {
            mutex.withLock {
                list.add(it)
            }
        }
    }.forEach { it.join() }
    println(list.sorted())
}

lock / unlock ด้วยตนเอง

คุณสามารถเรียก lock() และ unlock() โดยตรงเพื่อควบคุมได้ละเอียดขึ้น แต่ควรเลือกใช้ withLock เพื่อป้องกันการลืมปลดล็อก

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    mutex.lock()
    try {
        println("Critical section")
    } finally {
        mutex.unlock()  // must always release
    }
}

Mutex ไม่เป็นแบบ Reentrant

Mutex ของ Kotlin ไม่เป็นแบบ reentrant การเรียก lock() อีกครั้งจากโครูทีนเดิมจะทำให้เกิดการหยุดชะงัก ใช้ตัวนับหรือปรับโครงสร้างใหม่เพื่อหลีกเลี่ยงการล็อกซ้อนกัน

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    // This deadlocks:
    // mutex.withLock { mutex.withLock { println("never") } }
    mutex.withLock {
        println("Acquired once — safe")
    }
}

พื้นฐาน Semaphore

Semaphore(n) อนุญาตให้โครูทีนทำงานพร้อมกันได้สูงสุด n ตัว เปรียบเสมือนระบบบัตรที่มีช่องว่าง n ช่อง

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val semaphore = Semaphore(3) // max 3 concurrent
    List(10) { i ->
        launch {
            semaphore.withPermit {
                println("Concurrent task $i"); delay(100)
            }
        }
    }.forEach { it.join() }
}

ส่วนขยาย withPermit

semaphore.withPermit { ... } จะขอสิทธิ์ใช้งาน เรียกใช้บล็อก แล้วปล่อยสิทธิ์ เป็นรูปแบบที่ปลอดภัยสำหรับโครูทีนและเทียบเท่ากับการลองขอสิทธิ์จากเซมาฟอร์

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val sem = Semaphore(2)
    val results = mutableListOf<String>()
    List(5) { i ->
        launch {
            sem.withPermit {
                delay(50)
                synchronized(results) { results.add("task-$i") }
            }
        }
    }.forEach { it.join() }
    println(results)
}

จำกัดอัตราด้วย Semaphore

ใช้เซมาฟอร์เพื่อจำกัดจำนวนการเรียก API ที่ทำงานพร้อมกัน โดยจำกัดให้มีคำขอ N รายการในแต่ละครั้ง

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
val apiSemaphore = Semaphore(5) // max 5 concurrent requests
suspend fun callApi(id: Int): String {
    return apiSemaphore.withPermit {
        delay(100) // simulate network
        "Response-$id"
    }
}
fun main() = runBlocking {
    val results = (1..20).map { async { callApi(it) } }.awaitAll()
    println("Got ${results.size} responses")
}

Mutex กับ Semaphore

Mutex: เซมาฟอร์แบบไบนารีที่มีสิทธิ์ 1 รายการ สำหรับการเข้าถึงแบบผูกขาด Semaphore(n): มีสิทธิ์ n รายการ สำหรับการทำงานพร้อมกันในขอบเขตจำกัด ใช้ Mutex เพื่อปกป้องทรัพยากร และใช้ Semaphore เพื่อจำกัดการทำงานแบบขนาน

import kotlinx.coroutines.sync.*
// Mutex = Semaphore(1) for exclusive access
// Semaphore(n) = n-way concurrency limit
val exclusive = Mutex()          // one at a time
val limited   = Semaphore(3)     // three at a time
fun main() { println("Mutex for exclusion; Semaphore for rate limiting") }

ตัวนับแบบอะตอมิกด้วย Mutex

สร้างคลาสตัวนับที่ปลอดภัยต่อเธรด โดยใช้ Mutex เพื่อปกป้องการเพิ่มค่าและการอ่านค่า

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
class AtomicCounter {
    private val mutex = Mutex()
    private var count = 0
    suspend fun increment() = mutex.withLock { count++ }
    suspend fun get() = mutex.withLock { count }
}
fun main() = runBlocking {
    val counter = AtomicCounter()
    List(100) { launch { counter.increment() } }.forEach { it.join() }
    println("Count: ${counter.get()}")
}

ทางเลือก: ตัวจัดส่งงานแบบเธรดเดียว

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    val singleThread = newSingleThreadContext("CounterThread")
    var counter = 0
    List(1000) {
        launch(singleThread) { counter++ }
    }.forEach { it.join() }
    println("Counter: $counter") // 1000, no mutex needed
    singleThread.close()
}

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ความแตกต่างสำคัญระหว่าง Mutex กับ Semaphore คืออะไร

สรุปทบทวน

Mutex ให้สิทธิ์เข้าถึงสถานะที่ใช้ร่วมกันแบบผูกขาด โดยควรเลือกใช้ withLock ส่วน Semaphore(n) จำกัดการทำงานพร้อมกันไว้ที่ n ทั้งสองแบบจะพักการทำงานของโครูทีนที่รอ แทนการบล็อก ในกรณีง่าย ๆ ตัวจัดส่งงานแบบเธรดเดียวมักมีโครงสร้างที่ชัดเจนกว่า

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

บทเรียน “Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน”

ปกป้องสถานะที่เปลี่ยนแปลงได้และใช้ร่วมกันด้วย Mutex พร้อมจำกัดการทำงานพร้อมกันด้วย Semaphore คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

  1. พื้นฐาน Channel: send, receive และ close
  2. ชนิดของ Channel: Rendezvous, Buffered, Conflated, Unlimited
  3. Mutex และ Semaphore สำหรับสถานะที่ใช้ร่วมกัน
  4. Actor และการจัดการสถานะแบบมีโครงสร้าง
← กลับไปที่ Kotlin Academy