go并发模式

所谓的并发模式并不像设计模式一样是一个普遍的标准,只是前人总结出的经验

生成器

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	for {
		fmt.Println(<-m1)
		fmt.Println(<-m2)
	}
}

在这里插入图片描述
生成器其实就是服务/任务;利用chan来与对应服务去进行交互;再msgGen函数里面一直给两个chan发送数据;main函数里面一直再收两个chan的数据;但是两个chan的消息是交替消费的;必须要等接收到m1时再去接收m2,并不是同时的等待m1和m2;这个不是理想的效果

添加中间节点

在这里插入图片描述
我们通过添加一个中间节点,把两个chan的数据统一收上来,然后再发到中间节点上,我们main函数里面只负责去接收中间节点的消息,达到我们同时等待m1和m2数据的目的

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func fanIn(m1, m2 chan string) chan string {
	c := make(chan string)
	go func() {
		for {
			c <- <-m1
		}
	}()
	go func() {
		for {
			c <- <-m2
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	m := fanIn(m1, m2)
	for {
		fmt.Println(<-m)
	}
}

在这里插入图片描述

Select实现

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func fanIn(m1, m2 chan string) chan string {
	c := make(chan string)
	go func() {
		for {
			c <- <-m1
		}
	}()
	go func() {
		for {
			c <- <-m2
		}
	}()
	return c
}

func fanIntBySelect(m1, m2 chan string) chan string {
	c := make(chan string)
	go func() {
		for {
			select {
			case m := <-m1:
				c <- m
			case m := <-m2:
				c <- m
			}
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	m := fanIntBySelect(m1, m2)
	for {
		fmt.Println(<-m)
	}
}

在这里插入图片描述

中间节点 VS Select

  • Select

    • 写起来简单
    • 只开了一个协程(不管有多少chan都是一个协程)
    • 真正的做到了同时等待两个chan
    • 适合对chan数量不明确的场景下使用
  • 中间节点

    • 开了两个协程(有多少chan就要开多少协程)
    • 适合对chan数量明确的场景下使用

改造中间节点方式适用于chan数量不明确

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func fanIn(chans ...chan string) chan string {
	c := make(chan string)
	for _, ch := range chans {
		go func(ch chan string) {
			for {
				c <- <-ch
			}
		}(ch)
	}
	return c
}

func fanIntBySelect(m1, m2 chan string) chan string {
	c := make(chan string)
	go func() {
		for {
			select {
			case m := <-m1:
				c <- m
			case m := <-m2:
				c <- m
			}
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	m := fanIn(m1, m2)
	for {
		fmt.Println(<-m)
	}
}

在这里插入图片描述
再fanIn函数里面必须要把最外层的循环里面的ch传到协程里面;不然再main函数里面就只会接收到最后一个chan的数据.具体原因是因为最外层的ch这个变量再全局只有一份,我们再循环里面创建协程的时候,只是把协程创建了,并不会马上的去执行协程里面的内容,等到遍历到最后一个chan的时候所有的协程里面都会改变成最后一个ch;所以就只会接收到最后一个chan的数据.但是我们把ch当成参数传入到协程里面就相当于把每个chan都copy了一份放到协程里面;就不会发生改变,也不会出现问题!

任务的控制

非阻塞等待

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}
func main() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	for {
		fmt.Println(<-m1)
		select {
		case m := <-m2:
			fmt.Println(m)
		default:
			fmt.Println("no message from service2")
		}
	}
}


在这里插入图片描述

超时机制

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	for {
		if m, ok := timeoutWait(m1, time.Second); ok {
			fmt.Println(m)
		} else {
			fmt.Println("timeout")
		}
	}
}

func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
	select {
	case m := <-c:
		return m, true
	case <-time.After(timeout):
		return "", false
	}
}

在这里插入图片描述

任务中断/退出

package main

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

func msgGen(serviceName string) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("%s message: %d", serviceName, i)
			i++
		}
	}()
	return c
}

func main() {
	m1 := msgGen("service1")
	for i := 0; i < 5; i++ {
		if m, ok := timeoutWait(m1, time.Second); ok {
			fmt.Println(m)
		} else {
			fmt.Println("timeout")
		}
	}
}

func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
	select {
	case m := <-c:
		return m, true
	case <-time.After(timeout):
		return "", false
	}
}

在这里插入图片描述
这里面的退出是main函数把循环都执行完了,不管其他的协程是否执行完毕,都会统一的杀死

优雅的退出

package main

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

func msgGen(serviceName string, done chan struct{}) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			select {
			case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond):
				// 2秒内没有收到done才会去发送数据
				c <- fmt.Sprintf("%s message: %d", serviceName, i)
			case <-done:
				// 如果收到了done,直接优雅的退出
				fmt.Println("准备退出...")
				time.Sleep(2 * time.Second)
				done <- struct{}{}
				fmt.Println("准备完成")
				return
			}

			i++
		}
	}()
	return c
}

func main() {
	done := make(chan struct{})
	m1 := msgGen("service1", done)
	for i := 0; i < 5; i++ {
		if m, ok := timeoutWait(m1, time.Second); ok {
			fmt.Println(m)
		} else {
			fmt.Println("timeout")
		}
	}
	// main函数执行完毕通知其他协程退出
	done <- struct{}{}
	<-done
}

func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
	select {
	case m := <-c:
		return m, true
	case <-time.After(timeout):
		return "", false
	}
}

在这里插入图片描述

通过done chan双向交互进行优雅的退出,实际的应用中不一定要重用done chan

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

.番茄炒蛋

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

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

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

打赏作者

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

抵扣说明:

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

余额充值