常见的并发模式

一、发布订阅模式

package main

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

/*
	发布订阅(publish-and-subscribe)模型通常被简写为 pub/sub 模型。

在这个模型中,消息生产者成为发布者(publisher),而消息消费者则成为订阅者(subscriber),
生产者和消费者是 M:N 的关系。在传统生产者和消费者模型中,是将消息发送到一个队列中,而发布订阅模型则是将消息发布给一个主题。
*/

func main() {
	//有两个订阅者分别订阅了全部主题和含有"golang"的主题:
	p := NewPublisher(100*time.Millisecond, 10)
	defer p.Close()

	all := p.Subscribe()
	golang := p.SubscribeTopic(func(v interface{}) bool {
		if s, ok := v.(string); ok {
			return strings.Contains(s, "golang")
		}
		return false
	})
	p.Publish("hello,world!")
	p.Publish("hello,golang!")
	go func() {
		for msg := range all {
			fmt.Println("all:", msg)
		}
	}()
	go func() {
		for msg := range golang {
			fmt.Println("golang:", msg)
		}
	}()
	//运行一定时间退出
	time.Sleep(3 * time.Second)
}

type (
	subscriber chan interface{}         //订阅者为一个管道
	topicFunc  func(v interface{}) bool //主题为一个过滤器
)

// 发布者对象
type Publisher struct {
	m           sync.RWMutex             //读写锁
	buffer      int                      //订阅队列的缓存大小
	timeout     time.Duration            //发布超时时间
	subscribers map[subscriber]topicFunc //订阅者信息
}

// 构建一个发布者对象,可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
	return &Publisher{
		buffer:      buffer,
		timeout:     publishTimeout,
		subscribers: make(map[subscriber]topicFunc),
	}
}

// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
	return p.SubscribeTopic(nil)
}

// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
	ch := make(chan interface{}, p.buffer)
	p.m.Lock()
	p.subscribers[ch] = topic
	p.m.Unlock()
	return ch
}

// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	delete(p.subscribers, sub)
	close(sub)
}

// 发送主题,可以容忍一定超时
func (p *Publisher) sendTopic(sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup) {
	defer wg.Done()
	if topic != nil && !topic(v) {
		return
	}
	select {
	case sub <- v:
	case <-time.After(p.timeout):
	}
}

// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
	p.m.RLock()
	defer p.m.RUnlock()

	var wg sync.WaitGroup
	for sub, topic := range p.subscribers {
		wg.Add(1)
		go p.sendTopic(sub, topic, v, &wg)
	}
	wg.Wait()
}

// 关闭发布者对象,同时关闭所有订阅者管道
func (p *Publisher) Close() {
	p.m.Lock()
	defer p.m.Unlock()
	for sub := range p.subscribers {
		delete(p.subscribers, sub)
		close(sub)
	}
}

二、并发

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	wghelloChannels()
}

// 加waitgroup的channel
func wghelloChannels() {
	var wg sync.WaitGroup
	//开启N个后台打印线程
	for i := 0; i < 10; i++ {
		wg.Add(1) //增加等待事件的个数
		go func() {
			defer wg.Done() //完成一个事件
			fmt.Println("你好,世界")
		}()
	}
	//等待N个后台线程完成
	wg.Wait() //等待全部事件完成
	fmt.Println("exit")
}

func helloChannel() {
	done := make(chan int, 1) //带缓存的管道
	go func() {
		time.Sleep(3 * time.Second)
		fmt.Println("hello world")
		done <- 1
	}()
	<-done
	fmt.Println("exit")
}

// 对于带缓冲的 Channel,对于 Channel 的第 K 个接收完成操作发生在第 K+C 个发送操作完成之前,其中 C 是 Channel 的缓存大小。
func helloChannels() {
	done := make(chan int, 10)
	//开启N个后台打印线程
	for i := 0; i < cap(done); i++ {
		go func(i int) {
			fmt.Println("你好 世界", i)
			done <- 1
		}(i)
	}
	//等待N个后台线程完成
	for i := 0; i < cap(done); i++ {
		<-done
	}
	fmt.Println("EXIT")
}

三、 并发的安全退出

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	//3.现在每个工作者并发体的创建、运行、暂停和退出都是在 main 函数的安全控制之下了。
	cancel := make(chan bool)

	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go workerwg(&wg, cancel)
	}
	time.Sleep(time.Second)
	close(cancel)
	wg.Wait()
	//2.我们通过 select 和 default 分支可以很容易实现一个 Goroutine 的退出控制:
	//cancel := make(chan bool)
	//go worker(cancel)
	//time.Sleep(5 * time.Millisecond)
	//cancel <- true

	//1.当多个管道均可操作时,select会随机选择一个管道
	//ch := make(chan int)
	//go func() {
	//	for {
	//		select {
	//		case ch <- 0:
	//		case ch <- 1:
	//		}
	//	}
	//}()
	//for v := range ch {
	//	fmt.Println(v)
	//}

}

