多理发师问题(kotlin 多线程/coroutine)

理发师问题

一个理发师,一把理发椅,n把等候理发的顾客椅子,如果没有顾客则理发师便在理发椅上睡觉 ,当有一个顾客到达时,首先看理发师在干什么,如果理发师在睡觉,则唤醒理发师理发,如果理发师正在理发,则查看是否有空的顾客椅子可坐, 如果有,坐下等待,如果没有,则离开。

简化成如下步骤:

  1. 理发师等待顾客
  2. 顾客尝试进入等待区(人满则离开)
  3. (step0) 理发师招待顾客
  4. (step1) 顾客响应
  5. (step2) 理发师理发到完成
  6. (step3) 顾客离开
  7. (step4) 理发师结束,继续step0 招待新顾客
  • 要求:
    1. 每个顾客一个线程,每个理发师一个线程
    2. 每个步骤都打印
    3. 确保2-7是顺序执行的
    4. 1个理发师,多个顾客

思考下该怎么实现呢?















单理发师多线程版

为了保证 2-7的顺序执行,我们只需要让3-7每个步骤一个信号量,前一个步骤结束时释放下一个步骤的信号量。
kotlin 例子如下:

abstract class SleepingBarberSimulator(val seats: Int){
    companion object {
        const val BARBER_PREP_SEC = 3
        const val BARBER_WORK_SEC = 8
        const val BARBER_REST_SEC = 3

        const val BARBER_WORK_VAR = 2
    }
    private val rand = Random()

    abstract fun newBarber()
    abstract fun newCustomer()
    abstract fun joinAllCustomers()

    protected fun logBarber(msg: String) = println("=".repeat(100)+"Barber "+msg)
    protected fun barberWorkSpeed() = BARBER_WORK_SEC - BARBER_WORK_VAR + rand.nextInt(BARBER_WORK_VAR*2)
}

class MultiThreadSleepingBarberSimulator(seats: Int): SleepingBarberSimulator(seats){
    private val bid = AtomInt()
    private val cid = AtomInt()
    private val steps = (0 until 10).map{ Semaph(0) }

    private val freeSeats = AtomInt(seats)
    private val repliedCustomers = ConcurrentDeque()
//    val repliedCustomers = ConcurrentLinkedDeque()

    private val barberCustomerSemas = Hashtable>()
    val customerThreads = LinkedList()

    override fun newBarber(){
        val id = bid.inc()
        val workSpeed = barberWorkSpeed()
        Thread{
            logBarber("$id comes and make some prepare")
            sleep(BARBER_PREP_SEC)

            while(true){
                steps[0].acquire()
                logBarber("$id asking for a customer")
                steps[1].release()

                steps[2].acquire()
                val customer = repliedCustomers.poll()
                logBarber("$id working on customer $customer")
                sleep(workSpeed)
                logBarber("$id done on customer $customer")
                steps[3].release()

                steps[4].acquire()
                logBarber("$id has a rest")
                sleep(BARBER_REST_SEC)
                barberCustomerSemas.remove(customer)
            }
        }.apply{ start() }
    }

    override fun newCustomer() {
        val id = cid.inc()
        customerThreads.add(Thread{
            println("---Customer $id comes")
            if(freeSeats.dec() < 0){
                println("!!!!!!!!!!!!Customer $id has no seat and leaves")
                freeSeats.inc()
                return@Thread
            }
            println("---Customer $id sits and waits")
            steps[0].release()

            steps[1].acquire()
            println("---Customer $id responds, stands up and is served")
            freeSeats.inc()
            repliedCustomers.add(id)
            barberCustomerSemas[id] = Semaph(0) to Semaph(0)
            steps[2].release()

            steps[3].release()
            println("---Customer $id done and leaves")
            steps[4].release()
        }.apply{ start() })
    }
    override fun joinAllCustomers() = customerThreads.forEach{ it.join() }
    private fun sleep(nSecond: Int) = Thread.sleep(nSecond * 1000L)
}

多个理发师

