1. 使用三个协程,每秒钟打印 cat dog fish(要求:顺序不能变化,协程1打印cat,协程2打印dog,协程3打印fish)#
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(3)
chcat := make(chan struct{})
chdog := make(chan struct{})
chfish := make(chan struct{})
go printCat(&wg, chcat, chdog)
go printDog(&wg, chdog, chfish)
go printFish(&wg, chfish, chcat)
// 初始启动
chcat <- struct{}{} // 启动 cat 打印
wg.Wait()
}
func printCat(wg *sync.WaitGroup, cat chan struct{}, dog chan struct{}) {
defer wg.Done()
for {
<-cat
fmt.Println("cat")
time.Sleep(time.Second)
dog <- struct{}{} // 通知 dog 打印
}
}
func printDog(wg *sync.WaitGroup, dog chan struct{}, fish chan struct{}) {
defer wg.Done()
for {
<-dog
fmt.Println("dog")
time.Sleep(time.Second)
fish <- struct{}{} // 通知 fish 打印
}
}
func printFish(wg *sync.WaitGroup, fish chan struct{}, cat chan struct{}) {
defer wg.Done()
for {
<-fish
fmt.Println("fish")
time.Sleep(time.Second)
cat <- struct{}{} // 通知 cat 打印
}
}
2. 实现两个协程轮流输出 A1B2C3…Z26#
func ChannelFunc() {
// 思想:两个g,一个输出数字,一个输出字母,重点是如何控制两个g的打印顺序,让其可以轮流打印
// 分别使用两个缓存为1的chan,来控制两个g的打印顺序
strChan := make(chan int, 1)
numChan := make(chan int, 1)
strChan <- 0 // 先往字符chan中塞入,此时strChan再塞入会堵塞
// 负责打印字母
go func() {
for i := 65; i <= 90; i++ {
<-strChan // strChan取出,因为之前先塞入了,所以此处不会堵塞,会直接打印字母
fmt.Printf("%v ", string(rune(i))) // 打印字母
numChan <- i // numChan 塞入,塞入后,另一个g的numChan取出操作才能进行
}
return
}()
// 负责打印数字
go func() {
for i := 1; i <= 26; i++ {
<-numChan // 一直阻塞,直到字母被打印,这样每次数字都是在字母后面被打印的
fmt.Printf("%v ", i) /// 打印数字
strChan <- i // strChan塞入,此处塞入后,上面协程的strChan取出操作才能进
}
return
}()
time.Sleep(1 * time.Second)
fmt.Println()
// 输出: A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10 K 11 L 12 M 13 N 14 O 15 P 16 Q 17 R ...
}
3. N 个 Goroutine 顺序打印数字(需要多多练习,代码不难,面对且战胜它)#
package main
import (
"fmt"
"time"
)
// 多少个Goroutine (1 - n)
var n = 5
func main() {
// 核心思路,先创建n个channel,然后创建n个goroutine,最后在main goroutine里面发号施令
// 先创建n个channel
slice := make([]chan int, n)
for i := 0; i < n; i++ {
slice[i] = make(chan int)
}
// 创建goroutine
for i := 0; i < n; i++ {
// 开启goroutine
go Print(slice[i], i)
}
number := 0
for i := 0; i < 100; i++ {
for j := 0; j < n; j++ {
// 发号
slice[j] <- number
number++
// 休息一小会
time.Sleep(time.Second)
}
}
}
func Print(ch chan int, num int) {
for {
fmt.Printf("goroutine %d 进行打印 %d \n", num, <-ch)
}
}
4. 下面函数执行结果是啥(需要知道切片底层原理)#
package main
import "fmt"
func main() {
doAppend := func(s []int) {
s = append(s, 1)
printLengthAndCapacity(s)
}
s := make([]int, 8, 8)
doAppend(s[:4]) // len=5 cap=8
printLengthAndCapacity(s) // len=8 cap=8
doAppend(s) // len=9 cap=16 (触发扩容)
printLengthAndCapacity(s) // len=8 cap=8 (原 slice 不受影响)
}
func printLengthAndCapacity(s []int) {
fmt.Println(s)
fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
执行结果说明:
s[:4]创建了一个新的 slice header,但共享同一个底层数组,len=4, cap=8- 对
s[:4]执行 append 后,由于 cap=8 足够,直接修改了底层数组的第 5 个位置 - 但原始
s的 len 仍然是 8,看不到 append 的结果 - 对完整
s执行 append 时触发扩容,创建了新数组,原s不受影响
5. 下面代码的输出是啥(重在学习 GMP 模型创建一个 goroutine 背后的原理)#
package main
import (
"runtime"
"sync"
)
func main() {
// 1. 设置只有一个 P 可以工作
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
var n = 10
// 2. 启动n个 goroutine打印,哪个goroutine最先打印
wg.Add(n)
// 创建goroutine go func -> runtime.newproc
for i := 1; i <= n; i++ {
go func(i int) {
// waitGroup 计数器减一,可以忽略
defer wg.Done()
println("I am goroutine ", i)
}(i)
}
wg.Wait()
}
输出结果:由于 GOMAXPROCS(1) 只有一个 P,goroutine 的执行顺序取决于调度器的实现。通常情况下,后创建的 goroutine 可能先被执行(LIFO 调度策略),所以输出可能是逆序的,如 I am goroutine 10, 9, 8...,但这不是绝对的。
6. 有一个数组,用两个协程,一个打印所有偶数的和,一个打印所有奇数的和,要用 for channel 机制#
package main
import (
"fmt"
"time"
)
func main() {
// 测试数组
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 创建两个无缓冲channel
evenChan := make(chan int)
oddChan := make(chan int)
// 偶数求和协程
go func() {
sum := 0
// 使用 for range 遍历channel,直到channel被关闭
for num := range evenChan {
sum += num
}
fmt.Printf("偶数和:%d\n", sum)
}()
// 奇数求和协程
go func() {
sum := 0
// 使用 for range 遍历channel,直到channel被关闭
for num := range oddChan {
sum += num
}
fmt.Printf("奇数和:%d\n", sum)
}()
// 主协程负责分发数据
fmt.Printf("原数组:%v\n", arr)
for _, num := range arr {
if num%2 == 0 {
evenChan <- num // 发送偶数到偶数channel
} else {
oddChan <- num // 发送奇数到奇数channel
}
}
// 关闭channel,通知协程没有更多数据
close(evenChan)
close(oddChan)
// 等待协程完成 (简单的sleep,实际项目中建议使用WaitGroup)
time.Sleep(100 * time.Millisecond)
fmt.Println("计算完成")
}
7. 10 个生产者 5 个消费者,生产者总共生产 1000 个消费物料(编号 1-1000),5 个消费者并行消费#
package main
import (
"fmt"
"sync"
)
func main() {
const (
numProducers = 10
numConsumers = 5
totalJobs = 1000
)
jobs := make(chan int, totalJobs) // 生产任务通道(缓冲大小为总任务数)
materials := make(chan int, numProducers) // 物料传递通道(带缓冲)
// 1. 主协程预先生成 1-1000 的任务编号
go func() {
for i := 1; i <= totalJobs; i++ {
jobs <- i
}
close(jobs) // 任务生成完毕后关闭通道
}()
// 2. 启动 10 个生产者协程
var wgProducers sync.WaitGroup
wgProducers.Add(numProducers)
for i := 0; i < numProducers; i++ {
go func() {
defer wgProducers.Done()
for job := range jobs { // 从 jobs 通道读取任务
materials <- job // 将物料发送到 materials 通道
}
}()
}
// 3. 等待所有生产者结束,关闭 materials 通道
go func() {
wgProducers.Wait()
close(materials)
}()
// 4. 启动 5 个消费者协程
var wgConsumers sync.WaitGroup
wgConsumers.Add(numConsumers)
for i := 0; i < numConsumers; i++ {
go func() {
defer wgConsumers.Done()
for material := range materials { // 从 materials 通道读取物料
fmt.Printf("消费者处理物料:%d\n", material)
}
}()
}
// 5. 等待所有消费者结束
wgConsumers.Wait()
fmt.Println("所有物料处理完毕")
}
8. 通过协程(goroutine)和通道(channel)实现多个协程执行随机数加法,最后输出其中最大值#
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 设置随机数种子,确保每次运行结果不同
rand.Seed(time.Now().UnixNano())
const numGoroutines = 10 // 定义协程数量
resultChan := make(chan int)
// 启动多个协程生成随机数并计算加法
for i := 0; i < numGoroutines; i++ {
go func() {
a := rand.Intn(100) // 生成0~99的随机数
b := rand.Intn(100)
sum := a + b
resultChan <- sum // 将结果发送到通道
}()
}
// 主协程收集结果并计算最大值
max := 0
for i := 0; i < numGoroutines; i++ {
if current := <-resultChan; current > max {
max = current
}
}
fmt.Printf("最大值为:%d\n", max)
}
9. 使用两个 goroutine 交替打印 1-100 之间的奇数和偶数,输出时按照从小到大输出#
分析
创建两个无缓冲通道,用于控制交替执行。奇数协程信号通道和偶数协程信号通道互相通知对方继续执行。
回答
package main
import (
"fmt"
"sync"
)
func main() {
// 创建两个无缓冲通道,用于控制交替执行
oddChan := make(chan struct{}) // 奇数协程信号通道
evenChan := make(chan struct{}) // 偶数协程信号通道
var wg sync.WaitGroup
wg.Add(2)
// 奇数协程
go func() {
defer wg.Done()
for i := 1; i <= 99; i += 2 {
<-oddChan // 等待触发信号
fmt.Println("奇数:", i) // 打印奇数
evenChan <- struct{}{} // 通知偶数协程
}
}()
// 偶数协程
go func() {
defer wg.Done()
for i := 2; i <= 100; i += 2 {
<-evenChan // 等待触发信号
fmt.Println("偶数:", i) // 打印偶数
if i < 100 {
oddChan <- struct{}{} // 通知奇数协程(最后一次无需通知)
}
}
}()
oddChan <- struct{}{} // 初始触发奇数协程
wg.Wait() // 等待两个协程完成
}
10. 创建 10 个 goroutine,id 分别是 0,1,2,3…9,每个 goroutine 只能打印最后一位是自己 id 号的数字,例如:3 号只能打印 3,13,23,33…编写一个程序,依次打印 1-10000#
package main
import (
"fmt"
"sync"
)
func main() {
const (
maxNumber = 10000 // 最大数字
workers = 10 // 协程数量
)
var (
mu sync.Mutex // 互斥锁
cond = sync.NewCond(&mu) // 条件变量
current int // 当前处理的数字
wg sync.WaitGroup
)
wg.Add(workers)
// 创建10个工作协程
for id := 0; id < workers; id++ {
go func(workerID int) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
for current < maxNumber {
// 等待条件满足或任务完成
for current < maxNumber && (current+1)%10 != workerID {
cond.Wait()
}
// 检查终止条件
if current >= maxNumber {
return
}
// 处理当前数字
current++
fmt.Printf("%4d (worker %d)\n", current, workerID)
// 唤醒所有协程重新检查条件
cond.Broadcast()
}
}(id)
}
wg.Wait()
fmt.Println("所有数字打印完成")
}