// 3.现在每个工作者并发体的创建、运行、暂停和退出都是在 main 函数的安全控制之下了。
func workerwg(wg *sync.WaitGroup, cancel chan bool) {
	defer wg.Done()
	for {
		select {
		default:
			fmt.Println("hello")
		case <-cancel:
			return
		}
	}
}

// 2.我们通过 select 和 default 分支可以很容易实现一个 Goroutine 的退出控制:
func worker(cancel chan bool) {

	for {
		select {
		default:
			fmt.Println("hello") //正常退出
		case <-cancel:
			//退出
		}
	}
}

四、生产者消费者

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	ch := make(chan int, 64)
	go producer(5, ch)
	go producer(3, ch)
	go consumer(ch)

	//1. 运行一定时间退出
	//time.Sleep(1 * time.Second)

	//2.main函数保持阻塞状态,只有用户输入 ctrl-c 才真正退出
	sig := make(chan os.Signal, 1)
	//signa     l.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM) //这两个等价
	fmt.Printf("quit(%v)\n", <-sig)
}

// 生产者:生成factor的整数列
func producer(factor int, out chan<- int) {
	for i := 0; ; i++ {
		out <- i * factor
	}
}

// 消费者
func consumer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

五、素数筛

package main

import "fmt"

func main() {
	ch := GenerateNature()
	for i := 0; i < 100; i++ { //100次迭代循环
		prime := <-ch //新出现素数
		fmt.Printf("%v:%v\n", i+1, prime)
		ch = PrimeFilter(ch, prime) //基于新素数构造的新的过滤器
	}
}

// 返回生成自然数序列的管道 :2 3 4 ...
func GenerateNature() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}

// 管道过滤器,删除能被素数整除的数
func PrimeFilter(in <-chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := <-in; i%prime != 0 {
				out <- i
			}
		}
	}()
	return out
}

六、context完善同步

package main

import (
	"context"
	"fmt"
	"sync"
)

/*
	标准库增加了一个 context 包,用来简化对于处理单个请求的多个 Goroutine 之间与请求域的数据、超时和退出等操作,
	我们可以用 context 包来重新实现前面的线程安全退出或超时的控制:
*/

func main() {

	//2.context 素数筛
	wg := sync.WaitGroup{}
	//通过context控制后台 goroutine状态
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	ch := generateNaturecontext(ctx, &wg) //自然序数列:2,3,4
	for i := 0; i < 100; i++ {
		prime := <-ch //出现新的素数
		fmt.Printf("%v:%v\n", i+1, prime)
		wg.Add(1)
		ch = primeFiltercontext(ctx, ch, prime, &wg) //基于新素数构造的新过滤器
	}
	cancel()
	wg.Wait()
	//1.我们可以用 context 包来重新实现前面的线程安全退出或超时的控制:
	//ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	//var wg sync.WaitGroup
	//for i := 0; i < 10; i++ {
	//	wg.Add(1)
	//	go workercontext(ctx, &wg)
	//}
	//time.Sleep(time.Second)
	当调用cancel()函数时,它会向done通道发送一个信号,表示相关的上下文已经被取消,并且传入了一个错误值(如果有的话)。
	//cancel()
	//wg.Wait()
}

// 2.context 素数筛
// 返回生成自然数序列的管道: 2, 3, 4, ...
func generateNaturecontext(ctx context.Context, wg *sync.WaitGroup) chan int {
	ch := make(chan int)
	go func() {
		defer wg.Done()
		defer close(ch)
		for i := 2; ; i++ {
			select {
			case <-ctx.Done():
				return
			case ch <- i:
			}
		}
	}()
	return ch

}

// 2.context 素数筛
// 管道过滤器: 删除能被素数整除的数
func primeFiltercontext(ctx context.Context, in <-chan int, prime int, wg *sync.WaitGroup) chan int {
	out := make(chan int)
	go func() {
		defer wg.Done()
		defer close(out)
		for i := range in {
			if i%prime != 0 {
				select {
				case <-ctx.Done():
					return
				case out <- i:
				}
			}
		}
	}()
	return out
}

// 1.我们可以用 context 包来重新实现前面的线程安全退出或超时的控制:
func workercontext(ctx context.Context, wg *sync.WaitGroup) error {
	defer wg.Done()
	for {
		select {
		default:
			fmt.Println("hello")
			//在与该上下文相关的goroutine中,通过检查ctx.Done()通道来捕获取消信号。当cancel()函数被调用后,<-ctx.Done()将会接收到这个信号,
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值