多线程轮流打印问题

实现两个协程,其中一个产生随机数并写入到 chan 中,另一个从 chan 中读取,并打印出来,最终输出 5 个随机数

经典的“生产者消费者问题”

固定值交替打印

Tip

固定值交替打印比较简单

可以看到,无论是打印数字,还是打印字母,实际上没有区别。无非是打印数字可以直接通过遍历 make(chan token)

核心是构造一个current channext chan的方法

常见题目如下:

  • 假设有 4 个协程,分别编号为 1/2/3/4,每秒钟会有一个协程打印出自己的编号,现在要求输出编号总是按照 1/2/3/4 这样的顺序打印,共打印 100 次
  • 编写一个程序,开启 3 个线程,这 3 个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出结果必须按 ABC 的顺序显示;如:ABCABC...依次递推
  • 轮流打印 dog pig cat,共打印 10 次?
  • 两个人 bob 和 annie,互相喊对方的名字 10 次后,最终 bob 对 annie 说 bye bye?

无非是打印点什么数字、字符串之类的东西。我们这里就以打印数字为例。


// 假设有 4 个协程,分别编号为 1/2/3/4; 每秒钟会有一个协程打印出自己的编号; 现在要求输出编号总是按照 1/2/3/4 这样的顺序打印; 共打印 100 次;
func main() {
 inChan := make(chan int)
 outChan := make(chan int)

 go func() {
  for i :=0; i< 100; i++  {
   vx := []int{1, 2, 3, 4}
   for _, v := range  vx{
    inChan <- v
   }

   // for i := 0; i < num; i++ {
   //  inChan <- num
   // }
  }
  close(inChan)
 }()

 go func(inChan <-chan int){
  for v := range inChan {
   outChan <- v
   time.Sleep(time.Second)
  }

  // for  {
  //  token := <- inChan
  //  time.Sleep(time.Second)
  //  outChan <- token
  // }

  close(outChan)
 }(inChan)

 for out := range outChan {
  fmt.Println(out)
 }
}

package main

import (
 "fmt"
 "time"
)

// 假设有 4 个协程,分别编号为 1/2/3/4; 每秒钟会有一个协程打印出自己的编号; 现在要求输出编号总是按照 1/2/3/4 这样的顺序打印; 共打印 100 次;
func main() {
 nums := pubNums(1, 2, 3, 4)
 nu := subNums(nums)
 for n := range nu {
  fmt.Println(n)
 }
}

func pubNums(nums ...int) <-chan int {
 out := make(chan int)
 go func() {
  for i := 0; i < 100; i++ {
   for _, num := range nums {
    out <- num
    time.Sleep(time.Second)
   }
  }
  close(out)
 }()
 return out
}

func subNums(in <-chan int) <-chan int {
 out := make(chan int)
 go func() {
  for ik := range in {
   fmt.Println(ik)
  }
  close(out)
 }()
 return out
}

package main

import (
 "fmt"
 "time"
)

type token struct {}
// [channel 实战应用,这篇就够了! - SegmentFault 思否](https://segmentfault.com/a/1190000039056339)

func main()  {
 num := 4

 var chs []chan token
 for i := 0; i < num; i++ {
  chs = append(chs, make(chan token, 1))
 }
 for j := 0; j < num; j++ {
  go worker(j, chs[j], chs[(j+1)%num])
 }

 chs[0] <- token{}
 select {

 }
}

func worker(id int, ch chan token, next chan token) {
 for  {
  token := <-ch
  fmt.Println(id +1)
  time.Sleep(time.Second)
  next <- token
 }
}


package main

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

type token struct {}
var wg = sync.WaitGroup{}
// [channel 实战应用,这篇就够了! - SegmentFault 思否](https://segmentfault.com/a/1190000039056339)
func main()  {
 num := 4

 var chs []chan token
 for i := 0; i < num; i++ {
  wg.Add(1)
  chs = append(chs, make(chan token, 1))
 }
 for j := 0; j < num; j++ {
  go worker(j, chs[j], chs[(j+1)%num])
 }

 chs[0] <- token{}
 wg.Wait()
 select {

 }
}

func worker(id int, ch chan token, next chan token) {
 defer wg.Done()
 for  {
  token := <-ch
  fmt.Println(id +1)
  time.Sleep(time.Second)
  next <- token
 }
}
package main

import (
 "fmt"
 "time"
)

// 假设有 4 个协程,分别编号为 1/2/3/4; 每秒钟会有一个协程打印出自己的编号; 现在要求输出编号总是按照 1/2/3/4 这样的顺序打印; 共打印 100 次;
func main() {
 out := fanIn(pubNums(1, 2, 3, 4))
 for i := 0; i < 100; i++ {
  fmt.Println(<-out)
 }
}

func fanIn(channels ...<-chan int) <-chan int {
 out := make(chan int)
 for _, c := range channels {
  go func(c <-chan int) {
   for {
    out <- <-c
   }
  }(c)
 }
 return out
}