区别在于我们此时需要对理发师和顾客进行配对,也意味着是从step2开始不能再完全共享信号量,而是按照配对情况来共享。

    private val barberCustomerSemas = Hashtable>()
    override fun newBarber(){
        val id = bid.inc()
        val workSpeed = barberWorkSpeed()
        Thread{
            logBarber("$id comes and make some prepare")
            sleep(BARBER_PREP_SEC)

            while(true){
                steps[0].acquire()
                logBarber("$id asking for a customer")
                steps[1].release()

                steps[2].acquire()
                val customer = repliedCustomers.poll()
                logBarber("$id working on customer $customer")
                sleep(workSpeed)
                logBarber("$id done on customer $customer")
                val (cs, bs) = barberCustomerSemas[customer]!!
                cs.release()

                bs.acquire()
                logBarber("$id has a rest")
                sleep(BARBER_REST_SEC)
                barberCustomerSemas.remove(customer)
            }
        }.apply{ start() }
    }

    override fun newCustomer() {
        val id = cid.inc()
        customerThreads.add(Thread{
            println("---Customer $id comes")
            if(freeSeats.dec() < 0){
                println("!!!!!!!!!!!!Customer $id has no seat and leaves")
                freeSeats.inc()
                return@Thread
            }
            println("---Customer $id sits and waits")
            steps[0].release()

            steps[1].acquire()
            println("---Customer $id responds, stands up and is served")
            freeSeats.inc()
            repliedCustomers.add(id)
            barberCustomerSemas[id] = Semaph(0) to Semaph(0)
            steps[2].release()

            val (cs, bs) = barberCustomerSemas[id]!!
            cs.acquire()
            println("---Customer $id done and leaves")
            bs.release()
        }.apply{ start() })
    }

单线程coroutine版

(单线程)coroutine让实现变得更简单,因为在单线程下共享状态,不需要担心CPU缓存不一致,不需要加锁来保证原子性。

每个customer/barber一个coroutine,coroutine间的同步我们可以用channel来完成。
这里只需要每个customer/barber一个ReceiveChannel(相当于一个actor,可以用unbuffered(bufferSize=1) Channel 来使得send和receive都等待),
同步时往配对的channel里send/receive。

class CoroutineSleepingBarberSimulator(seats: Int): SleepingBarberSimulator(seats){     // by single thread coroutine
    var bid = 0
    var cid = 0
    val scope = CoroutineScope(newSingleThreadContext("sleep barbers"))
    val customerJobs = LinkedList()

    val seatedCustomers = Chan()
    val customerChans = mutableMapOf>()
    val barberChans = mutableMapOf>()
    override fun newBarber(){
        val id = ++bid
        val workSpeed = barberWorkSpeed()
        scope.launch {
            logBarber("$id comes and make some prepare")
            sleep(BARBER_PREP_SEC)
            while(true){
                val customer = seatedCustomers.receive()
                val customerChan = customerChans[customer]!!
                val chan = Chan(1)
                barberChans[id] = chan
                logBarber("$id asking for a customer")
                customerChan.send(id)

                chan.receive()
                logBarber("$id working on customer $customer")
                sleep(workSpeed)
                logBarber("$id done on customer $customer")
                customerChan.send(0)

                chan.receive()
                logBarber("$id has a rest")
                sleep(BARBER_REST_SEC)
                barberChans.remove(id)
            }
        }
    }
    override fun newCustomer(){
        val id = ++cid
        customerJobs.add(scope.launch {
            println("---Customer $id comes")
            if(seatedCustomers.size() >= seats){
                println("!!!!!!!!!!!!Customer $id has no seat and leaves")
                return@launch
            }
            println("---Customer $id sits and waits")
            val chan =  Chan(1)
            customerChans[id] = chan
            seatedCustomers.send(id)

            val barber = chan.receive()
            val barberChan = barberChans[barber]!!
            println("---Customer $id responds, stands up and is served by barber $barber")
            barberChan.send(0)

            chan.receive()
            println("---Customer $id done and leaves")
            customerChans.remove(id)
            barberChan.send(0)
        })
    }
    override fun joinAllCustomers() = runBlocking { customerJobs.forEach { it.join() } }
    private suspend fun sleep(nSecond: Int) = delay(nSecond * 1000L)
}

附加问题: 打印sleep/awake

对于step0: 要让理发师在没有顾客时打印"sleep", 并且如果sleep那么唤醒时需要额外打印"awake"。
那么该如何实现?

References

https://en.wikipedia.org/wiki/Sleeping_barber_problem
完整代码参见: https://github.com/davidhuangdw/kotlin.concurrent/blob/master/src/main/kotlin/examples/sleeping_barbers.kt

你可能感兴趣的:(多理发师问题(kotlin 多线程/coroutine))