章节

10.6 练习

本篇通过若干分级练习巩固 goroutine、WaitGroup、channel、select 与超时处理,并能把并发基础迁移到结果汇总、任务分发和超时控制场景中。

练习

学完本章你应该掌握

  • 能使用 go 启动 goroutine,理解主 goroutine 不会自动等待其他任务,并能判断为什么并发输出顺序不固定。
  • 能正确使用 sync.WaitGroupAddDoneWait 协调一组任务结束,并识别计数不匹配、循环变量传递错误等常见问题。
  • 能使用 channel 在 goroutine 之间传递数据、通知结束,并写对 closerange、发送与接收的配合关系。
  • 能使用 select 同时等待多个 channel 事件,区分阻塞等待、default 非阻塞检查和“哪个先准备好就先处理”的行为。
  • 能使用 time.After 为并发任务增加超时控制,并理解为什么超时场景里常配合带缓冲结果 channel。
  • 能把 01-09 单元的函数、结构体、切片、map、自定义类型、接口、判断和循环自然放进并发题目里,而不是只会写孤立语法。

简单

第 1 题:并发启动三个学习任务并等待结束

第 1 题 简单

编写一个 Go 程序,完成下面要求:

  • 准备一个切片:
1
tasks := []string{"变量复习", "切片复习", "协程入门"}
  • 编写函数 runTask(name string, wg *sync.WaitGroup)
  • 这个函数输出:开始:任务名
  • main 中为每个任务启动一个 goroutine
  • 使用 sync.WaitGroup 等待这 3 个任务都输出完成
  • 最后输出:全部任务已启动完成

说明:前 3 行任务输出顺序不固定,只要最后一行一定出现在全部任务输出之后即可。

查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"fmt"
	"sync"
)

func runTask(name string, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("开始:", name)
}

func main() {
	tasks := []string{"变量复习", "切片复习", "协程入门"}

	var wg sync.WaitGroup

	for _, task := range tasks {
		wg.Add(1)
		go runTask(task, &wg)
	}

	wg.Wait()
	fmt.Println("全部任务已启动完成")
}

说明:这道题的重点是体会 go 启动任务后,主 goroutine 仍会继续往下走,所以必须用 WaitGroup 明确等待。

第 2 题:用 channel 取回两个数的和

第 2 题 简单

编写一个 Go 程序,完成下面要求:

  • 编写函数 calcSum(a, b int, resultCh chan int)
  • 这个函数把 a + b 的结果发送到 resultCh
  • main 中创建一个 chan int
  • 使用 goroutine 调用 calcSum(12, 30, resultCh)
  • 在主 goroutine 中接收结果,并输出:
1
sum=42
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func calcSum(a, b int, resultCh chan int) {
	resultCh <- a + b
}

func main() {
	resultCh := make(chan int)

	go calcSum(12, 30, resultCh)

	result := <-resultCh
	fmt.Printf("sum=%d\n", result)
}

说明:这里主 goroutine 不是靠 time.Sleep 猜时间,而是直接在 <-resultCh 处等待结果到来。

第 3 题:修正不会结束的 channel 遍历

第 3 题 简单

下面程序的目标是输出:

1
2
3
1
2
3

但它现在会一直卡住。请改正代码,让它在输出完 1、2、3 后正常结束。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
	numbers := make(chan int)

	go func() {
		for i := 1; i <= 3; i++ {
			numbers <- i
		}
	}()

	for number := range numbers {
		fmt.Println(number)
	}
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
	numbers := make(chan int)

	go func() {
		for i := 1; i <= 3; i++ {
			numbers <- i
		}
		close(numbers)
	}()

	for number := range numbers {
		fmt.Println(number)
	}
}

原因说明:

  • for number := range numbers 会一直读取,直到 numbers 被关闭。
  • 原程序只发送了 3 个值,但没有 close(numbers),所以接收方不知道“数据已经发完了”,就会继续阻塞等待。
  • 这里由发送方关闭 channel,更符合这一章的常见写法。

一般

第 4 题:并发生成学生成绩摘要

第 4 题 一般

编写一个 Go 程序,完成下面要求:

  • 定义结构体 Student,字段包括: Name string Score int
  • 准备下面这组数据:
1
2
3
4
5
students := []Student{
	{Name: "小李", Score: 82},
	{Name: "小王", Score: 59},
	{Name: "小张", Score: 91},
}
  • 编写函数 buildSummary(student Student) string
  • 返回格式如下: 如果分数大于等于 60,返回 小李: 82 (及格) 否则返回 小王: 59 (不及格)
  • 为每个学生启动一个 goroutine 生成摘要,并把摘要发送到 summaryCh
  • 使用 WaitGroup 等待所有 goroutine 完成后关闭 summaryCh
  • main 中使用 for range 读取并输出所有摘要

