要求
采用Golang语言中channel实现协程间的通讯,并通过控制channel控制起输入与输出格式。其中一个协程sender负责发送字符串"ABC"的字符,另一个协程reciever负责接收这些字符并打印。
要求接收方reciever协程能够按照顺序,按照行输出对接收的值打印出"ABC"(或"AB",跳过"C"),重复10次。
实现代码
以下下代码实现了sender goroutine和revicever goroutine(主协程g0)通过通讯控制,按照字符串顺序接收并打印出AB"(跳过"C"),实现重复10次。
另外,对于跳过的"C"字符的轮齿,打印" No.X"(X为循环输出行的对应次数,输出行内容对应ABC或者AB)。
计时并打印程序运行的总时间。
go
package main
import (
"fmt"
"time"
)
func main() {
content := "ABC"
sig := make(chan bool)
ch := make(chan rune, 3)
go func() {
for i := 0; i < 10; i++ {
for _, c := range content {
ch <- c
}
sig <- true
}
close(ch)
}()
i, j := 1, 1
now := time.Now()
/*
output: ABC
for v := range ch {
if i%3 == 0 {
<-sig
fmt.Printf("%s No.%d\n", string(v), j)
j++
} else {
fmt.Printf("%s", string(v))
}
i++
}
*/
// output: AB
for v := range ch {
if i%3 > 0 {
fmt.Printf("%s", string(v))
} else {
fmt.Printf(" No.%d\n", j)
<-sig
j++
}
i++
}
close(sig)
d := time.Since(now)
fmt.Printf("Running time: %v\n", d)
}
题解分析
以上代码涉及了 Go 语言中的协程(goroutine)、通道(channel)和时间测量。我们将逐段分析代码的执行逻辑,特别是协程操作控制部分。
代码结构
1. List item
-
初始化变量和通道: content 字符串包含 "ABC"。
-
sig 是一个布尔类型的通道,用于信号通知。
-
ch是一个有缓冲区的通道,可以存储 3 个 rune 类型的值。
2. 启动协程
- 在协程中,循环 10 次,每次将 content 中的字符发送到 ch 通道,并在每次循环结束时向 sig 发送一个 true 信号。
- 循环结束后,关闭 ch 通道。
3.主函数中的 for 循环
-
使用 for v := range ch 接收 ch 通道中的值。
-
根据条件打印字符或处理信号,通过sig通道控制输入协程。
协程操作控制逻辑
关键逻辑在于 for v := range ch 循环中的条件判断:
go
for v := range ch {
if i%3 > 0 {
fmt.Printf("%s", string(v))
} else {
fmt.Printf(" No.%d\n", j)
<-sig
j++
}
i++
}
变量 i 和 j:
-
i 用于计数,从 1 开始递增。
-
j 用于生成信号后的编号,从 1 开始递增。
条件判断:
-
如果 i%3 > 0,即 i 不是 3 的倍数时,直接打印字符 v。
-
如果 i 是 3 的倍数时,执行以下操作:
- 打印 " No.%d\n",其中 %d 被 j 的值替换。
- 从 sig 通道接收信号(阻塞操作,直到 sig 有信号发送)。
- j 自增。
通道关闭:
- 协程在发送完 10 轮字符后关闭 ch 通道,此时 for v := range ch 循环结束。
输出分析
根据代码逻辑,输出会如下进行:
-
第一轮:打印 "A", "B"(因为 i 为 1 和 2,都不是 3 的倍数)。
-
第二轮(i 为 3):打印 " No.1"(因为 i 是 3 的倍数),然后等待 sig 信号(此时协程发送完第一轮信号)。
-
后续字符继续按此逻辑打印,但注意 sig信号是在每一轮结束后发送的,所以每次 i 为 3 的倍数时,会先打印编号再等待信号。
但是,由于主循环在 i 为 3 的倍数时等待 sig信号,而 sig 信号在每一轮字符发送完后才发送,因此:
-
在打印完 "AB" 后,i 变为 3,此时会等待第一个 sig 信号(即第一轮结束后发送的信号)。
-
在接收到信号后,打印 " No.1"并继续。
然而,由于代码中的 for 循环逻辑和信号发送的频率,主循环会在每次 i 为 3 的倍数时等待信号,且 sig信号每轮只发送一次,因此输出将受限于信号发送和接收的同步。
最终结果
实际运行代码时,由于信号同步和通道缓冲区的限制,输出将会是按顺序的 "AB" 后接一个编号,但由于信号在每轮结束后才发送,且主循环在 i 为 3 的倍数时等待信号,实际输出将受限于信号发送的时机和通道缓冲区大小。理论上,如果缓冲区足够且信号同步,输出可能是:
shell
AB No.1
...
AB NO.10
但考虑到信号和缓冲区同步的细节,实际运行可能更复杂,且由于代码逻辑,No.2 及之后的编号可能无法按预期打印,因为主循环在接收到信号后会继续,而信号发送频率与字符发送频率不匹配。
注意
- 代码中的 close(sig) 是多余的,因为 sig 通道在协程结束时并不会被主循环关闭,且主循环结束后程序也会终止,无需显式关闭 sig。
- 代码中的时间测量部分用于记录程序运行时间,对逻辑分析无直接影响。