vault backup: 2026-06-07 12:14:39
This commit is contained in:
+311
-394
@@ -1,105 +1,112 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- 代码面试题
|
||||
tags: [go, golang, interview, code-questions]
|
||||
create time: 2026-06-07 14:30
|
||||
---
|
||||
|
||||
# 代码面试题
|
||||
# 编程算法面试题 💻
|
||||
|
||||
## 1.开启100个协程,顺序打印1-1000,且保证协程号1的,打印尾数为1的数字
|
||||
## 概述
|
||||
|
||||
本文件涵盖 Go 并发编程场景下的 9 道代码实现题,涉及协程同步、通道协作、并发控制等实战场景。这些题目在面试中通常要求"现场手写代码",考察对 Channel、Mutex、WaitGroup 的综合运用能力。
|
||||
|
||||
> [!tip] 💡 使用建议
|
||||
> 每道题先自己思考解题思路,再对照答案学习。重点理解"为什么用这种方式"而非死记代码。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hzh/GolangStar/Go面试题库/Channel面试题]] — Channel 原理
|
||||
- [[hzh/GolangStar/Go面试题库/Sync面试题]] — Mutex/WaitGroup 原理
|
||||
- [[hzh/GolangStar/Go面试题库/Context面试题]] — Context 超时控制
|
||||
|
||||
## 正文
|
||||
|
||||
### Q1:100 个协程顺序打印 1-1000 🟡中等
|
||||
|
||||
> [!question] ❓ 思考一下
|
||||
> 100 个协程同时运行,如何让它们按 1-1000 的顺序依次打印?你会用什么机制来协调它们的执行顺序?
|
||||
|
||||
## 参考答案
|
||||
|
||||
**核心思路**:用 map 为每个协程分配独立的 channel,主循环按序发送信号到对应协程的 channel,通过阻塞保证顺序。
|
||||
|
||||
```go
|
||||
// 同时开启100个协程(分别为1号协程 2号协程 ... 100号协程,
|
||||
// 1号协程只打印尾数为1的数字,2号协程只打印尾数为2的数,
|
||||
// 以此类推),请顺序打印1-1000整数以及对应的协程号;
|
||||
|
||||
func main() {
|
||||
s := make(chan struct{})
|
||||
//通过map的key来保证协程的顺序
|
||||
m := make(map[int]chan int, 100)
|
||||
//填充map,初始化channel
|
||||
s := make(chan struct{}) // 控制打印顺序的信号
|
||||
m := make(map[int]chan int, 100) // 每个协程的入口 channel
|
||||
|
||||
for i := 1; i <= 100; i++ {
|
||||
m[i] = make(chan int)
|
||||
}
|
||||
//开启100个协程,死循环打印
|
||||
//go func() { 这个协程不加也可以的
|
||||
for i := 1; i <= 100; i++ {
|
||||
go func(id int) {
|
||||
for {
|
||||
num := <-m[id]
|
||||
fmt.Println(num)
|
||||
s <- struct{}{}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
//}()
|
||||
//循环1-1000,并把值传递给匹配的map
|
||||
//然后通过s限制循序打印
|
||||
for i := 1; i <= 1000; i++ {
|
||||
id := i % 100
|
||||
if id == 0 {
|
||||
id = 100
|
||||
}
|
||||
m[id] <- i
|
||||
//通过s这个来控制打印顺序。每次遍历一次i
|
||||
//都通过s阻塞协程的打印,最后打印完毕
|
||||
<-s
|
||||
go func(id int) {
|
||||
for {
|
||||
num := <-m[id] // 等待属于自己的数字
|
||||
fmt.Println(num)
|
||||
s <- struct{}{} // 打印完成,通知主循环
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
for i := 1; i <= 1000; i++ {
|
||||
id := i % 100
|
||||
if id == 0 { id = 100 }
|
||||
m[id] <- i // 发送给对应协程
|
||||
<-s // 阻塞等待该协程打印完毕
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2.三个goroutinue交替打印abc 10次
|
||||
> [!note] 📝 解析思路
|
||||
> - **map + channel 组合**:每个协程独占一个 channel,主循环精确投递
|
||||
> - **信号量 s**:每次只发一个数,收到打印完成的信号才发下一个,保证严格顺序
|
||||
> - **面试官可能会追问**:"如果不用 map 怎么做?"→ 可以用 100 个独立声明的 channel 或用 `sync.Mutex` 保护共享输出
|
||||
|
||||
---
|
||||
|
||||
### Q2:三个 goroutine 交替打印 abc 10 次 🟡中等
|
||||
|
||||
## 参考答案
|
||||
|
||||
**核心思路**:三个 channel 串联成环形,a -> b -> c -> a。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 定义3个channel
|
||||
ch1 := make(chan struct{})
|
||||
ch2 := make(chan struct{})
|
||||
ch3 := make(chan struct{})
|
||||
ch1 := make(chan struct{}) // a → b
|
||||
ch2 := make(chan struct{}) // b → c
|
||||
ch3 := make(chan struct{}) // c → a
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
// 打印a
|
||||
|
||||
// 打印 a
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch1
|
||||
fmt.Println("a")
|
||||
ch2 <- struct{}{}
|
||||
}
|
||||
// 第10次的时候,打印c的goroutine写入了ch1
|
||||
// 为了防止阻塞,要消费以下ch1
|
||||
<-ch1
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch1
|
||||
fmt.Print("a")
|
||||
ch2 <- struct{}{}
|
||||
}
|
||||
<-ch1 // 消费最后一次信号
|
||||
}()
|
||||
// 打印b
|
||||
|
||||
// 打印 b
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch2
|
||||
fmt.Println("b")
|
||||
ch3 <- struct{}{}
|
||||
}
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch2
|
||||
fmt.Print("b")
|
||||
ch3 <- struct{}{}
|
||||
}
|
||||
}()
|
||||
// 打印c
|
||||
|
||||
// 打印 c
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch3
|
||||
fmt.Println("c")
|
||||
ch1 <- struct{}{}
|
||||
}
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10; i++ {
|
||||
<-ch3
|
||||
fmt.Print("c")
|
||||
ch1 <- struct{}{}
|
||||
}
|
||||
}()
|
||||
// 启动
|
||||
ch1 <- struct{}{}
|
||||
|
||||
ch1 <- struct{}{} // 启动信号
|
||||
wg.Wait()
|
||||
close(ch1)
|
||||
close(ch2)
|
||||
@@ -108,366 +115,276 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 3.用不超过10个goroutine不重复的打印slice中的100个元素
|
||||
> [!warning] ⚠️ 高频陷阱
|
||||
> 第 10 次打印 c 后会给 ch1 发信号,但 a 的循环已经结束了。需要额外消费一次 ch1 防止阻塞。
|
||||
|
||||
> [!tip] 💡 面试技巧
|
||||
> 回答时可以画图展示三个 channel 的环形关系,然后说明"每次只有收到信号的 goroutine 才能执行,执行完给下一个发信号"。
|
||||
|
||||
---
|
||||
|
||||
### Q3:不超过 10 个 goroutine 不重复打印 slice 中的 100 个元素 🟡中等
|
||||
|
||||
## 参考答案
|
||||
|
||||
**方案一**:有缓冲 channel 控制并发度(无序)
|
||||
|
||||
```go
|
||||
package main
|
||||
ch := make(chan struct{}, 10) // 容量 10 = 最大并发数
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
ch <- struct{}{} // 写满 10 个后阻塞
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
fmt.Printf("%d\n", ss[idx])
|
||||
<-ch // 打印完释放一个槽位
|
||||
}(i)
|
||||
}
|
||||
```
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
**方案二**:固定 10 个 goroutine 顺序打印
|
||||
|
||||
// 用不超过10个goroutine不重复的打印slice中的100个元素
|
||||
// 容量为10的有缓冲channel实现
|
||||
// 每次启动10个,累计启动100个goroutine,且无序打印
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
// 创建切片
|
||||
ss := make([]int, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
ss[i] = i
|
||||
}
|
||||
ch := make(chan struct{}, 10)
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
// 写10个就阻塞了,此时goroutine中打印
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
fmt.Printf("val: %d \n", ss[idx])
|
||||
// 打印结束,从缓冲channel中删除一个
|
||||
<-ch
|
||||
}(i)
|
||||
```go
|
||||
hashMap := make(map[int]chan int) // 10 个 channel 对应 10 个 goroutine
|
||||
sort := make(chan struct{}) // 控制顺序
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
// 关闭channel
|
||||
close(ch)
|
||||
fmt.Println("end")
|
||||
for i := 0; i < 10; i++ {
|
||||
hashMap[i] = make(chan int)
|
||||
go func(idx int) {
|
||||
for val := range hashMap[idx] {
|
||||
fmt.Printf("go %d: %d\n", idx, val)
|
||||
sort <- struct{}{} // 打印完通知
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// 用不超过10个goroutine不重复的打印slice中的100个元素
|
||||
// 创建10个无缓冲channel和10个goroutine
|
||||
// 固定10个goroutine,且顺序打印
|
||||
func test9() {
|
||||
var wg sync.WaitGroup
|
||||
// 创建切片
|
||||
ss := make([]int, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
ss[i] = i
|
||||
}
|
||||
// 创建channel和goroutine
|
||||
hashMap := make(map[int]chan int)
|
||||
sort := make(chan struct{})
|
||||
for _, v := range ss {
|
||||
id := v % 10
|
||||
hashMap[id] <- v
|
||||
<-sort // 等当前打印完再发下一个
|
||||
}
|
||||
```
|
||||
|
||||
> [!info] 🔗 延伸阅读
|
||||
> - 方案一适合不需要顺序的场景(吞吐量优先)
|
||||
> - 方案二适合需要顺序的场景(可控性优先)
|
||||
|
||||
---
|
||||
|
||||
### Q4:两个协程交替打印奇偶数 🟢简单
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
chan1 := make(chan struct{})
|
||||
|
||||
// 偶数协程
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
hashMap[i] = make(chan int)
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
for val := range hashMap[idx] {
|
||||
fmt.Printf("go id: %d, val: %d \n", idx, val)
|
||||
sort <- struct{}{}
|
||||
}
|
||||
}(i)
|
||||
chan1 <- struct{}{} // 发出信号
|
||||
if i%2 == 0 {
|
||||
fmt.Println("偶数:", i)
|
||||
}
|
||||
}
|
||||
// 循环切片,对10取模,找到对应channel的key,写入值
|
||||
for _, v := range ss {
|
||||
id := v % 10
|
||||
hashMap[id] <- v
|
||||
// 有序
|
||||
<-sort
|
||||
}()
|
||||
|
||||
// 奇数协程
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
<-chan1 // 等待信号
|
||||
if i%2 == 1 {
|
||||
fmt.Println("奇数:", i)
|
||||
}
|
||||
}
|
||||
// 循环结束关闭channel,删除map的key
|
||||
for k, _ := range hashMap {
|
||||
close(hashMap[k])
|
||||
delete(hashMap, k)
|
||||
}
|
||||
wg.Wait()
|
||||
close(sort)
|
||||
fmt.Println("end")
|
||||
}
|
||||
}()
|
||||
```
|
||||
|
||||
## 4.两个协程交替打印奇偶数
|
||||
---
|
||||
|
||||
### Q5:用单个 channel 实现 0,1 交替打印 🟢简单
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
package main
|
||||
msg := make(chan struct{})
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//golang交替打印奇偶数
|
||||
//交替打印,可以通过channel来实现
|
||||
chan1 := make(chan struct{})
|
||||
//偶数
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
chan1 <- struct{}{}
|
||||
if i%2 == 0 {
|
||||
fmt.Println("打印偶数:", i)
|
||||
}
|
||||
}
|
||||
}()
|
||||
//奇数
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
<-chan1
|
||||
if i%2 == 1 {
|
||||
fmt.Println("打印奇数数:", i)
|
||||
}
|
||||
}
|
||||
}()
|
||||
//阻塞
|
||||
select {
|
||||
case <-time.After(time.Second * 10):
|
||||
go func() {
|
||||
for {
|
||||
<-msg // 接收信号
|
||||
fmt.Println("0")
|
||||
msg <- struct{}{} // 回传信号
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-msg // 接收信号
|
||||
fmt.Println("1")
|
||||
msg <- struct{}{} // 回传信号
|
||||
}
|
||||
}()
|
||||
|
||||
msg <- struct{}{} // 初始信号
|
||||
```
|
||||
|
||||
## 5.用单个channel实现0,1的交替打印
|
||||
> [!tip] 💡 面试技巧
|
||||
> "用一个 channel 作为信号灯,0 和 1 各持一半——谁拿到灯谁就打印,打印完把灯放回 channel。"
|
||||
|
||||
---
|
||||
|
||||
### Q6:sync.Cond 实现多生产者多消费者 🟡中等
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
package main
|
||||
var cond sync.Cond
|
||||
cond.L = new(sync.Mutex)
|
||||
msgCh := make(chan int, 5)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
msg := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
<-msg
|
||||
fmt.Println("0")
|
||||
msg <- struct{}{}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
<-msg
|
||||
fmt.Println("1")
|
||||
msg <- struct{}{}
|
||||
}
|
||||
}()
|
||||
msg <- struct{}{}
|
||||
time.Sleep(3 * time.Minute)
|
||||
// 生产者:缓冲区满时等待
|
||||
producer := func(ctx context.Context, out chan<- int, idx int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
cond.Broadcast()
|
||||
return
|
||||
default:
|
||||
cond.L.Lock()
|
||||
for len(msgCh) == 5 {
|
||||
cond.Wait() // 缓冲区满,等待
|
||||
}
|
||||
out <- rand.Intn(500)
|
||||
cond.Signal() // 唤醒一个消费者
|
||||
cond.L.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 消费者:缓冲区空时等待
|
||||
consumer := func(ctx context.Context, in <-chan int, idx int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
for len(msgCh) > 0 { /* 继续消费 */ }
|
||||
return
|
||||
default:
|
||||
cond.L.Lock()
|
||||
for len(msgCh) == 0 {
|
||||
cond.Wait() // 缓冲区空,等待
|
||||
}
|
||||
num := <-in
|
||||
cond.Signal() // 唤醒一个生产者
|
||||
cond.L.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6.sync.Cond实现多生产者多消费者
|
||||
> [!note] 📝 解析思路
|
||||
> `sync.Cond` 的核心是:**Lock + Wait/Signal/Broadcast**。Wait 会释放锁并挂起 goroutine,Signal 唤醒一个等待者,Broadcast 唤醒全部。适合"条件满足才执行"的场景。
|
||||
|
||||
---
|
||||
|
||||
### Q7:1000 个并发控制 + 1 秒超时 🟡中等
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
package main
|
||||
tasks := make(chan int, 1000)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
var cond sync.Cond
|
||||
cond.L = new(sync.Mutex)
|
||||
msgCh := make(chan int, 5)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 生产者
|
||||
producer := func(ctx context.Context, out chan<- int, idx int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 每次生产者退出,都唤醒一个消费者处理,防止最后有消费者线程死锁
|
||||
// 生产者比消费者多,所以cond.Signal()就可以。不然的话建议Broadcast()
|
||||
cond.Broadcast()
|
||||
fmt.Println("producer finished")
|
||||
return
|
||||
default:
|
||||
cond.L.Lock()
|
||||
for len(msgCh) == 5 {
|
||||
cond.Wait()
|
||||
}
|
||||
num := rand.Intn(500)
|
||||
out <- num
|
||||
fmt.Printf("producer: %d, msg: %d\n", idx, num)
|
||||
cond.Signal()
|
||||
cond.L.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 消费者
|
||||
consumer := func(ctx context.Context, in <-chan int, idx int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 消费者可以选择继续消费直到channel为空
|
||||
for len(msgCh) > 0 {
|
||||
select {
|
||||
case num := <-in:
|
||||
fmt.Printf("consumer %d, msg: %d\n", idx, num)
|
||||
default:
|
||||
// 如果channel已经空了,跳出循环
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Println("consumer finished")
|
||||
return
|
||||
default:
|
||||
cond.L.Lock()
|
||||
for len(msgCh) == 0 {
|
||||
cond.Wait()
|
||||
}
|
||||
num := <-in
|
||||
fmt.Printf("consumer %d, msg: %d\n", idx, num)
|
||||
cond.Signal()
|
||||
cond.L.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动生产者和消费者
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go producer(ctx, msgCh, i+1)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
wg.Add(1)
|
||||
go consumer(ctx, msgCh, i+1)
|
||||
}
|
||||
|
||||
// 模拟程序运行一段时间
|
||||
wg.Wait()
|
||||
close(msgCh)
|
||||
fmt.Println("all finished")
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
tasks <- i
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return // 超时退出
|
||||
default:
|
||||
fmt.Printf("goroutine %d\n", id)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
fmt.Println("exec done")
|
||||
close(tasks)
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
## 7.使用go实现1000个并发控制并设置执行超时时间1秒
|
||||
> [!warning] ⚠️ 高频陷阱
|
||||
> 这段代码的缺陷是 1000 个 goroutine 几乎同时启动,1 秒内可能来不及全部执行完。实际场景中应限制并发数量(如用 semaphore channel)。
|
||||
|
||||
> [!tip] 💡 面试技巧
|
||||
> 如果面试官指出问题,可以回答:"可以用有缓冲 channel 做信号量控制并发数:`sem := make(chan struct{}, 10)`,获取/释放 sem 来控制最多 10 个并发。"
|
||||
|
||||
---
|
||||
|
||||
### Q8:两个 Goroutine 交替打印字母与数字 a1b2c3 🟡中等
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
package main
|
||||
numCh := make(chan struct{}) // 通知打印数字
|
||||
strCh := make(chan struct{}) // 通知打印字母
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建 1000 个协程,并且进行打印
|
||||
// 总共超时时间 1s,1s 没执行完就超时,使用 ctx 进行控制
|
||||
|
||||
// 定义任务 channel
|
||||
tasks := make(chan int, 1000)
|
||||
// 定义 ctx
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// 启动 1000 个协程
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
tasks <- i
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
fmt.Printf("goroutine id: %d\n", id)
|
||||
}
|
||||
}(i)
|
||||
// 打印字符 a-z
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 'a'; i <= 'z'; i++ {
|
||||
fmt.Print(string(i))
|
||||
numCh <- struct{}{} // 通知打印数字
|
||||
<-strCh // 等待对方完成
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
fmt.Println("exec done")
|
||||
close(tasks)
|
||||
wg.Wait()
|
||||
fmt.Println("finish")
|
||||
}
|
||||
// 打印数字 1-26
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 1; i <= 26; i++ {
|
||||
<-numCh // 等待对方完成
|
||||
fmt.Print(i)
|
||||
strCh <- struct{}{} // 通知打印字母
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
## 8.使用两个Goroutine,向标准输出中按顺序按顺序交替打出字母与数字,输出是a1b2c3
|
||||
---
|
||||
|
||||
### Q9:限制 10 个 goroutine 执行,执行完一个放一个新进来 🟢简单
|
||||
|
||||
## 参考答案
|
||||
|
||||
```go
|
||||
package main
|
||||
ch := make(chan struct{}, 10) // 容量 10 = 并发上限
|
||||
var wg sync.WaitGroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 定义两个channel,一个打印数字,一个打印字母
|
||||
numCh := make(chan struct{})
|
||||
strCh := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
// 打印字符
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 'a'; i <= 'z'; i++ {
|
||||
fmt.Println(string(i))
|
||||
// 通知打印数字
|
||||
numCh <- struct{}{}
|
||||
// 阻塞等待打印字母
|
||||
<-strCh
|
||||
}
|
||||
}()
|
||||
// 打印字母
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 1; i <= 26; i++ {
|
||||
<-numCh
|
||||
fmt.Println(i)
|
||||
// 通知打印字母
|
||||
strCh <- struct{}{}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
fmt.Println("finished")
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
ch <- struct{}{} // 写满 10 个后自动阻塞
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
fmt.Printf("id: %d\n", id)
|
||||
<-ch // 执行完释放一个槽位
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
## 9.编写一个程序限制10个goroutine执行,每执行完一个goroutine就放一个新的goroutine进来
|
||||
> [!tip] 💡 面试技巧
|
||||
> 这是最经典的**并发控制模式**——用有缓冲 channel 做信号量(Semaphore)。面试中如果只写出这个,说明掌握了基础;如果能补充"用 context 加超时"或"用 errgroup 处理错误",则是加分项。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 编写一个程序限制10个goroutine执行,每执行完一个goroutine就放一个新的goroutine进来
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
ch := make(chan struct{}, 10)
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
fmt.Println("id: %d", id)
|
||||
<-ch
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
}
|
||||
```
|
||||
## 关联笔记
|
||||
|
||||
- [[hzh/GolangStar/Go面试题库/Channel面试题]]
|
||||
- [[hzh/GolangStar/Go面试题库/Sync面试题]]
|
||||
- [[hzh/GolangStar/Go面试题库/Context面试题]]
|
||||
|
||||
Reference in New Issue
Block a user