go实现判断20000数据范围内哪些是素数(只能被1和它本身整除的数),采用多协程和管道实现

news2024/11/17 17:36:45

实现一个并发程序,用于寻找 20000 以内的所有素数。使用了 Goroutines 和 Channels 来分发和处理任务,并通过 WaitGroup(实现为 exitChan)来同步 Goroutines 的退出。

一.GO代码

package main

import (
	"fmt"
	"time"
)

// 判断20000数据范围内哪些是素数(只能被1和它本身整除的数) 开启4个协程完成 采用管道同步通信 sync.WaitGroup
// WaitGroup 通常用于当只需要知道一组 Goroutines 何时结束,而不需要它们之间通信的场景
func main() {
	// 创建用于保存待检查数字的通道
	intChan := make(chan int, 1000)
	// 创建用于保存素数结果的通道
	primeChan := make(chan int, 2000)
	// 创建用于协调 Goroutines 退出的通道
	exitChan := make(chan bool, 4) // 协程数量并不是越多越快 根据CPU核数改变充分利用CPU性能
	// 开始时间 时间戳
	//startTime := time.Now().Unix()
	startTime := time.Now()
	// 开启一个 Goroutine 向 intChan 写入数据
	go putNum(intChan)

	// 开启 8 个 Goroutines 从 intChan 读取数据并判断是否为素数
	for i := 0; i < cap(exitChan); i++ {
		go primeNum(intChan, primeChan, exitChan)
	}

	// 开启一个匿名 Goroutine 等待所有 primeNum Goroutines 完成
	go func() {
		for i := 0; i < cap(exitChan); i++ {
			<-exitChan // 等待每个 primeNum Goroutine 的退出信号
		}
		// 结束时间
		useTime := time.Now().Sub(startTime)
		fmt.Println("-----------------所用时间:------------------------", useTime) // 所用时间: 3.1556ms
		close(primeChan)                                                       // 所有 primeNum Goroutines 完成后关闭 primeChan
	}()

	for i := 0; i < 10; i++ {
		go say(i)
		//time.Sleep(time.Second)
	}
	// 从 primeChan 中读取并打印素数结果
	for {
		//prime, ok := <-primeChan
		_, ok := <-primeChan
		if !ok {
			break // 如果 primeChan 被关闭,则退出循环
		}
		//fmt.Println("素数:", prime)
	}
	fmt.Println("主线程退出!!!!!!!!!!")
}

// putNum 函数:向 intChan 中写入数字
func putNum(intChan chan int) {
	for i := 1; i <= 20000; i++ {
		intChan <- i // 将数字 1 到 20000 写入 intChan
	}
	close(intChan) // 写入完成后关闭 intChan
	fmt.Println("向intChan写入2000条数据完成")
}

// primeNum 函数:从 intChan 中读取数字并判断是否为素数
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
	for {
		num, ok := <-intChan // 从 intChan 中读取数据
		if !ok {
			fmt.Println("其中一个协程数据处理完毕~~~")
			break // 如果 intChan 被关闭,则退出循环
		}
		// 判断读取的数字是否为素数
		if isPrime(num) {
			primeChan <- num // 如果是素数,将其发送到 primeChan
		}
	}
	exitChan <- true // 发送退出信号到 exitChan
}

// isPrime 函数:判断一个数是否为素数
/*
假设 n 不是素数,那么它可以表示为两个因子的乘积,即 n = a * b。
如果 a 和 b 都大于 sqrt(n)(n 的平方根),那么 a * b 将大于 n,这与 n = a * b 矛盾。
因此,如果 n 有超过1和它本身以外的因子,它必定至少有一个因子是小于或等于 sqrt(n) 的。
*/
func isPrime(n int) bool {
	if n == 1 {
		return false // 1 不是素数
	}
	for i := 2; i*i <= n; i++ { //如果一个数不是素数,则它必定有一个因子小于或等于它的平方根。
		if n%i == 0 {
			return false // 如果 n 能被除了 1 和它本身以外的数整除,则不是素数
		}
	}
	return true // 如果不能被任何数整除,则是素数
}

func say(i int) {
	defer func() {
		if e := recover(); e != nil {
			fmt.Println("eeeeee:", e)
		}
	}()
	if i == 5 {
		panic("say-------------------i==5 err")
	}

	fmt.Printf("协程 %d 执行完毕\n", i)
}

二.管道只读和只写

在 Go 语言中,通道(Channel)是用于在不同的 Goroutines 之间传递数据的一种方式。通道可以是双向的,也可以是单向的。单向通道分为只读和只写通道。这种区分有助于提高程序的类型安全性和清晰性。

