当前位置: 首页 > news >正文

go语言基础之goroutine与channel经典练习题

概述

Goroutine: 在 Go 语言中,goroutine 是轻量级的线程。它是 Go 运行时管理的,而不是由操作系统管理。一个 Go 程序可以启动很多 goroutine,每个 goroutine 都是一个独立的执行单元。

Channel: Channel 是 Go 语言中的一种数据类型,用于在 goroutine 之间传递数据。Channel 可以用于在 goroutine 之间同步数据,或者用于在 goroutine 之间传递数据

Goroutine的特点

Goroutine 是 Go 语言的并发执行单元,它们是轻量级的线程。
Goroutine 由 Go 运行时管理,而不是操作系统。
Goroutine 的创建和调度都是由 Go 运行时自动管理的。
Goroutine 的调度是非确定性的,这意味着它们的执行顺序是不确定的。
Goroutine 的栈大小是可变的,可以根据需要动态调整。

Channel的特点

Channel 是 Go 语言中的数据类型,用于在 goroutine 之间传递数据。
Channel 可以用于在 goroutine 之间同步数据,或者用于在 goroutine 之间传递数据。
Channel 的发送和接收操作都是阻塞的。
Channel 是线程安全的,这意味着多个 goroutine 可以安全地向同一个 channel 发送和接收数据。
Channel 可以使用 select 语句进行非阻塞的操作。
Channel 可以使用 close 函数关闭,这将导致向已关闭的 channel 发送数据会导致 panic,而从已关闭的 channel 接收数据会返回零值和一个 false。
这些特性使得 Go 语言的并发编程模型具有高度的灵活性和强大性,使得开发者可以轻松地编写出高性能、高并发的应用程序。

本文将通过goroutine与channel实现并发处理任务的经典题目,从而加深对goroutine与channel的理解。

经典题目

题目:计算出50万以内的素数。用普通方法后,再考虑用goroutine加快处理速度。

题目要求

  • 使用普通方法进行计算。
  • 利用goroutine多协程方式实现。
  • 对比两种方法的时间效率差。
    说明: 本例是一个使用goroutine与channel比较综合的题目,有一定复杂度,需要先仔细分析,有思路后才上代码。

普通方式实现

即轮询1-50万的正整数,依次计算出素数