说明:摘要输出顺序不固定。

查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
	"fmt"
	"sync"
)

type Student struct {
	Name  string
	Score int
}

func buildSummary(student Student) string {
	level := "不及格"
	if student.Score >= 60 {
		level = "及格"
	}
	return fmt.Sprintf("%s: %d (%s)", student.Name, student.Score, level)
}

func main() {
	students := []Student{
		{Name: "小李", Score: 82},
		{Name: "小王", Score: 59},
		{Name: "小张", Score: 91},
	}

	summaryCh := make(chan string)
	var wg sync.WaitGroup

	for _, student := range students {
		wg.Add(1)
		go func(s Student) {
			defer wg.Done()
			summaryCh <- buildSummary(s)
		}(student)
	}

	go func() {
		wg.Wait()
		close(summaryCh)
	}()

	for summary := range summaryCh {
		fmt.Println(summary)
	}
}

说明:这道题把结构体、函数、判断、切片、goroutine、WaitGroup 和 channel 串起来了,很接近“并发生产结果,主流程统一收集”的基本模型。

第 5 题:谁先返回就先显示哪条消息

第 5 题 一般

编写一个 Go 程序,完成下面要求:

  • 创建两个 chan stringarticleCh exerciseCh
  • 启动两个 goroutine: 一个在 120ms 后向 articleCh 发送 文章:channel 入门 一个在 60ms 后向 exerciseCh 发送 练习:WaitGroup 实战
  • 使用 select 接收这两条消息
  • 总共接收 2 次,并按“谁先准备好就先输出谁”的顺序打印

期望输出类似这样:

1
2
练习:WaitGroup 实战
文章:channel 入门
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"fmt"
	"time"
)

func main() {
	articleCh := make(chan string)
	exerciseCh := make(chan string)

	go func() {
		time.Sleep(120 * time.Millisecond)
		articleCh <- "文章:channel 入门"
	}()

	go func() {
		time.Sleep(60 * time.Millisecond)
		exerciseCh <- "练习:WaitGroup 实战"
	}()

	for i := 0; i < 2; i++ {
		select {
		case msg := <-articleCh:
			fmt.Println(msg)
		case msg := <-exerciseCh:
			fmt.Println(msg)
		}
	}
}

说明:这道题的重点是理解 select 不是按 case 书写顺序执行,而是谁先准备好就先进入谁。

第 6 题:用 select + default 写一个等待提示器

第 6 题 一般

编写一个 Go 程序,完成下面要求:

  • 创建 doneCh := make(chan string)
  • 启动一个 goroutine,在 120ms 后向 doneCh 发送 批改完成
  • 在主 goroutine 中使用 for {} + select
  • 如果 doneCh 里还没有消息,就输出 等待中...
  • 一旦收到 批改完成,就输出它并结束程序

要求:

  • 使用 default 分支
  • 为了避免 CPU 空转,请在 default 里加一点点等待时间
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"fmt"
	"time"
)

func main() {
	doneCh := make(chan string)

	go func() {
		time.Sleep(120 * time.Millisecond)
		doneCh <- "批改完成"
	}()

	for {
		select {
		case msg := <-doneCh:
			fmt.Println(msg)
			return
		default:
			fmt.Println("等待中...")
			time.Sleep(30 * time.Millisecond)
		}
	}
}

说明:

  • defaultselect 在“还没有消息”时也能先执行别的逻辑。
  • 如果 default 里什么都不做,循环会疯狂空转,所以通常要配合 time.Sleep 或其他阻塞操作。

第 7 题:实现一个带超时的订单确认函数

第 7 题 一般

编写一个 Go 程序,完成下面要求:

  • 编写函数 confirmOrder(orderID int, workTime time.Duration) string
  • 在这个函数里启动一个 goroutine,模拟后台确认订单
  • 如果后台任务在 100ms 内完成,就返回:
1
订单1001确认完成
  • 如果超过 100ms 还没完成,就返回:
1
订单1001确认超时

要求:

  • 使用结果 channel 接收后台 goroutine 的返回值
  • 使用 select + time.After 做超时控制
  • main 中至少测试两次: 一次成功 一次超时
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
	"fmt"
	"time"
)

func confirmOrder(orderID int, workTime time.Duration) string {
	resultCh := make(chan string, 1)

	go func() {
		time.Sleep(workTime)
		resultCh <- fmt.Sprintf("订单%d确认完成", orderID)
	}()

	select {
	case msg := <-resultCh:
		return msg
	case <-time.After(100 * time.Millisecond):
		return fmt.Sprintf("订单%d确认超时", orderID)
	}
}

