
在并发编程中,素数生成是一个经典的案例,常用于演示并发模型的优雅性。然而,一个常见的并发素数生成器(例如基于通道的流水线筛法)虽然结构精巧,但在处理大量数字时可能会面临效率问题。原始的“朴素”素数判断方法(即检查一个数m是否能被所有小于m的数整除)其时间复杂度接近O(n^2)。当我们将这种逻辑并发化时,虽然能利用多核优势,但算法本身的低效性依然存在。
为了提升效率,一种常见的优化思路是将素数判断的复杂度降低到O(n^1.5),即对于一个数字m,我们只需要检查它能否被小于或等于其平方根的数整除。这是因为如果一个合数m有一个大于其平方根的因子a,那么它必然还有一个小于或等于其平方根的因子b(m = a * b)。因此,我们只需检查到平方根即可。将这种优化应用到并发素数生成器中,需要精心设计并发结构。
我们选择采用基于试除法的并发素数生成方案,该方案能够直接应用平方根优化。其核心思想是:
Go语言的Goroutine和Channel机制非常适合实现这种生产者-消费者模型。
立即学习“go语言免费学习笔记(深入)”;
首先,我们实现一个高效的isPrime函数,它利用了平方根优化以及一些基本的素数性质(如跳过偶数和3的倍数)。
package main
import (
"fmt"
"math"
"sync"
"time"
)
// isPrime 检查一个数字是否为素数,采用试除法并优化至平方根。
// 复杂度近似 O(sqrt(n))。
func isPrime(n int) bool {
if n < 2 {
return false
}
if n == 2 || n == 3 {
return true
}
if n%2 == 0 || n%3 == 0 { // 排除偶数和3的倍数
return false
}
// 只需要检查到数字的平方根
limit := int(math.Sqrt(float64(n)))
// 检查形如 6k ± 1 的因子
for i := 5; i <= limit; i += 6 {
if n%i == 0 || n%(i+2) == 0 {
return false
}
}
return true
}接下来,我们构建整个并发流程。
// generateNumbers 将指定范围内的数字发送到通道。
func generateNumbers(start, end int, out chan<- int) {
defer close(out) // 所有数字发送完毕后关闭通道
for i := start; i <= end; i++ {
out <- i
}
}
// worker 从输入通道接收数字,判断是否为素数,并将素数发送到输出通道。
func worker(id int, in <-chan int, out chan<- int, wg *sync.WaitGroup) {
defer wg.Done() // 工作完成后通知 WaitGroup
for num := range in {
if isPrime(num) {
out <- num
}
}
}
func main() {
const limit = 1000000 // 查找素数的上限
const numWorkers = 8 // 并发工作者数量,通常设为CPU核心数或其倍数
// nums 通道:用于发送待检查的数字给工作者
nums := make(chan int, 1000)
// primes 通道:用于从工作者接收发现的素数
primes := make(chan int, 1000)
var wg sync.WaitGroup // 用于等待所有工作者完成
start := time.Now() // 记录开始时间
// 1. 启动一个 Goroutine 生成待检查的数字
// 从2开始,因为2是最小的素数
go generateNumbers(2, limit, nums)
// 2. 启动多个 worker Goroutine
wg.Add(numWorkers) // 增加 WaitGroup 计数器
for i := 0; i < numWorkers; i++ {
go worker(i, nums, primes, &wg)
}
// 3. 启动一个 Goroutine,等待所有 worker 完成后关闭 primes 通道
// 这样主 Goroutine 在 range primes 时知道何时结束
go func() {
wg.Wait() // 等待所有 worker Goroutine 完成
close(primes) // 关闭 primes 通道
}()
// 4. 主 Goroutine 收集并打印素数
foundPrimes := []int{}
for p := range primes {
foundPrimes = append(foundPrimes, p)
}
elapsed := time.Since(start) // 计算总耗时
fmt.Printf("在 %d 内找到了 %d 个素数,耗时 %s\n", limit, len(foundPrimes), elapsed)
// fmt.Println("素数列表 (部分显示):", foundPrimes[:min(len(foundPrimes), 20)]) // 可选:打印部分素数
}
// 辅助函数,用于打印部分素数时避免索引越界
func min(a, b int) int {
if a < b {
return a
}
return b
}通过将素数判断的核心算法从O(n)优化到O(sqrt(n)),并结合Go语言强大的并发原语(Goroutine和Channel),我们成功实现了一个高效且结构清晰的并发素数生成器。这种设计模式不仅适用于素数生成,也可以推广到其他需要并行处理独立计算任务的场景。在选择素数生成算法时,应根据具体需求(如生成范围、是否需要所有素数、并发度要求等)权衡试除法与筛法的优劣。对于需要并发生成大量素数且每个数字判断相对独立的场景,本文的并发试除法是一个非常有效的解决方案。
以上就是Go语言中高效并发素数生成器的实现与优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号