Go并发范式——生成器模式

本文介绍了Go语言中的生成器模式,包括带缓冲的生成器、多个goroutine增强型生成器和含退出通知的生成器。通过示例代码展示了各种模式的优缺点,强调了合理缓冲、控制goroutine数量以及自动退出机制的重要性。最后,提出了融合并发、缓冲和退出通知的优化方案,以实现性能最佳并减少资源消耗。
摘要由CSDN通过智能技术生成

生成器模式

0x00 前言

  Golang作为天生支持并发的语言,本系列将介绍Go的并发范式,也会提供部分示例代码,生产中依托业务稍加修改即可。

0x01 带缓冲的生成器

代码示例如下所示

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

// 创建数据,每创建一个暂停1秒
func generateIntA() chan int {
	ch := make(chan int, 10)
	go func() {
		for {
			ch <- rand.Int()
			time.Sleep(time.Second)
		}
	}()
	return ch
}

// 不间断读取管道中数据
func getIntA(ch chan int) {
	go func() {
		for {
			fmt.Println(<-ch)
		}
	}()
}

func main() {
    ch := generateIntA()
    getIntA(ch)
    // 睡眠五秒,使得协程能输出结果
    time.Sleep(time.Second * 5)
    // 打印当前存在的goroutine数
    fmt.Printf("runtime.NumGoroutine(): %v\n", runtime.NumGoroutine())
}

验证代码

package main

import (
	"sync"
	"sync/atomic"
	"testing"
)

func BenchmarkGenerateIntA(b *testing.B) {
	for i := 0; i < b.N; i++ {
		c := make(chan uint32, 100)

		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			defer wg.Done()

			for i := uint32(0); i < 1000; i++ {
				c <- i % 2
			}
			close(c)
		}()

		var total uint32
		for w := 0; w < 5; w++ {
			wg.Add(1)
			go func() {
				defer wg.Done()

				for {
					v, ok := <-c
					if !ok {
						break
					}
					atomic.AddUint32(&total, v)
				}
			}()
		}

		wg.Wait()
	}
}
func BenchmarkGenerateIntB(b *testing.B) {
	for i := 0; i < b.N; i++ {
		c := make(chan uint32, 1)

		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			defer wg.Done()

			for i := uint32(0); i < 1000; i++ {
				c <- i % 2
			}
			close(c)
		}()

		var total uint32
		for w := 0; w < 5; w++ {
			wg.Add(1)
			go func() {
				defer wg.Done()

				for {
					v, ok := <-c
					if !ok {
						break
					}
					atomic.AddUint32(&total, v)
				}
			}()
		}

		wg.Wait()
	}
}

得到结果

goos: windows
goarch: amd64
pkg: exp5
cpu: Intel(R) Core(TM) i5-10500 CPU @ 3.10GHz
BenchmarkGenerateIntA-12    	    9722	    125295 ns/op	     542 B/op	       3 allocs/op
BenchmarkGenerateIntB-12    	    3084	    381594 ns/op	     129 B/op	       3 allocs/op
PASS

结论

可以看出平均处理时间,有缓冲区会远快于无缓冲区

优点:消费者先获取缓冲区数据,只有当缓冲区数据全部被读取后才会进入读取阻塞状态
缺点:缓冲区不可能无限,如果并发达到一定数量仍会进入阻塞,生产者生产速度赶不上消费速度

0x02 多个goroutine增强型生成器

示例代码如下所示

// 多个goroutine增强型生成器
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

// 创建数据A
func generateIntA() chan int {
	ch := make(chan int)
	go func() {
		for {
			ch <- rand.Int()
		}
	}()
	return ch
}

// 创建数据B
func generateIntB() chan int {
	ch := make(chan int)
	go func() {
		for {
			ch <- rand.Int()
		}
	}()
	return ch
}

// 增强型生成器,每一秒生成一个新的AB生成器
func generateInt() chan int {
	ch := make(chan int)
	go func() {
		for {
			select {
			case ch <- <-generateIntA():
				time.Sleep(time.Second)
			case ch <- <-generateIntB():
				time.Sleep(time.Second)
			}
		}
	}()
	return ch
}

// 读取管道中数据
func getIntA(ch chan int) {
	go func() {
		for {
			<-ch
			// fmt.Println(<-ch)
		}
	}()
}