1.只读通道(Receive-Only Channel)

只读通道是一种只能从中接收数据的通道。在函数参数中使用只读通道可以确保该函数不会意外地向通道发送数据。

声明只读通道的方式是:<-chan DataType

2.只写通道(Send-Only Channel)

只写通道是一种只能向其发送数据的通道。在函数参数中使用只写通道可以确保该函数不会意外地从通道读取数据。

声明只写通道的方式是:chan<- DataType

3.示例

下面是一个简单的例子,展示了如何使用只读和只写通道:

goCopy codepackage main

import (
	"fmt"
	"time"
)

// 该函数接收一个只写通道参数,只能向该通道发送数据
func sendOnly(ch chan<- int) {
	for i := 0; i < 5; i++ {
		ch <- i
	}
	close(ch) // 发送完数据后关闭通道
}

// 该函数接收一个只读通道参数,只能从该通道读取数据
func receiveOnly(ch <-chan int) {
	for v := range ch {
		fmt.Println("Received:", v)
	}
}

func main() {
	ch := make(chan int)
	go sendOnly(ch)       // 启动一个 Goroutine 向通道发送数据
	receiveOnly(ch)       // 在主 Goroutine 中从通道接收数据
	fmt.Println("Done")
}

在这个例子中:

  • sendOnly 函数有一个只写通道参数,它向该通道发送一系列整数,然后关闭通道。
  • receiveOnly 函数有一个只读通道参数,它从该通道接收并打印数据,直到通道被关闭。
  • main 函数中,我们创建了一个双向通道 ch,然后启动 sendOnly 函数在一个新的 Goroutine 中运行,并在主 Goroutine 中调用 receiveOnly 函数。
    在这里插入图片描述

三.select的应用介绍

在 Go 语言中,select 语句是一种处理多个通道(Channel)的方式。它可以监听多个通道上的发送和接收操作,并且当任何一个通道准备就绪时,select 就会执行该操作。如果多个通道同时就绪,select 将随机选择一个执行。select 语句是非阻塞的,它可以与 Go 的并发特性结合,实现高效的任务处理和通信。

1.基本语法

select 语句的基本语法如下:

select {
case <-chan1:
    // 执行通道 chan1 上的接收操作
case chan2 <- value:
    // 向通道 chan2 发送值 value
default:
    // 如果以上都没有准备就绪,则执行默认操作
}

2.示例

  • 启动多个协程,每个协程向各自的通道发送数据。

  • 使用 select 语句来接收不同协程的数据,同时监控超时情况和程序结束信号。

    package 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
            }
        }
    }
    
    
    • 有两个数据发送协程,每个协程向其通道 ch1ch2 发送一个唯一的标识符。
    • 设置了一个超时协程,如果在5秒内没有完成操作,则向 timeout 通道发送一个信号。
    • main 函数的 select 语句中,我们监听四种情况:从 ch1 接收数据、从 ch2 接收数据、超时和结束程序。
    • 一旦超时发生,我们向 done 通道发送一个信号并结束程序。

四.recover

在 Go 中,协程(Goroutines)是轻量级的线程,用于并发执行任务。当一个协程因为 panic 而异常中断时,它不会影响其他协程的运行,但是如果 panic 没有被捕获(recover),它会导致整个程序崩溃。因此,在协程中合理使用 recover 是处理 panic 的一种有效方法。每个协程都应该独立地处理它们自己的 panic。这意味着你应该在每个可能产生 panic 的协程中使用 recoverrecover 需要在 defer 函数中使用,因为只有在延迟函数中它才能捕获到协程的 panic。

1.示例

package main

import (
    "fmt"
    "time"
)

func main() {
    // 启动多个协程
    for i := 0; i < 3; i++ {
        go safeGoroutine(i)
    }

    // 等待足够长的时间以确保协程执行
    time.Sleep(1 * time.Second)
    fmt.Println("主程序结束")
}

func safeGoroutine(id int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("协程 %d 捕获到 panic: %v\n", id, r)
        }
    }()

    // 这里是协程可能会触发 panic 的地方
    if id == 1 { // 假设只有 id 为 1 的协程会触发 panic
        panic(fmt.Sprintf("协程 %d 发生 panic", id))
    }

    fmt.Printf("协程 %d 执行完毕\n", id)
}

  • main 函数启动了 3 个协程。
  • 每个协程都调用了 safeGoroutine 函数,在这个函数中,我们使用 deferrecover 来捕获并处理可能发生的 panic。
  • 如果在协程中发生 panic,recover 会捕获到它,并允许协程优雅地处理 panic,而不是使整个程序崩溃。

