多线程轮流打印问题
Sep 16, 2024 - ⧖ 6 min实现两个协程,其中一个产生随机数并写入到 chan 中,另一个从 chan 中读取,并打印出来,最终输出 5 个随机数
经典的“生产者消费者问题”
固定值交替打印
Tip
固定值交替打印比较简单
可以看到,无论是打印数字,还是打印字母,实际上没有区别。无非是打印数字可以直接通过遍历 make(chan token)
核心是构造一个current chan和next 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 实现一下,非常简单