LeetCode Go 并发题详解:交替打印字符串

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode Go 并发题详解:交替打印字符串相关的知识,希望对你有一定的参考价值。

原文地址:https://mp.weixin.qq.com/s/K032xlARjiyS8ecJrqZXaA

本题 LeetCode 链接:

https://leetcode.com/problems/fizz-buzz-multithreaded/

本题题目

给定一个数列从 1 ~ n,依序输出,但是:

  • 如果 n 可以被 3 整除,输出 "fizz"
  • 如果 n 可以被 5 整除,输出 "buzz"
  • 如果 n 同时可以被 3 与 5 整除,输出 "fizzbuzz"

实战要求:使用 4 个执行线程实现一个多执行线程版本。一个 FizzBuzz 的 instance 要被传递到以下四个执行线程中:

  • Thread A 会调用 fizz()     以检查 n 是否可以被    3 整除?若可以就输出 fizz
  • Thread B 会调用 buzz()     以检查 n 是否可以被    5 整除?若可以就输出 buzz
  • Thread C 会调用fizzbuzz() 以检查 n 是否可以被 3, 5 整除?若可以就输出 fizzbuzz
  • Thread D 会调用 number()   照常输出原本数字 n

本题考核难点?判断责任去中心化!

我一开始认为「这题没什么难的嘛~还不就那些套路再用一次!」,所以最早的实现版本,是写了一个中心控管的 goroutine,判断整除条件后,再把输出任务透过 channel 发派给其他 goroutine A, B, C, D。

直到我为了分享这题,将英文题目翻译为中文的时候,才发现自己误解题目了(尴尬)!题目真正的要求更困难,要各个 goroutine 自行负担检查整除条件的责任。所以只好重写 XD

在过去的 LeetCode Concurrency 详解中,我提到过很多次:

goroutine 若不刻意控制,将无法保证执行的先后顺序,因此本题就是要考核对 goroutine 顺序控制的能力。

但前面几题的解法,大多是把判断责任中心化,方便控管顺序。这次,与前面几题不同的是,这一题要求把判断责任分散到 thread A, B, C 中,所以每个 goroutine 也无法准确得知下一个要接棒的 goroutine 是哪一个?这样的顺序控制会由于分散化,变得更加困难。

By the way,我还解过「DiningPhilosophers」这一题用的就是去中心化方法,但目前还没写那一题详解。

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

type FizzBuzz struct {
    n int
    wg *sync.WaitGroup
    streamBaton chan int
}

func (this *FizzBuzz) PrintLoop(passCondition func(int) bool, printString func(int)) {
    defer this.wg.Done()

    for i := 0; i <= this.n; i++ {
        if passCondition(i) {
            nextNum := <-this.streamBaton //接棒
            if i == nextNum {
                printString(i)
                this.streamBaton <- i + 1 //交棒
            } else {
                this.streamBaton <- nextNum //把數字還回去
                i--
            }
            runtime.Gosched()
        }
    }
}

func (this *FizzBuzz) PrintFizz() {
    PassCondition := func(i int) bool { return (0 == i%3) && (0 != i%5) }
    PrintString := func(i int) { fmt.Printf("Fizz(%d), ", i) }

    this.PrintLoop(PassCondition, PrintString)
}

func (this *FizzBuzz) PrintBuzz() {
    PassCondition := func(i int) bool { return (0 != i%3) && (0 == i%5) }
    PrintString := func(i int) { fmt.Printf("Buzz(%d), ", i) }

    this.PrintLoop(PassCondition, PrintString)
}

func (this *FizzBuzz) PrintFizzBuzz() {
    PassCondition := func(i int) bool { return 0 == i%(3*5) }
    PrintString := func(i int) { fmt.Printf("FizzBuzz(%d), ", i) }

    this.PrintLoop(PassCondition, PrintString)
}

func (this *FizzBuzz) PrintNumber() {
    PassCondition := func(i int) bool { return (0 != i%3) && (0 != i%5) }
    PrintString := func(i int) { fmt.Printf("%d, ", i) }

    this.PrintLoop(PassCondition, PrintString)
}

func main() {
    start := time.Now()

    for testCase := 0; testCase <= 20; testCase++ {

        fizzbuzz := &FizzBuzz{
            n: testCase,
            wg: &sync.WaitGroup{},
            streamBaton: make(chan int, 1),
        }

        fizzbuzz.wg.Add(4)
        go fizzbuzz.PrintFizz()
        go fizzbuzz.PrintBuzz()
        go fizzbuzz.PrintFizzBuzz()
        go fizzbuzz.PrintNumber()
        fizzbuzz.streamBaton <- 0 //啟動交棒
        fizzbuzz.wg.Wait()
        close(fizzbuzz.streamBaton)

        fmt.Println() //這個 Test Case 結束了,換行。
    }

    spentTime := time.Now().Sub(start)
    fmt.Println("Spent time:", spentTime)
}

以上是关于LeetCode Go 并发题详解:交替打印字符串的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode——多线程问题汇总

leetcode-1195-交替打印字符串

LeetCode(多线程)- 1195. 交替打印字符串

Java面试题:交替打印字符串

多线程面试题之三线程按顺序交替打印ABC的方法

Go实现交替打印数字和字⺟