2.位置

defer func() { ... }() 放在函数中的最上面是一种最佳实践。

  • 确保覆盖整个函数:defer 放在函数开始处可以确保无论 panic 在函数的哪个部分发生,defer 代码块都将被执行。这意味着,无论是由于哪个操作引发的 panic,都会被 defer 中的 recover 捕获和处理。
  • 防止遗漏 panic: 如果将 defer 放在函数中间或末尾,那么在 defer 之前的代码如果发生了 panic,recover 将无法捕获到这个 panic,因为 defer 语句本身还没有被执行。

2.位置

defer func() { ... }() 放在函数中的最上面是一种最佳实践。

  • 确保覆盖整个函数:defer 放在函数开始处可以确保无论 panic 在函数的哪个部分发生,defer 代码块都将被执行。这意味着,无论是由于哪个操作引发的 panic,都会被 defer 中的 recover 捕获和处理。
  • 防止遗漏 panic: 如果将 defer 放在函数中间或末尾,那么在 defer 之前的代码如果发生了 panic,recover 将无法捕获到这个 panic,因为 defer 语句本身还没有被执行。
  • 逻辑清晰:defer 放在函数开头,可以让读代码的人立即知道这个函数有处理 panic 的逻辑,这使得代码的逻辑更清晰、更易于理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1396015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker安装confluence全套流程

docker安装confluence全套流程 1.安装mysql2.创建mysql容器3.拉取confluence7容器4.拷贝mysql驱动到confluence中:1195ef11d768为容器id(需要修改为你自己的)5.创建confluence容器6.激活confluence到数据连接可能需报数据库编码和环境隔离错误 1.安装mysql docker pull mysql/…

保姆版Vps安装灯塔(ARL)

因为灯塔的默认端口为5003&#xff0c;所以我们在安装之前就在防火墙里把我们的5003端口打开 打开端口步骤如下&#xff1a; 1.我们打开控制面板&#xff0c;在控制面板里点击 系统和安全 。如下图&#xff1a; 2.接着点击 Windows Defender防火墙,如下图&#xff1a; 3.再…

好用的流程图工具

分享工作中常用的装逼工具 目前市面上的流程图或者思维导图工具挺多的&#xff0c;但是有的会限制使用数量或者收费&#xff0c;典型的有processon、Xmind&#xff0c;推荐今天Mermaid(官网)。 快速上手 中文教程&#xff1a;Mermaid 初学者用户指南 | Mermaid 中文网。我们选择…

Spring | Spring中的Bean--下