func main() {
	fmt.Println(confirmOrder(1001, 50*time.Millisecond))
	fmt.Println(confirmOrder(1002, 200*time.Millisecond))
}

一种可能的输出结果:

1
2
订单1001确认完成
订单1002确认超时

说明:这里把结果 channel 写成 make(chan string, 1),是为了让超时后后台 goroutine 仍然有机会把结果放进去,不容易被卡在发送位置。

第 8 题:并发计算切片平方并按原顺序输出

第 8 题 一般

编写一个 Go 程序,完成下面要求:

  • 准备一个切片:
1
nums := []int{2, 4, 6, 8}
  • 定义结构体 SquareResult,字段包括: Index int Value int
  • 为切片中的每个数字启动一个 goroutine,计算平方
  • 每个 goroutine 把结果以 SquareResult 的形式发送到 resultCh
  • 主 goroutine 收集所有结果,并恢复成原来的顺序
  • 最后输出:
1
[4 16 36 64]

要求:

  • 使用 WaitGroup
  • 所有结果发送完成后关闭 resultCh
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
	"fmt"
	"sync"
)

type SquareResult struct {
	Index int
	Value int
}

func calcSquare(index, num int, resultCh chan SquareResult, wg *sync.WaitGroup) {
	defer wg.Done()
	resultCh <- SquareResult{
		Index: index,
		Value: num * num,
	}
}

func main() {
	nums := []int{2, 4, 6, 8}
	resultCh := make(chan SquareResult)
	results := make([]int, len(nums))

	var wg sync.WaitGroup

	for index, num := range nums {
		wg.Add(1)
		go calcSquare(index, num, resultCh, &wg)
	}

	go func() {
		wg.Wait()
		close(resultCh)
	}()

	for item := range resultCh {
		results[item.Index] = item.Value
	}

	fmt.Println(results)
}

说明:并发结果的返回顺序不一定和原切片顺序一样,所以这里额外把下标一起传回来,再由主 goroutine 按 Index 放回去。

进阶

下面 5 题会把 goroutine、WaitGroup、channel、select、超时处理和前面学过的结构体、自定义类型、接口、函数、切片、map 一起用起来,更接近真实的并发程序组织方式。

第 9 题:实现一个双工人任务处理队列

第 9 题 进阶

请完成一个稍微完整一点的小场景:

  • 定义结构体 Task,字段包括: ID int Name string
  • 准备下面这组任务:
1
2
3
4
5
6
7
tasks := []Task{
	{ID: 1, Name: "复习变量"},
	{ID: 2, Name: "复习切片"},
	{ID: 3, Name: "完成 channel 练习"},
	{ID: 4, Name: "整理接口笔记"},
	{ID: 5, Name: "复盘协程超时"},
}
  • 创建任务 channel jobCh 和结果 channel resultCh
  • 编写函数 worker(workerID int, jobCh chan Task, resultCh chan string, wg *sync.WaitGroup)
  • 启动 2 个 worker goroutine,共同处理同一条任务队列
  • 每个 worker 处理完一项任务后,向 resultCh 发送类似下面的结果:
1
worker1 handled task 2: 复习切片
  • 所有任务发完后关闭 jobCh
  • 所有 worker 结束后关闭 resultCh
  • 主 goroutine 使用 for range 输出所有处理结果

说明:结果输出顺序不固定。

查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

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

type Task struct {
	ID   int
	Name string
}

func worker(workerID int, jobCh chan Task, resultCh chan string, wg *sync.WaitGroup) {
	defer wg.Done()

	for task := range jobCh {
		time.Sleep(40 * time.Millisecond)
		resultCh <- fmt.Sprintf("worker%d handled task %d: %s", workerID, task.ID, task.Name)
	}
}

func main() {
	tasks := []Task{
		{ID: 1, Name: "复习变量"},
		{ID: 2, Name: "复习切片"},
		{ID: 3, Name: "完成 channel 练习"},
		{ID: 4, Name: "整理接口笔记"},
		{ID: 5, Name: "复盘协程超时"},
	}

	jobCh := make(chan Task)
	resultCh := make(chan string)

	var wg sync.WaitGroup
	for workerID := 1; workerID <= 2; workerID++ {
		wg.Add(1)
		go worker(workerID, jobCh, resultCh, &wg)
	}

	go func() {
		for _, task := range tasks {
			jobCh <- task
		}
		close(jobCh)
	}()

	go func() {
		wg.Wait()
		close(resultCh)
	}()

	for result := range resultCh {
		fmt.Println(result)
	}
}

