原文地址: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)
}