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 main

import (
	"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) //创建一个无缓存channel
	start := time.Now().Unix()
	for i:=1;i<500000;i++{
		if isPrieme(i) {
			//fmt.Println(i)
		}
	}
	totolTime := time.Now().Unix() - start
	fmt.Println("用时(s):",totolTime)

}

执行结果

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

多协程并发处理方式实现

  • 思路分析

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

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

在这里插入图片描述

package main

import (
	"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个数据到inChannel
	for i:=1;i<=500000;i++{
		inChannel <- i
		//fmt.Println("写入数据data= ",i)
	}
	// 写入完成后关闭inChannel
	close(inChannel)
}

func readAndDealData(inChannel chan int,resultChannel chan int, flagChannel chan bool){

	fmt.Println("启动一个新的协程------------")
	for {
		// 从inChannel中拿数据
		data,ok := <-inChannel
		if ok {
			// 如果拿到数据
			//fmt.Println("拿到数据data= ",data)
			// 对拿到的数据进行处理
			if isPrieme(data) {
				// 处理完毕后,将结果放入结果管道resultChannel
				resultChannel<- 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 := <-flagChannel
		if ok {
			// 一旦有数据,表示所有协程已经处理完成
			fmt.Println("一个协程处理完毕!")
		}
	}
	close(flagChannel)
	fmt.Println("Done!")

	totolTime := time.Now().Unix() - start
	fmt.Println("总共用时(s): ",  totolTime )
}

执行结果

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

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

结果对比

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SRExianxian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值