说明:这道题体现了 channel 既可以传“结果”,也可以当“任务队列”使用。两个 worker 同时从同一个 jobCh 取任务,是很常见的并发模型。

第 10 题:并发统计任务状态数量

第 10 题 进阶

编写一个 Go 程序,完成下面要求:

  • 定义自定义类型 TaskStatus,底层类型是 int
  • 定义三个状态常量: StatusTodo StatusDoing StatusDone
  • TaskStatus 绑定方法 Text() string
  • 定义结构体 Task,字段包括: Name string Status TaskStatus
  • 准备下面这组任务:
1
2
3
4
5
6
7
tasks := []Task{
	{Name: "看课程", Status: StatusTodo},
	{Name: "写代码", Status: StatusDoing},
	{Name: "做练习", Status: StatusDone},
	{Name: "复盘", Status: StatusTodo},
	{Name: "提交作业", Status: StatusDone},
}
  • 为每个任务启动一个 goroutine
  • 每个 goroutine 把自己的 Status 发送到 statusCh
  • 主 goroutine 使用 map[TaskStatus]int 统计每种状态的数量
  • 最后按下面顺序输出:
1
2
3
待开始: 2
进行中: 1
已完成: 2
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main

import (
	"fmt"
	"sync"
)

type TaskStatus int

const (
	StatusTodo  TaskStatus = 1
	StatusDoing TaskStatus = 2
	StatusDone  TaskStatus = 3
)

func (s TaskStatus) Text() string {
	switch s {
	case StatusTodo:
		return "待开始"
	case StatusDoing:
		return "进行中"
	case StatusDone:
		return "已完成"
	default:
		return "未知状态"
	}
}

type Task struct {
	Name   string
	Status TaskStatus
}

func main() {
	tasks := []Task{
		{Name: "看课程", Status: StatusTodo},
		{Name: "写代码", Status: StatusDoing},
		{Name: "做练习", Status: StatusDone},
		{Name: "复盘", Status: StatusTodo},
		{Name: "提交作业", Status: StatusDone},
	}

	statusCh := make(chan TaskStatus)
	counts := make(map[TaskStatus]int)

	var wg sync.WaitGroup
	for _, task := range tasks {
		wg.Add(1)
		go func(t Task) {
			defer wg.Done()
			statusCh <- t.Status
		}(task)
	}

	go func() {
		wg.Wait()
		close(statusCh)
	}()

	for status := range statusCh {
		counts[status]++
	}

	order := []TaskStatus{StatusTodo, StatusDoing, StatusDone}
	for _, status := range order {
		fmt.Printf("%s: %d\n", status.Text(), counts[status])
	}
}

说明:这道题把自定义类型、方法、结构体、切片、map 和并发收集放到了一起。真正要练的不是“统计本身”,而是把多个 goroutine 产出的值安全汇总到主流程。

第 11 题:修正超时后仍可能卡住发送方的程序

第 11 题 进阶

下面程序会输出:

1
timeout

但它还存在一个问题:超时之后,后台 goroutine 仍可能卡在发送结果的位置。
请改正代码,让它在保持超时输出的同时,不再让发送方因为结果没人接收而被卡住。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"
	"time"
)

func main() {
	resultCh := make(chan string)

	go func() {
		time.Sleep(200 * time.Millisecond)
		resultCh <- "success"
	}()

	select {
	case msg := <-resultCh:
		fmt.Println(msg)
	case <-time.After(100 * time.Millisecond):
		fmt.Println("timeout")
	}
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"
	"time"
)

func main() {
	resultCh := make(chan string, 1)

	go func() {
		time.Sleep(200 * time.Millisecond)
		resultCh <- "success"
	}()

	select {
	case msg := <-resultCh:
		fmt.Println(msg)
	case <-time.After(100 * time.Millisecond):
		fmt.Println("timeout")
	}
}

原因说明:

  • 原来的 resultCh 是无缓冲 channel。
  • 超时分支先执行后,主 goroutine 不再接收 resultCh,后台 goroutine 到了 resultCh <- "success" 这一步就可能一直等着。
  • 改成 make(chan string, 1) 后,就算主 goroutine已经走到超时分支,后台 goroutine 仍可以把一个结果先放进 channel 里,不容易卡死在发送位置。

第 12 题:实现首个搜索结果优先的资源搜索器

第 12 题 进阶

