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

实现一个并发程序,用于寻找 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.mfbz.cn/a/331712.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

捍卫中华数学产权系列6.Σ1/n的分级通式与对应的求和公式

调和级数Σ1/n在欧系数学是极为重要的概念&#xff0c;它是纯粹数学的七寸、关乎欧系数学的存废。然而欧洲人对Σ1/n从来没有发现任意一个求和公式。本人以简单而充分的逻辑给Σ1/n进行了多种多样的分级、并给出了各自对应的求和公式&#xff0c;所以&#xff0c;一切关于调和级…

vue+elementui实现12个日历平铺,初始化工作日,并且可点击

<template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" :inline"true"><el-form-item label"年份" prop"holidayYear"><el-date-…

Verilog基础:强度建模(一)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 一、强度建模基础 Verilog HDL提供了针对线网信号0、1、x、z的精准强度建模方式&#xff0c;这样可以允许将两个线网信号进行线与操作从而更加精确地描述出硬件行…

string 模拟实现

string的数据结构 char* _str; size_t _size; size_t _capacity; _str 是用来存储字符串的数组&#xff0c;采用new在堆上开辟空间&#xff1b; _size 是用来表示字符串的长度&#xff0c;数组大小strlen(_str)&#xff1b; _capacity 是用来表示_str的空间大小, _capacity…

使用 Postman 发送 get 请求的简易教程

在API开发与测试的场景中&#xff0c;Postman 是一种普遍应用的工具&#xff0c;它极大地简化了发送和接收HTTP请求的流程。要发出GET请求&#xff0c;用户只需设定正确的参数并点击发送即可。 如何使用 Postman 发送一个GET请求 创建一个新请求并将类型设为 GET 首先&#…

余承东发声!预测:2024年,鸿蒙OS将取代苹果iOS…

半导体行业观察机构Techinsights&#xff0c;1月3日发布报告预测&#xff1b;从2024年起&#xff0c;鸿蒙Harmony OS将取代苹果iOS&#xff0c;成为中国市场上第二大智能手机操作系统。 TechInsights预测&#xff0c;2024年全球智能手机销量将同比反弹3%。华为手机在2024年将坚…

基于Harris角点的多视角图像全景拼接算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Harris角点检测 4.2 图像配准 4.3 图像变换和拼接 4.4 全景图像优化 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 function [ImageB…

k8s---ingress对外服务(ingress-controller)

ingress 概念 k8s的对外服务&#xff0c;ingress service作用现在两个方面&#xff1a; 1、集群内部&#xff1a;不断跟踪的变化&#xff0c;更新endpoint中的pod对象&#xff0c;基于pod的ip地址不断变化的一种服务发现机制。 2、集群外部&#xff1a;类似于负载均衡器&a…

如何在Linux运行RStudio Server并实现Web浏览器远程访问

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言1. 安装RStudio Server2. 本地访问3. Linux 安装cpolar4. 配置RStudio server公网访问地址5. …

Linux系统编程(二)文件IO/系统调用IO

一、IO 简介 I/O 是一切实现的基础&#xff1a; 标准 IO&#xff08;stdio&#xff09;&#xff1b;系统调用 IO&#xff08;sysio&#xff0c;文件IO&#xff09;&#xff1b; 不同系统上的系统调用 IO 的使用方式可能不一样&#xff0c;为了隐藏不同系统上的细节&#xff…

mysql 为大表新增字段或索引

1 问题 mysql 为大表增加或增加索引等操作时&#xff0c;直接操作原表可能会因为执行超时而导致失败。解决办法如下。 2 解决办法 &#xff08;1&#xff09;建新表-复制表A 的数据结构&#xff0c;不复制数据 create table B like A; &#xff08;2&#xff09;加字段或索…

使用muduo库编写网络server端

muduo库源码编译安装和环境搭建 C muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装-CSDN博客 #include<iostream> #include<muduo/net/TcpServer.h> #include<muduo/net/EventLoop.h> using namespace std; using namespace muduo; using name…

两道有挑战的问题(算法村第九关黄金挑战)

将有序数组转换为二叉搜索树 108. 将有序数组转换为二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个…

作为班主任如何管理好班级

作为班主任&#xff0c;如何才能把班级管理得井井有条&#xff0c;让每个学生都能够得到全面的发展呢&#xff1f;这个问题一直困扰着许多班主任。接下来&#xff0c;我将从几个方面来分享一下自己的经验和看法。 建立良好的师生关系是班级管理的基石。作为班主任&#xff0c;…

【linux】粘滞位.yum

粘滞位 1.为什么我们普通用户可以删掉别人的文件&#xff08;包括root&#xff09;?合理吗&#xff1f; 2.删除一个文件和目标文件有关系吗&#xff1f; 没关系&#xff0c;和所处的目录有关系。 1.我们先以root身份创建一个目录&#xff0c;接着在这个目录下创建一个文件 2…

如何获取一个德国容器

1.注册discord账号 discord注册网址:https://discord.com/ 下面是容器的discord邀请链接 https://discord.com/Discord邀请链接:https://discord.com/invite/jVMSWrchC4 2.进入discord群聊点击link 在点击网址,这个网址每星期都会变就是图中的② 3.进入容器网址,进入界面…

POKT Network 开启周期性通缩,该计划将持续至 2025 年

POKT Network&#xff08;也被称为 Pocket Network&#xff09;在通证经济模型上完成了重大的改进&#xff0c;不仅将通货膨胀率降至 5% 以下&#xff0c;并使 POKT 通证在 2025 年走向通缩的轨迹上&#xff0c;预计到2024 年年底通货膨胀率将降至 2% 以下。POKT Network 的 “…

JVM工作原理与实战(十九):运行时数据区-方法区

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、运行时数据区 二、方法区 1.方法区介绍 2.方法区在Java虚拟机的实现 3.类的元信息 4.运行时常量池 5.字符串常量池 6.静态变量的存储 总结 前言 JVM作为Java程序的运行环境…

什么是网络安全,如何防范?

网络安全&#xff08;Cyber Security&#xff09;是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 网络安全涵盖了网络设备安全、网络信息安全…

canvas绘制不同样式的五角星(图文示例)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…