func pubNums(nums ...int) <-chan int {
 out := make(chan int)
 go func() {
  for i := 0; i < 100; i++ {
   for _, num := range nums {
    out <- num
    time.Sleep(time.Second)
   }
  }
  close(out)
 }()
 return out
}

package main

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

func main() {
 barrier := sync.NewCond(&sync.Mutex{})
 current := 1

 for i := 0; i < 100; i++ {
  go func(id int) {
   for {
    barrier.L.Lock()
    for current != id {
     barrier.Wait()
    }
    fmt.Println("协程编号:", id)
    current = (current % 4) + 1
    barrier.Broadcast()
    barrier.L.Unlock()
    time.Sleep(time.Second)
   }
  }(i % 4 + 1)
 }

 time.Sleep(time.Second * 5)
}

package main

import (
 "fmt"
 "time"
)

func main() {
 semaphore := make([]chan struct{}, 4)
 for i := range semaphore {
  semaphore[i] = make(chan struct{}, 0)
 }

 for i := 0; i < 100; i++ {
  go func(id int) {
   for {
    <-semaphore[id-1]
    fmt.Println("协程编号:", id)
    semaphore[(id)%4] <- struct{}{}
    time.Sleep(time.Second)
   }
  }(i % 4 + 1)
 }

 semaphore[0] <- struct{}{}
 time.Sleep(time.Second * 5)
}

多线程数字 + 字母

Tip

*这类问题比固定值要稍微复杂一些,但是与线程本身无关,这类题目往往有一些小技巧需要注意。

  • 使用两个协程交替打印序列,一个协程打印数字,另一个协程打印字母,最终效果如下1A2B...26Z
  • 用三个线程,顺序打印字母 A-Z,输出结果是 1A 2B 3C 1D 2E...打印完毕最后输出一个 OK
  • 实现两个协程,其中一个产生随机数并写入到 chan 中,另一个从 chan 中读取,并打印出来,最终输出 5 个随机数
  • 给一个数组,并发交替打印奇数和偶数,请分别用 chan、sync 和原子操作实现?

package main

import (
 "fmt"
 "sync"
)

func main() {
 numberCh := make(chan int)
 letterCh := make(chan rune)

 var wg sync.WaitGroup
 wg.Add(2)

 go func() {
  defer wg.Done()
  for i := 1; i <= 26; i++ {
   fmt.Print(<-numberCh)
   fmt.Print(string(<-letterCh))
  }
 }()

 go func() {
  defer wg.Done()
  for i := 'A'; i <= 'Z'; i++ {
   numberCh <- int(i - 'A' + 1)
   letterCh <- i
  }
 }()

 wg.Wait()
 close(numberCh)
 close(letterCh)

 fmt.Println()
}

package main

import (
 "fmt"
 "sync"
)


// 2A 3B 1C 2D 3E 1F 2G 3H 1I 2J 3K 1L 2M 3N 1O 2P 3Q 1R 2S 3T 1U 2V 3W 1X 2Y 3Z OK
// 1A 2B 3C 1D 2E 3F 1G 2H 3I 1J 2K 3L 1M 2N 3O 1P 2Q 3R 1S 2T 3U 1V 2W 3X 1Y 2Z OK
func main() {
 var wg sync.WaitGroup
 wg.Add(3)

 letterCh := make(chan rune)
 numberCh := make(chan int)

 go func() {
  defer wg.Done()
  for i := 0; i < 26; i++ {
   fmt.Printf("%d%c ", <-numberCh, <-letterCh)
  }
 }()

 go func() {
  defer wg.Done()
  for i := 1; i <= 26; i++ {
   vi := i % 3
   if vi == 0 {
    vi = 3
   }
   numberCh <- vi
  }
 }()

 go func() {
  defer wg.Done()
  for i := 'A'; i <= 'Z'; i++ {
   letterCh <- i
  }
 }()

 wg.Wait()
 close(numberCh)
 close(letterCh)

 fmt.Println("OK")
}

多线程纯数字打印

Tip

这里只列举一下第一题的几种解法,其他几个变种题目可以自己实现一下

实际上也是某种“非固定值打印”

这种问题嘛,就是多此一举,实际上就是循环一个 go func,该 func 里再起个循环打印就可以了。

  • 10 个线程依次打印 1-10,11-20 和到 100?
  • 三个线程交替打印至 100:线程 1 打印 1、4、7,线程 2 打印 2、5、8,线程 3 打印 3、6、9,一直打印到 100 结束
  • 如何让 10 个线程按照顺序打印 0123456789?
  • 怎么开 10 个线程,每个线程打印 1000 个数字,要按照顺序从 1 打印到 1w?
  • 用五个线程,顺序打印数字 1~无穷大,其中每 5 个数字为 1 组,如下:其中 id 代表线程的 id

这几个都可以分别用 chan、mutex、wg 和 atomic 实现一下,非常简单


Go Playground - The Go Programming Language