实现两个协程,其中一个产生随机数并写入到 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?
无非是打印点什么数字、字符串之类的东西。我们这里就以打印数字为例。
- 我自己实现的,最简单版本
- 小优化,封装了一下
- 优化2
- pipeline+wg
- fan-in
- Barrier
- semaphore
// 假设有 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 和原子操作实现?
- 1A2B...26Z
- 1A2B3C...
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 实现一下,非常简单