print in order,并发下顺序打印数字

708 阅读2分钟

题目

创建N个线程(1, 2, 3……N)(线程或协程,这里统一称为线程,下同),要求每个线程按顺序打印一个数字,数字每次加1。当数字达到MAX时,所有的线程正常退出。要求除这N个线程以外,不能有外部线程参与管理。

例如,N=3时,程序的执行顺序为线程1打印1,线程2打印2,线程3打印3,线程1打印4……

思路

这题我被面试面到过,当时没见到过这种并发问题,当时程序写出来了,但是思路也挺混乱的。恰好我又看到了这一篇文章,就想把这个经典问题给终结掉。

这题大体上有两种思路,一种思路是每个线程在运行完成后,顺势通知下一个线程运行,图解如下

Thread AThread BThread C
A.Wait()B.Wait()C.Wait()
print(number)print(number2)print(number3)
number ++number ++number ++
A.Add()B.Add()C.Add()
B.Done()C.Done()A.Done()

另一种思路是通过多个线程间共享一个变量,然后多个线程争抢锁,并判断是否轮到自己打印数字。图解如下

Thread AThread BThread C
lock()lock()lock()
A = numberB = numberC = number
unlock()unlock()unlock()
if A % 3 == 0if B % 3 == 1if C % 3 == 2
print(A);number++print(B);number++print(C);number++

第一种方法没有锁的争抢,第二种方法有锁的争抢。

实现

两种方法都有实现,代码如下

solution1是无锁的,solution2是有锁的

 package main
 ​
 import (
     `sync`
 )
 ​
 const (
     MAX      = 100000       // 打印多少值
     GoCount = 4             // 几个协程
 )
 ​
 func main() {
     //solution1(MAX, GoCount)
     solution2(MAX, GoCount)
 }
 ​
 func solution2(max, goCount int) *[]int{
     lock := sync.Mutex{}
     wg := sync.WaitGroup{}
     result := make([]int, 0, max)
 ​
     count := 1
     wg.Add(goCount)
     for i := 0; i < goCount; i++ {
         go func(i int) {
             for {
                 lock.Lock()
                 now := count
                 lock.Unlock()
 ​
                 if now > max {
                     wg.Done()
                     return
                 }
                 if now % goCount == i {
                     //fmt.Println(now)
                     result = append(result, now)
                     count ++
                 }
             }
         }(i)
     }
     wg.Wait()
     return &result
 }
 ​
 func solution1(max, goCount int) *[]int{
     result := make([]int, 0, max)
     wgLine := make([]*sync.WaitGroup, goCount, goCount)
     wg := &sync.WaitGroup{}
     for i := 0; i < goCount; i++ {
         wgLine[i] = &sync.WaitGroup{}
         wgLine[i].Add(1)
     }
 ​
     count := 1
     wg.Add(goCount)
     for i := 0; i < goCount; i++ {
         go func(max int, selfWg, nextWg *sync.WaitGroup) {
             for {
                 selfWg.Wait()
                 if count > max {
                     wg.Done()
                     selfWg.Add(1)
                     nextWg.Done()
                     return
                 }
                 //println(count)
                 result = append(result, count)
                 count++
                 selfWg.Add(1)
                 nextWg.Done()
             }
         }(max, wgLine[i], wgLine[(i+goCount-1)%goCount])
         if i == 0 {
             wgLine[goCount-1].Done()
         }
     }
     wg.Wait()
     return &result
 }

正确性验证

利用go test实现的正确性测试

 package main
 ​
 import (
    `testing`
 )
 ​
 const (
    TestCount = 10
 )
 ​
 func TestSolution1(t *testing.T) {
    for i := 0; i < TestCount; i++ {
       result := solution1(MAX, TestCount)
       testResult(result, t)
    }
 }
 ​
 func TestSolution2(t *testing.T) {
    for i := 0; i < TestCount; i++ {
       result := solution2(MAX, TestCount)
       testResult(result, t)
    }
 }
 ​
 func testResult(result *[]int, t *testing.T) {
    for i, v := range *result {
       if i+1 != v {
          t.Error("错了!")
       }
    }
 }

性能测试

性能测试也是基于go test bench

 func BenchmarkSolution1(b *testing.B) {
    for i := 0; i < b.N; i++ {
       solution1(MAX, TestCount)
    }
 }
 ​
 ​
 func BenchmarkSolution2(b *testing.B) {
    for i := 0; i < b.N; i++ {
       solution2(MAX, TestCount)
    }
 }

在我windows10系统上,9代i5 4核,测试出来性能如下(MAX为100000,协程数为4)

 PS C:\Project\mygo\src> go test -bench .
 goos: windows
 goarch: amd64
 pkg: mygo
 BenchmarkSolution1-8          36          31884292 ns/op
 BenchmarkSolution2-8           3         390073667 ns/op

\