Spring中的Bean: 4.Bean的生命周期5.Bean的配装配式 ( 添加Bean到IOC容器的方式 依赖注入的方式 )5.1 基于XML的配置5.2 基于Annotation (注解) 的装配 (更常用&#xff09;5.3 自动装配 4.Bean的生命周期 Spring容器可以管理 singleton作用域的Bean的生命周期&#xff0c;在此…

51单片机_电压采集器电压表

实物演示效果&#xff1a; https://www.bilibili.com/video/BV1My4y1F7xY/?vd_source6ff7cd03af95cd504b60511ef9373a1d 一、基本功能 利用51单片机作为主控芯片&#xff0c;3段式电压采集。模拟量经A/D&#xff08;ADC0809&#xff09;模数转换芯片&#xff0c;把模拟量转换…

【计算机网络】(1)OSI七层模型、协议、交换技术、路由器技术

文章目录 计算机网络功能与分类计算机网络的定义计算机网络的功能计算机网络的指标计算机网络的性能指标计算机网络的非性能指标 计算机网络的分布范围以及拓扑结构划分图计算机网络分类总线型拓扑星型拓扑环形图拓扑树型拓扑分布式拓扑 通信技术信道物理信道逻辑信道 发信机OS…

微信小程序(六)tabBar的使用

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1. 标签栏文字的内容以及默认与选中颜色 2. 标签栏图标的默认样式与选中样式 3. 标签选项路径页面 4.标签栏背景颜色 &#x1f43c;&#xff08;文末补充&#xff09;设置标签栏后为什么navigator标签无法跳转页…

ros2仿真学习04 -turtlebot3实现cartographer算法建图演示

安装看这里 https://blog.csdn.net/hai411741962/article/details/135619608?spm1001.2014.3001.5502 虚拟机配置&#xff1a; 内存16g cpu 4 核 磁盘40G,20G 不够 启动仿真 ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动成功如下 启动建图 重新开一个…

springboot 集成短信发送功能(人工智能编写)

要在Spring Boot中集成短信发送功能&#xff0c;你可以使用第三方的短信服务提供商的API来实现。以下是一个基本的示例代码&#xff0c;我是通过chatGPT4.0一键生成代码。 1. 添加依赖&#xff1a;在pom.xml文件中添加相应的短信服务提供商的SDK依赖&#xff0c;例如阿里云的a…

枚举类型缝缝补补

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 1.关键字enum的定义 enum是C语言中的一个关键字&#xff0c;enum叫枚举数据类型&#…

DSX-5000线缆认证测试仪-之原厂校准

众所周知&#xff0c;DTX-1800校准是校准本身的设备精度&#xff0c;还原原厂精度事宜。 135 375 00200 但是DSX-5000或者DSX-8000校准的不是平台本身&#xff0c;而是测试模块。也就是后面备注了型号的模块。所以每次告诉客户&#xff0c;不用把全部设备发过来&#xff…

备忘录怎么分享到微信 备忘录分享到微信的方法

在忙碌的工作和生活中&#xff0c;我时常发现自己需要记录一些重要的信息。那些一闪而过的灵感&#xff0c;或是同事突如其来的建议&#xff0c;都需要我迅速捕捉并妥善保存。这时&#xff0c;一个好用的备忘录就显得尤为重要。 然而&#xff0c;记录只是第一步。更多的时候&a…

Labview实现用户界面切换的几种方式---通过VI间相互调用

在做用户界面时我们的程序往往面对的对象是程序使用者&#xff0c;复杂程序如果放在同一个页面中&#xff0c;往往会导致程序冗长卡顿&#xff0c;此时通过多个VI之间的切换就可以实现多个界面之间的转换&#xff0c;也会显得程序更加的高大上。 本文所有程序均可下载&#xff…

30分钟带你深入优化安卓Bitmap大图

30分钟带你源码深入了解Bitmap以及优化安卓大图 一、前言二、Bitmap入门1. 如何创建Bitmap?2. Bitmap的堆内存分布在哪里3. 图片文件越大&#xff0c;Bitmap堆内存会越大吗&#xff1f;4. 如何管理Bitmap的内存&#xff1f;5. 实战修改Bitmap的堆内存&#xff0c;改变图片的图…

关于c++的三大特性 --- 多态(底层原理)

目录 多态的原理 虚函数表 底层 打印虚表 多继承的虚函数表 多态的原理 虚函数表 建议看下面的内容之前&#xff0c;先看一下c特性之多态 这里我们先来看一个笔试题&#xff1a;请问 sizeof(Base&#xff09;是多少&#xff1f; class Base { public:virtual void Func…

每周一算法:数独游戏

题目链接 数独游戏 题目描述 数独是根据 9 9 9 \times 9 99 盘面上的已知数字&#xff0c;推理出所有剩余空格的数字&#xff0c;并满足每一行、每一列、每一个粗线宫内的数字均含 1 − 9 1 - 9 1−9 &#xff0c;不重复。每一道合格的数独谜题都有且仅有唯一答案&#x…

vue3前端开发,生命周期函数的基础练习

vue3前端开发,生命周期函数的基础练习&#xff01; 下面先给大家看一个图片&#xff0c;帮助大家了解&#xff0c;vue3的生命周期函数&#xff0c;和旧版本vue2的生命周期函数&#xff0c;有什么变化。 如图所示&#xff0c;vue3里面&#xff0c;把前面2个函数&#xff0c;混在…

视频美颜SDK与人工智能的结合:技术突破与挑战

本篇文章&#xff0c;小编将与大家共同探讨美颜SDK与人工智能结合背后的技术原理、创新应用以及面临的挑战。 一、技术原理&#xff1a;人工智能在美颜中的应用 视频美颜SDK通过整合深度学习和计算机视觉技术&#xff0c;能够更准确地识别人脸特征、肤色、表情等信息&#xff…

CAN数据记录仪解决汽车电子与工程机械冬测难点

CAN数据记录仪在汽车电子与工程机械冬测中扮演着重要的角色。在寒冷的冬季&#xff0c;汽车可能会因为环境温度过低而出现各种问题&#xff0c;例如电池电量不足、发动机启动困难等。为了确保汽车在冬季的正常运行&#xff0c;需要对汽车进行电子冬测。 CAN数据记录仪在冬测中发…

CentOS 7.9 安装图解

特特特别的说明 CentOS发行版已经不再适合应用于生产环境&#xff0c;客观条件不得不用的话&#xff0c;优选7.9版本&#xff0c;8.5版本次之&#xff0c;最次6.10版本&#xff08;比如说Oracle 11GR2就建议在6版本上部署&#xff09;&#xff01; 引导和开始安装 选择倒计时结…