package mainimport ("fmt""time"
)// 判断一个数是否是素数
func isPrieme(num int) bool {for i:=2;i<num;i++{if num%i == 0 {return false}}return true
}func main() {//ch := make(chan int) //创建一个无缓存channelstart := time.Now().Unix()for i:=1;i<500000;i++{if isPrieme(i) {//fmt.Println(i)}}totolTime := time.Now().Unix() - startfmt.Println("用时(s):",totolTime)}

执行结果

(base) ➜  study go run test.go
用时(s): 71

多协程并发处理方式实现

  • 思路分析

需要借用三个管道来完成题目

  1. 用一个协程,将50万个数放入一个管道inChannel中,放入完成后关闭管道。
  2. 启动多个协程,同时从inChannel中取数据,并判断是否为素数,如果是素数则将结果放入另一个管道 resultChannel
  3. 当某个协程完成操作后,放入一个标记位True,到一个叫flagChannel的管道。这个channel用于标识某个协程已经完成退出,当flagChannel管道内的元素个数等于启动的协程数量时,表示所有协程都完成并退出了。

在这里插入图片描述

package mainimport ("fmt""runtime""time"
)/*
1. 用一个协程,将1-100个数放入一个channel inChannel中。
2. 启动10个协程,同时从inChannel中取数据,并计算n的平方,将结果值放入一个新的channel resultChannel
3. 10个协程,写完后,放入一个标记位True,到标记channel flagChannel,这个channel用于标识已经完成数据处理
4. 当标记位个数为10时,表示10个协程都不能取数据了(inChannel已经空了),这时才可以关闭管道resultChannel*/// 判断一个数是否是素数
func isPrieme(num int) bool {for i:=2;i<num;i++{if num%i == 0 {return false}}return true
}func writeData(inChannel chan int){// 写入100个数据到inChannelfor i:=1;i<=500000;i++{inChannel <- i//fmt.Println("写入数据data= ",i)}// 写入完成后关闭inChannelclose(inChannel)
}func readAndDealData(inChannel chan int,resultChannel chan int, flagChannel chan bool){fmt.Println("启动一个新的协程------------")for {// 从inChannel中拿数据data,ok := <-inChannelif ok {// 如果拿到数据//fmt.Println("拿到数据data= ",data)// 对拿到的数据进行处理if isPrieme(data) {// 处理完毕后,将结果放入结果管道resultChannelresultChannel<- data//fmt.Println("素数=",data)}}else {// 如果没有从inChannel拿到数据,表示这个协程的工作已经结束break}}// 拿不到数据则表示这个协程已经处理完成,则放一个标记位到flagChannel管道flagChannel <- true}func main(){fmt.Println("hello world")// 设置可用处理器个数cpuNum := runtime.NumCPU()runtime.GOMAXPROCS(cpuNum-1)fmt.Println("cpuNum=",cpuNum)// 启动协程的数量gorutNum := 8//1 --- 69,2 --- 35s,4 --- 21// 保存输入数据inChannel := make(chan int, 10000)// 保存计算结果resultChannel := make(chan int, 100000)// 保存退出标记位,如果flagChannel一旦有数据,标识已经处理完成,主进程检查到后就退出flagChannel := make(chan bool, gorutNum)start := time.Now().Unix()// 启动写数据的协程go writeData(inChannel)// 启动10个同时拿数据并处理数据的协程for i:=0;i<gorutNum;i++ {go readAndDealData(inChannel,resultChannel,flagChannel)}// 阻塞主进程,等待所有协程完成for i:=0;i<gorutNum;i++ {_,ok := <-flagChannelif ok {// 一旦有数据,表示所有协程已经处理完成fmt.Println("一个协程处理完毕!")}}close(flagChannel)fmt.Println("Done!")totolTime := time.Now().Unix() - startfmt.Println("总共用时(s): ",  totolTime )
}

执行结果

(base) ➜  04 go run main.go
hello world
cpuNum= 8
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
启动一个新的协程------------
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
一个协程处理完毕!
Done!
总共用时(s):  15

可以看出,使用多协程方式来计算50万以内的素数只用了15秒

结果对比

测试机多协程方式普通方式
macbook i715秒71秒

所以看处理大数据计算密集时可以考虑用goroutine方式。但是从编码难度上考虑,goroutine方式要比普通方式要大一些。


http://www.mrgr.cn/news/4431.html

相关文章:

  • 【Linux —— 线程互斥】
  • 数据库进阶 - 可串行化隔离级别的底层原理
  • ubuntu20 vmware硬盘空间不够,进行扩容,实操成功!
  • 个人查找下载Begell House数据库文献的方法
  • 服务优雅上下线优雅停机
  • SDK 和 API
  • 迁移学习代码复现
  • golang实现windows获取加密盘符的总大小
  • 【好书推荐】值得深读的EMC参考书籍
  • spring boot自动配置
  • 英语二【00015】精选单词练习第十天
  • 使用Blender进行3D建模—基础操作笔记(移动、缩放、视角切换,旋转)
  • 考驾照需要多长时间?你考驾照用了多长时间?
  • 【Solidity】代币
  • 深入探讨 ElementUI 动态渲染 el-table
  • 【ARM 芯片 安全与攻击 5 -- 测信道攻击(Side-channel Attack)】
  • python实现人脸轮廓提取(开操作和闭操作)
  • 【流媒体】RTMPDump—AMF编码
  • 【esp32程序编译提示undefined reference to ‘xxxx‘】
  • 线程池介绍