func main() {
	ch := generateInt()
	getIntA(ch)
	// 等待五秒使协程能够运行
	time.Sleep(time.Second * 5)
	fmt.Printf("runtime.NumGoroutine(): %v\n", runtime.NumGoroutine())
}

基准测试结果如下

goos: windows
goarch: amd64
pkg: exp6
cpu: Intel(R) Core(TM) i5-10500 CPU @ 3.10GHz
BenchmarkGenerateInt-12     	   34491	     36177 ns/op	    7811 B/op	      32 allocs/op
BenchmarkGenerateIntA-12    	   10000	    671327 ns/op	   24202 B/op	     112 allocs/op
PASS

结论

优点:将阻塞状态出现的可能性降得更低,基本不会出现阻塞状态
缺点:生成大量goroutine,可能造成程序崩溃

0x03 含退出通知的生成器

代码示例如下所示

在此代码中,done通道作为退出通知的接收通道,利用对已关闭通道可以读取而不会引发panic的特性,不对done进行写入,当需要取消生成时,关闭done通道,select选择器可以运行读取done通道操作,从而退出生成器,结束协程。

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

// 创建数据,每创建一个暂停1秒
func generateIntA(done chan int) chan int {
	ch := make(chan int, 10)
	go func() {
	Label:
		for {
			select {
			case ch <- rand.Int():
				time.Sleep(time.Second)
			case <-done:
				break Label
			}
		}
		close(ch)
	}()
	return ch
}

// 不间断读取管道中数据
func getIntA(ch, done chan int) {
	go func() {
	Label:
		for {
			select {
			case <-ch:
				fmt.Println(<-ch)
			case <-done:
				break Label
			}
		}
	}()
}

func main() {
	done := make(chan int)
	ch := generateIntA(done)
	getIntA(ch, done)
	// 睡眠五秒,使得协程能输出结果
	time.Sleep(time.Second * 5)
	// 发送关闭信号,停止生成
	close(done)
	// 睡眠五秒保证协程退出
	time.Sleep(time.Second * 5)
	// 打印当前存在的goroutine数
	fmt.Printf("runtime.NumGoroutine(): %v\n", runtime.NumGoroutine())
}

结论

优点:可以使生成器自动退出,减少资源消耗

0x04 融合并发、缓冲、退出通知的生成器

代码示例如下所示

// 多个goroutine增强型生成器 含推出通知机制
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

// 创建数据A
func generateIntA(done chan struct{}) chan int {
	ch := make(chan int, 10)
	go func() {
	Label:
		for {
			select {
			case ch <- rand.Int():
				time.Sleep(time.Second)
			case <-done:
				break Label
			}
		}
		close(ch)
	}()
	return ch
}

// 创建数据B
func generateIntB(done chan struct{}) chan int {
	ch := make(chan int, 10)
	go func() {
	Label:
		for {
			select {
			case ch <- rand.Int():
				time.Sleep(time.Second)
			case <-done:
				break Label
			}
		}
		close(ch)
	}()
	return ch
}

// 增强型生成器
func generateInt(done chan struct{}) chan int {
	ch := make(chan int, 10)
	go func() {
	Label:
		for {
			select {
			case ch <- <-generateIntA(done):
			case ch <- <-generateIntB(done):
			case <-done:
				break Label
			}
		}
		close(ch)
	}()
	return ch
}

// 读取管道中数据
func getIntA(ch chan int, done chan struct{}) {
	go func() {
	Label:
		for {
			select {
			case <-ch:
				fmt.Println(<-ch)
				time.Sleep(time.Second)
			case <-done:
				break Label
			}
		}
	}()
}

func main() {
	// 规定监控通道
	done := make(chan struct{})
	ch := generateInt(done)
	getIntA(ch, done)

	// 等待五秒使协程能够运行
	time.Sleep(time.Second * 5)
	close(done)

	// 等待十五秒使协程完全退出
	time.Sleep(time.Second * 15)

	fmt.Printf("runtime.NumGoroutine(): %v\n", runtime.NumGoroutine())
}

结论

性能最佳,并且可以自动退出

0x05 总结

  1. 合理的缓冲区大小可以有效提升程序运行效率。
  2. 使用增强型生成器时要注意控制程序的goroutine数量。
  3. 自动退出机制根据具体使用场景使用如生成大批订单号时,可以临时调用生成完订单号后退出,资源临时调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值