多理发师问题(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<Int>()
//    val repliedCustomers = ConcurrentLinkedDeque<Int>()

    private val barberCustomerSemas = Hashtable<Int, Pair<Semaph, Semaph>>()
    val customerThreads = LinkedList<Thread>()

    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<Int, Pair<Semaph, Semaph>>()
    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<Job>()

    val seatedCustomers = Chan<Int>()
    val customerChans = mutableMapOf<Int, Chan<Int>>()
    val barberChans = mutableMapOf<Int, Chan<Int>>()
    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<Int>(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<Int>(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容