go并发编程-WatiGroup、Cond

前言

我也不是很懂sync.Cond用来干什么的,才学,主要是没用过没见过,学着很懵逼,建议观看大佬
【【Go语言面试知识点】 Goroutine - sync.Cond 从社区入手,了解高并发下的单播/多播实现】https://www.bilibili.com/video/BV1f64y1F72X?vd_source=8e14beefe0703b37528893bf5b340f80

sync.WaitGroup

入门

sync.WaitGroup 是 Go 语言标准库中的一个并发工具,用于管理一组 goroutine 的同步。它提供了三个主要的方法:Add、Done 和 Wait。

  • Add 方法用于添加需要等待的 goroutine 的数量,它接受一个整数参数表示要等待的 goroutine 的数量。
  • Done 方法用于表示一个 goroutine 已经执行完毕,它将等待的数量减去 1。
  • Wait 方法会阻塞当前的 goroutine,直到等待的数量为 0。
    WaitGroup 的主要作用是确保在一组 goroutine 执行完之前,当前的 goroutine 不会退出或继续执行后续的代码。

通过正确使用 Add、Done 和 Wait 方法,可以实现并发任务的同步,等待所有的任务完成后再进行后续的处理。

package _case

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

func WaitGroupCase() {
	var a, b = 1000, 1000
	start := time.Now()
	for i := 0; i < 500000000; i++ {
		multi(a, b)
	}
	t := time.Since(start) //计算耗时时间
	fmt.Println(t)

	start = time.Now()
	wg := sync.WaitGroup{}
	for i := 0; i < 30; i++ {
		wg.Add(1)   //计数器加1
		go func() { //采用闭包的方式
			defer wg.Done() //计数器-1
			for j := 0; j < 500000000; j++ {
				multi(a, b)
			}
		}()
	}
	wg.Wait() //等待协程
	t = time.Since(start)
	fmt.Println("多协程", t)
}

func multi(a, b int) int {
	return a * b
}

作用

  1. 等待一组协程完成
  2. 工作原理: 通过计数器来或偶去协程的完成情况
  3. 启动一个协程计数器 +1 协程退出时计数器 -1
  4. 通过wait方法阻塞主协程, 等待计数器清零后才能执行后续操作

陷阱

  1. 协程间传递时需要以指针的方式或闭包的方式引用 WaitGroup 对象,否则会造成死锁

应用场景

  1. 通过协程并行执行一组任务, 且任务全部完成后才能进行下一步操作

sync.Cond

入门

sync.Cond 是 Go 语言标准库中的一个条件变量类型,用于在多个 goroutine 之间进行同步和通信。

条件变量通过等待和通知机制来协调 goroutine 之间的操作。它提供了以下几个方法:

  • Wait 方法会使当前的 goroutine 进入等待状态,并且释放相应的锁。当其他 goroutine 调用条件变量的 Signal 或 Broadcast 方法时,当前的 goroutine 会重新获得锁并继续执行。
  • Signal 方法会通知一个等待在条件变量上的 goroutine,使其从等待状态变为运行状态。
  • Broadcast 方法会通知所有等待在条件变量上的 goroutine,使它们从等待状态变为运行状态。

Wait 方法和 Signal、Broadcast 方法都需要在互斥锁的保护下进行调用。
条件变量主要用于在多个 goroutine 之间进行线程间同步和通信。它常用于解决生产者-消费者模型、任务调度和其他需要协调多个 goroutine 的场景。通过使用条件变量,我们可以更灵活地控制和同步 goroutine 的执行顺序和操作。

需要注意的是,使用条件变量时应避免死锁和竞态条件,确保互斥锁的正确使用以及对条件变量的正确等待和通知操作。

package _case

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

func CondCase() {
	list := make([]int, 0)
	//sync.NewCond(&sync.Mutex{}) 是用于创建一个条件变量(sync.Cond)
	//需要和互斥锁(sync.Mutex)配合使用
	cood := sync.NewCond(&sync.Mutex{})
	go readList(&list, cood)
	go readList(&list, cood)
	go readList(&list, cood)
	time.Sleep(time.Second * 10)
	initList(&list, cood)

}

func initList(list *[]int, c *sync.Cond) {
	//主叫方, 可以持有锁,也可以不持锁
	c.L.Lock() //持锁
	defer c.L.Unlock()
	for i := 0; i < 10; i++ {
		*list = append(*list, i)
	}

	//唤醒所有条件等待的协程
	c.Broadcast() //全部打印
}

func readList(list *[]int, c *sync.Cond) {
	//被叫方,必须持锁
	c.L.Lock()
	defer c.L.Unlock()
	for len(*list) == 0 {
		fmt.Printf("readList wait")
		c.Wait()
	}
	fmt.Println("list: ", list)
}

type queue struct {
	list []int
	cond *sync.Cond
}

func newQueue() *queue {
	q := &queue{
		list: []int{},
		cond: sync.NewCond(&sync.Mutex{}),
	}
	return q
}

func (q *queue) Put(item int) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	q.list = append(q.list, item)
	//当写入数据成功,唤醒一个协程来处理数据
	q.cond.Signal() //随机打印一个
}

func (q *queue) GetMany(n int) []int {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	for len(q.list) < n {
		q.cond.Wait()
	}
	list := q.list[:n]
	q.list = q.list[n:]
	return list
}

func CondQueueCase() {
	q := newQueue()
	var wg sync.WaitGroup
	for n := 0; n < 10; n++ {
		wg.Add(1)
		go func(n int) {
			list := q.GetMany(n)
			fmt.Printf("%d: %d \n", n, list)
		}(n)
	}
}

作用

  1. 设置一组协程根据条件阻塞, 可以根据不同的条件阻塞
  2. 可以根据条件唤醒相应的协程

注意事项

  1. 被叫方必须持有锁
  2. 主叫方可以持有锁,但是允许不持有
  3. 尽可能减少无效唤醒
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值