golang中的几种并发模式

0.1、索引

https://blog.waterflow.link/articles/1663551951058

1、for- select模式

这种模式通常用在从多个通道读取数据

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1, ch2 := make(chan int), make(chan int)

  // 每2秒不断往通道1写数据
	go func() {
		i := 0
		for {
			i += 2
			ch1 <- i
			time.Sleep(2 * time.Second)
		}
	}()

  // 每2秒不断往通道2写数据
	go func() {
		i := 1
		for {
			i += 2
			ch2 <- i
			time.Sleep(2 * time.Second)
		}
	}()

  // 不断从通道读数据
	for {
		select {
		case v := <-ch1:
			fmt.Println("ch1:", v)
			time.Sleep(time.Second)
		case v := <-ch2:
			fmt.Println("ch2:", v)
			time.Sleep(time.Second)
		default:
			fmt.Println("default")
			time.Sleep(time.Second)
		}
	}

}

如果ch1和ch2没数据,会走default

如果ch1和ch2都有数据会随机选择一个执行,之所以随机是为了避免只执行第一个case导致饥饿

2、done-channel模式

由于goroutine不会被垃圾回收,因此很可能导致内存泄漏。

为了避免内存泄漏,goroutine应该有被触发取消的机制。父 Goroutine 需要通过一个名为 done 的只读通道向其子 Goroutine 发送取消信号。按照惯例,它被设置为第一个参数。

这种模式在其他模式中也被大量使用。

package main

import (
	"fmt"
)

func main() {
	jobs := make(chan int, 5)
	done := make(chan bool)

	go doWork(done, jobs)

	for j := 1; j <= 3; j++ {

		fmt.Println("sent job", j)
		jobs <- j
	}
	close(jobs)
	fmt.Println("sent all jobs")

  // 任务结束
	done <- true
}

func doWork(done chan bool, jobs chan int) {
	for {
		select {
		case j, more := <-jobs:
			if more {
				fmt.Println("received job", j)
			} else {
				fmt.Println("received all jobs")
			}
		case <-done: // 任务结束,关闭子协程
			return
		default:
		}
	}
}

3、or-done模式

该模式旨在将多个完成通道组合成一个 agg_done;这意味着如果一个 done 通道发出信号,则整个 agg_done 通道也将关闭。然而,我们不知道在运行时完成通道的数量。

or-done 模式可以通过使用 goroutine 和 递归 来实现。

示例中 使上下递归函数像树一样相互依赖。上部将自己的 orDone 通道注入下部。然后下层也将自己的 orDone 返回给上层。

如果任何 orDone 通道关闭,则通知上层和下层。

这点和上面done-channel模式是不同的,上面是所有goroutine完成任务,这里是只要有1个goroutine完成就结束所有goroutine。

就好比发送一个请求到多个微服务节点,只要有1个返回就算完成。

package main

import (
	"fmt"
	"time"
)

func main() {
	var or func(channels ...<-chan interface{}) <-chan interface{}
	// 只要有1个结束阻塞,关闭orDone并返回
	or = func(channels ...<-chan interface{}) <-chan interface{} {
		// 小于2个通道直接返回
		switch len(channels) {
		case 0:
			return nil
		case 1:
			return channels[0]
		}
		// 声明一个orDone
		orDone := make(chan interface{})
		go func() {
			// 完成关闭orDone
			defer close(orDone)
			switch len(channels) {
			case 2: // 如果是2个channel,只需要监听这两个
				select {
				case <-channels[0]:
				case <-channels[1]:
				}
			default:
				// 二分法递归
				m := len(channels) / 2
				select {
				case <-or(channels[:m]...):
				case <-or(channels[m:]...):
				}
			}
		}()
		return orDone
	}

	// 传入一个时间模拟请求时长,时间到了就close掉,结束当前channel的阻塞
	sig := func(after time.Duration) <-chan interface{} {
		c := make(chan interface{})
		go func() {
			defer close(c)
			time.Sleep(after)
		}()
		return c
	}

	start := time.Now()
	// 这里orDone开始是阻塞的,里面开了5个channel
	<-or(
		sig(2*time.Hour),
		sig(5*time.Minute),
		sig(1*time.Second),
	)
	fmt.Printf("done after %v\n", time.Since(start))
}

4、fanout-channel模式

意思是只有1个输入channel,有多个输出channel,经常用在设计模式中的观察者模式。观察者模式中,当数据发生变动后,多个观察者都会收到这个信号。

package main

import (
	"fmt"
	"time"
)

func main() {
	// 输入的channel,相当于被观察者
	ch := make(chan interface{})
	go func() {
		for {
			ch <- time.Now()
			time.Sleep(3 * time.Second)
		}
	}()

	// 观察者
	out := make([]chan interface{}, 2)
	for k := range out {
		out[k] = make(chan interface{})
	}

	go fanout(ch, out)

	// 是否观察到数据变化
	for {
		select {
		case res := <-out[0]:
			fmt.Println(res)
		case res := <-out[1]:
			fmt.Println(res)
		}
	}
}

func fanout(ch <-chan interface{}, out []chan interface{}) {
	defer func() {
		for i := 0; i < len(out); i++ {
			close(out[i])
		}
	}()

	// 订阅被观察者
	for v := range ch {
		v := v
		for i := 0; i < len(out); i++ {
			i := i
			out[i] <- v
		}
	}

}

5、fan-in-channel模式

和上面的相反,这个是指多个源channel输入,一个目标channel输出的情况。

package main

import (
	"fmt"
	"time"
)

func main() {
	// 输入的channel
	in := make([]chan interface{}, 2)
	in2 := make([]<-chan interface{}, 2)

	for k := range in {
		k := k
		in[k] = make(chan interface{})
		var inin <-chan interface{} = in[k]
		in2[k] = inin

		go func() {
			for {
				in[k] <- time.Now()
				time.Sleep(3 * time.Second)
			}
		}()

	}

	// 打印输出的channel
	for v := range fanIn(in2...) {
		fmt.Println(v)
	}

}

func fanIn(chans ...<-chan interface{}) <-chan interface{} {
	switch len(chans) {
	case 0:
		c := make(chan interface{})
		close(c)
		return c
	case 1:
		return chans[0]
	case 2:
		return mergeTwo(chans[0], chans[1])
	default: // 多个channel二分法
		m := len(chans) / 2
		return mergeTwo(fanIn(chans[:m]...), fanIn(chans[m:]...))
	}

}

func mergeTwo(a, b <-chan interface{}) <-chan interface{} {
	// 针对2个channel输出
	c := make(chan interface{})
	go func() {
		defer close(c)
		for a != nil || b != nil {
			select {
			case v, ok := <-a:
				if !ok {
					a = nil
					continue
				}
				c <- v
			case v, ok := <-b:
				if !ok {
					b = nil
					continue
				}
				c <- v
			}

		}
	}()
	return c
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值