请完成一个稍微完整一点的小场景:

  • 定义接口 Searcher,要求实现: Search(keyword string) string
  • 定义两个实现: ArticleSearcher VideoSearcher
  • 两个结构体都带一个字段: Delay time.Duration
  • Search 方法里先等待 Delay,再返回对应结果文字
  • 编写函数 firstResult(keyword string, list []Searcher) string
  • 这个函数要为切片中的每个搜索器启动一个 goroutine
  • 使用 select 同时等待“最快返回的搜索结果”和“150ms 超时”
  • 如果先拿到结果,就直接返回该结果
  • 如果超时先到,就返回 timeout

要求:

  • main 中至少测试两组数据: 一组能在超时前拿到结果 一组会超时
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
	"fmt"
	"time"
)

type Searcher interface {
	Search(keyword string) string
}

type ArticleSearcher struct {
	Delay time.Duration
}

func (a ArticleSearcher) Search(keyword string) string {
	time.Sleep(a.Delay)
	return "文章结果:" + keyword
}

type VideoSearcher struct {
	Delay time.Duration
}

func (v VideoSearcher) Search(keyword string) string {
	time.Sleep(v.Delay)
	return "视频结果:" + keyword
}

func firstResult(keyword string, list []Searcher) string {
	resultCh := make(chan string, len(list))

	for _, item := range list {
		go func(searcher Searcher) {
			resultCh <- searcher.Search(keyword)
		}(item)
	}

	select {
	case result := <-resultCh:
		return result
	case <-time.After(150 * time.Millisecond):
		return "timeout"
	}
}

func main() {
	fastList := []Searcher{
		ArticleSearcher{Delay: 80 * time.Millisecond},
		VideoSearcher{Delay: 200 * time.Millisecond},
	}

	slowList := []Searcher{
		ArticleSearcher{Delay: 180 * time.Millisecond},
		VideoSearcher{Delay: 220 * time.Millisecond},
	}

	fmt.Println(firstResult("channel", fastList))
	fmt.Println(firstResult("timeout", slowList))
}

一种可能的输出结果:

1
2
文章结果:channel
timeout

说明:

  • 这里的重点是“谁先返回就用谁”,而不是把所有结果都等齐。
  • resultCh 使用了 len(list) 大小的缓冲,这样即使主流程已经拿到第一个结果或进入超时分支,后面慢一点的 goroutine 也不容易被卡在发送上。

第 13 题:实现一个并发成绩分析中心

第 13 题 进阶

请完成一个综合练习:

  • 定义结构体 Student,字段包括: Name string Score int
  • 准备下面这组数据:
1
2
3
4
5
6
students := []Student{
	{Name: "小李", Score: 82},
	{Name: "小王", Score: 59},
	{Name: "小张", Score: 91},
	{Name: "小周", Score: 76},
}
  • 编写三个函数: calcTotal(students []Student, ch chan int) calcPassCount(students []Student, ch chan int) findTopStudent(students []Student, ch chan Student)
  • 分别启动 3 个 goroutine 去计算: 总分 及格人数 最高分学生
  • 主 goroutine 使用 3 个不同的 channel 接收结果
  • 再用 select 循环收集这 3 份结果
  • 最后输出:
1
2
3
total=308
passCount=3
top=小张 91
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func calcTotal(students []Student, ch chan int) {
	total := 0
	for _, student := range students {
		total += student.Score
	}
	ch <- total
}

func calcPassCount(students []Student, ch chan int) {
	count := 0
	for _, student := range students {
		if student.Score >= 60 {
			count++
		}
	}
	ch <- count
}

func findTopStudent(students []Student, ch chan Student) {
	top := students[0]
	for _, student := range students[1:] {
		if student.Score > top.Score {
			top = student
		}
	}
	ch <- top
}

func main() {
	students := []Student{
		{Name: "小李", Score: 82},
		{Name: "小王", Score: 59},
		{Name: "小张", Score: 91},
		{Name: "小周", Score: 76},
	}

	totalCh := make(chan int, 1)
	passCh := make(chan int, 1)
	topCh := make(chan Student, 1)

	go calcTotal(students, totalCh)
	go calcPassCount(students, passCh)
	go findTopStudent(students, topCh)

	total := 0
	passCount := 0
	var top Student

	for i := 0; i < 3; i++ {
		select {
		case total = <-totalCh:
		case passCount = <-passCh:
		case top = <-topCh:
		}
	}

	fmt.Printf("total=%d\n", total)
	fmt.Printf("passCount=%d\n", passCount)
	fmt.Printf("top=%s %d\n", top.Name, top.Score)
}

说明:这道题把结构体、切片、函数、goroutine、多个 channel 和 select 全串起来了。真正想练的是“把不同类型的并发结果分别算出来,再在主流程统一收集”。

本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字