go waitgroup 使用 cond

 

在Go语言中,sync.WaitGroup是一种用于等待一组协程完成的同步机制。它提供了一种简便的方法来确保多个协程全部执行完毕后再继续执行主程序或其他操作。以下是sync.WaitGroup的基本使用方法:

  1. 创建WaitGroup:首先,你需要创建一个WaitGroup实例。
  2. 添加计数:使用Add方法为WaitGroup添加计数,通常在启动新的协程之前调用。
  3. 在协程中调用Done:每个协程在完成任务时调用Done方法,表示它的工作已经完成。
  4. 等待所有协程完成:使用Wait方法阻塞主协程,直到所有计数器都为零。

下面是一个具体的示例代码,演示了如何使用sync.WaitGroup

go
package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 在协程完成时调用Done fmt.Printf("Worker %d starting\n", id) // 模拟一些工作 time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) // 启动一个新的协程前增加计数 go worker(i, &wg) } wg.Wait() // 等待所有协程完成 fmt.Println("All workers done") }

在这个示例中:

  • 我们创建了一个sync.WaitGroup实例wg
  • 在启动每个worker协程之前,调用wg.Add(1)来增加计数。
  • 每个worker协程完成任务时,调用wg.Done()来减少计数。
  • 主协程调用wg.Wait()来等待所有协程完成。当所有worker都调用了Done并且计数器减到零时,wg.Wait()解除阻塞,主协程继续执行。

这种机制非常适用于需要协调多个并发任务的情况,确保所有任务都完成后再执行后续操作。

package main

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

func worker(id int, wg *sync.WaitGroup) {
        wg.Add(1) // 会崩溃
    defer wg.Done() // 在协程完成时调用Done
    fmt.Printf("Worker %d starting\n", id)

    // 模拟一些工作
    time.Sleep(time.Second)
    
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        // wg.Add(1) // 启动一个新的协程前增加计数
        go worker(i, &wg)
    }

    wg.Wait() // 等待所有协程完成
    fmt.Println("All workers done")
}

 

https://studygolang.com/articles/12972

0x01 介绍

经常会看到以下了代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main(){
for i := 0; i < 100 ; i++{
go fmt.Println(i)
}
time.Sleep(time.Second)
}

主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep() 来睡眠一段时间,等待其他线程充分运行。对于简单的代码,100个for循环可以在1秒之内运行完毕,time.Sleep() 也可以达到想要的效果。

但是对于实际生活的大多数场景来说,1秒是不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep() 来完成等待操作了。

可以考虑使用管道来完成上述操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
c := make(chan bool, 100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
c <- true
}(i)
}

for i := 0; i < 100; i++ {
<-c
}
}

首先可以肯定的是使用管道是能达到我们的目的的,而且不但能达到目的,还能十分完美的达到目的。

但是管道在这里显得有些大材小用,因为它被设计出来不仅仅只是在这里用作简单的同步处理,在这里使用管道实际上是不合适的。而且假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。

对于这种情况,go语言中有一个其他的工具sync.WaitGroup 能更加方便的帮助我们达到这个目的。

WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0。

使用WaitGroup 将上述代码可以修改为:

1
2
3
4
5
6
7
8
9
10
11
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}

这里首先把wg 计数设置为100, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为零——也就是所有的100个for循环都运行完毕。相对于使用管道来说,WaitGroup 轻巧了许多。

0x02 注意事项

1. 计数器不能为负值

我们不能使用Add() 给wg 设置一个负值,否则代码将会报错:

1
2
3
4
5
6
7
panic: sync: negative WaitGroup counter

goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
D:/Go/src/sync/waitgroup.go:75 +0x1d0
main.main()
D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54

同样使用Done() 也要特别注意不要把计数器设置成负数了。

2. WaitGroup对象不是一个引用类型

WaitGroup对象不是一个引用类型,在通过函数传值的时候需要使用地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go f(i, &wg)
}
wg.Wait()
}

// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup) {
fmt.Println(i)
wg.Done()
}

 

chan 实现相同功能

package main

import (
    "fmt"
    "time"
)

func worker(id int, done chan<- bool) {
    fmt.Printf("Worker %d starting\n", id)
    
    // 模拟一些工作
    time.Sleep(time.Second)
    
    fmt.Printf("Worker %d done\n", id)
    // 任务完成后发送信号
    done <- true
}

func main() {
    const numWorkers = 5
    done := make(chan bool, numWorkers)

    for i := 1; i <= numWorkers; i++ {
        go worker(i, done)
    }

    // 等待所有协程完成
    for i := 1; i <= numWorkers; i++ {
        <-done
    }

    fmt.Println("All workers done")
}

 

chan 还可以限制,同一时间,go 处理的数量

 

 

 

cond :

Cond
条件变量用于在满足特定条件之前阻塞或唤醒一个或多个协程。
package main

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

func main() {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    var ready bool

    go func() {
        time.Sleep(time.Second) // 模拟一些工作
        mu.Lock()
        ready = true
        cond.Signal() // 通知一个等待的协程
        mu.Unlock()
    }()

    mu.Lock()
    for !ready {
        cond.Wait() // 阻塞直到条件满足
    }
    fmt.Println("Condition met, continuing...")
    mu.Unlock()
}
package main

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

func main() {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    var ready bool

    go func() {
        time.Sleep(time.Second) // 模拟一些工作
        mu.Lock()
        fmt.Println("i am lock routine")
        ready = true
        cond.Signal() // 通知一个等待的协程
        mu.Unlock()
    }()

    mu.Lock()
    fmt.Println("i am lock")
    for !ready {
        cond.Wait() // 阻塞直到条件满足
    }
    fmt.Println("Condition met, continuing...")
    mu.Unlock()
}


output:
i am lock
i am lock routine
Condition met, continuing...

Program exited.

 

Once

Once确保某些初始化代码只执行一次。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var initValue int

    initialize := func() {
        fmt.Println("Initializing...")
        initValue = 42
    }

    var wg sync.WaitGroup
    const numWorkers = 5

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            once.Do(initialize) // 只执行一次
            fmt.Printf("Worker %d, initValue: %d\n", id, initValue)
        }(i)
    }

    wg.Wait()
}

 

参考:

posted @ 2024-06-06 14:09  redrobot  阅读(4)  评论(0编辑  收藏  举报