go实现判断20000数据范围内哪些是素数(只能被1和它本身整除的数),采用多协程和管道实现
实现一个并发程序,用于寻找 20000 以内的所有素数。使用了 Goroutines 和 Channels 来分发和处理任务,并通过 WaitGroup(实现为 exitChan
)来同步 Goroutines 的退出。
一.GO代码
1 | package main |
二.管道只读和只写
在 Go 语言中,通道(Channel)是用于在不同的 Goroutines 之间传递数据的一种方式。通道可以是双向的,也可以是单向的。单向通道分为只读和只写通道。这种区分有助于提高程序的类型安全性和清晰性。
1.只读通道(Receive-Only Channel)
只读通道是一种只能从中接收数据的通道。在函数参数中使用只读通道可以确保该函数不会意外地向通道发送数据。
声明只读通道的方式是:<-chan DataType
。
2.只写通道(Send-Only Channel)
只写通道是一种只能向其发送数据的通道。在函数参数中使用只写通道可以确保该函数不会意外地从通道读取数据。
声明只写通道的方式是:chan<- DataType
。
3.示例
下面是一个简单的例子,展示了如何使用只读和只写通道:
1 | goCopy codepackage main |
在这个例子中:
sendOnly
函数有一个只写通道参数,它向该通道发送一系列整数,然后关闭通道。receiveOnly
函数有一个只读通道参数,它从该通道接收并打印数据,直到通道被关闭。- 在
main
函数中,我们创建了一个双向通道ch
,然后启动sendOnly
函数在一个新的 Goroutine 中运行,并在主 Goroutine 中调用receiveOnly
函数
三.select的应用介绍
在 Go 语言中,select
语句是一种处理多个通道(Channel)的方式。它可以监听多个通道上的发送和接收操作,并且当任何一个通道准备就绪时,select
就会执行该操作。如果多个通道同时就绪,select
将随机选择一个执行。select
语句是非阻塞的,它可以与 Go 的并发特性结合,实现高效的任务处理和通信。
1.基本语法
select
语句的基本语法如下:
1 | select { |
2.示例
启动多个协程,每个协程向各自的通道发送数据。
使用
select
语句来接收不同协程的数据,同时监控超时情况和程序结束信号。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
57package main
import (
"fmt"
"math/rand"
"time"
)
func sendData(ch chan<- int, id int) {
for {
// 模拟随机的发送间隔
time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
ch <- id
}
}
func main() {
rand.Seed(time.Now().UnixNano())
// 创建两个通道
ch1 := make(chan int)
ch2 := make(chan int)
// 创建一个超时通道
timeout := make(chan bool)
// 创建一个结束信号的通道
done := make(chan bool)
// 启动协程发送数据
go sendData(ch1, 1)
go sendData(ch2, 2)
// 启动一个协程来控制超时
go func() {
time.Sleep(5 * time.Second) // 设置超时时间为5秒
timeout <- true
}()
// 使用 select 处理不同的情况
for {
select {
case msg := <-ch1:
fmt.Printf("Received from ch1: %d\n", msg)
case msg := <-ch2:
fmt.Printf("Received from ch2: %d\n", msg)
case <-timeout:
fmt.Println("Operation timed out!")
done <- true
return
case <-done:
fmt.Println("Program ended!")
return
}
}
}- 有两个数据发送协程,每个协程向其通道
ch1
和ch2
发送一个唯一的标识符。 - 设置了一个超时协程,如果在5秒内没有完成操作,则向
timeout
通道发送一个信号。 - 在
main
函数的select
语句中,我们监听四种情况:从ch1
接收数据、从ch2
接收数据、超时和结束程序。 - 一旦超时发生,我们向
done
通道发送一个信号并结束程序。
- 有两个数据发送协程,每个协程向其通道
四.recover
在 Go 中,协程(Goroutines)是轻量级的线程,用于并发执行任务。当一个协程因为 panic 而异常中断时,它不会影响其他协程的运行,但是如果 panic 没有被捕获(recover),它会导致整个程序崩溃。因此,在协程中合理使用 recover
是处理 panic 的一种有效方法。每个协程都应该独立地处理它们自己的 panic。这意味着你应该在每个可能产生 panic 的协程中使用 recover
。recover
需要在 defer 函数中使用,因为只有在延迟函数中它才能捕获到协程的 panic。
1.示例
1 | package main |
main
函数启动了 3 个协程。- 每个协程都调用了
safeGoroutine
函数,在这个函数中,我们使用defer
和recover
来捕获并处理可能发生的 panic。 - 如果在协程中发生 panic,
recover
会捕获到它,并允许协程优雅地处理 panic,而不是使整个程序崩溃。
2.位置
将 defer func() { ... }()
放在函数中的最上面是一种最佳实践。
- 确保覆盖整个函数: 将
defer
放在函数开始处可以确保无论 panic 在函数的哪个部分发生,defer
代码块都将被执行。这意味着,无论是由于哪个操作引发的 panic,都会被defer
中的recover
捕获和处理。 - 防止遗漏 panic: 如果将
defer
放在函数中间或末尾,那么在defer
之前的代码如果发生了 panic,recover
将无法捕获到这个 panic,因为defer
语句本身还没有被执行。 - 逻辑清晰: 将
defer
放在函数开头,可以让读代码的人立即知道这个函数有处理 panic 的逻辑,这使得代码的逻辑更清晰、更易于理解。