go例程子集上的waitgroup

时间:2018-09-07 18:31:03

标签: multithreading go synchronization

我遇到的情况是,主要的go例程将创建“ x” go例程。但是它只对“ y”(y

我希望使用Waitgroup。但是Waitgroup只允许我等待所有执行例程。例如,我不能这样做,

1. wg.Add (y)
2 create "x" go routines. These routines will call wg.Done() when finished. 
3. wg. Wait()

当y + 1 go例程调用wg.Done()时,由于wg计数器变为负值,这种情况会引起恐慌。

我当然可以使用渠道来解决此问题,但我对Waitgroup是否能解决这个问题很感兴趣。

3 个答案:

答案 0 :(得分:3)

Adrian's answer中所述,sync.WaitGroup是一个简单的计数器,其Wait方法将阻塞直到计数器值达到零为止。旨在允许您在允许执行的主要流程继续之前阻塞(或联接)许多goroutine。

WaitGroup的接口不能充分表达您的用例,也并非旨在如此。特别是,您不能仅仅通过调用wg.Add(y)(其中y th goroutine对wg.Done的调用将cause a panic,因为等待组的内部值为负是错误的。此外,我们不能通过观察WaitGroup的内部计数器值来“聪明”;这样会破坏一个抽象,并且无论如何都不会导出其内部状态。


实施您自己的!

您可以根据下面的代码(playground link)使用一些通道自己实现相关的逻辑。从控制台中观察到启动了10个goroutine,但是在完成两个goroutine之后,我们陷入困境,继续在main方法中执行。

package main

import (
    "fmt"
    "time"
)

// Set goroutine counts here
const (
    // The number of goroutines to spawn
    x = 10
    // The number of goroutines to wait for completion
    // (y <= x) must hold.
    y = 2
)

func doSomeWork() {
    // do something meaningful
    time.Sleep(time.Second)
}

func main() {
    // Accumulator channel, used by each goroutine to signal completion.
    // It is buffered to ensure the [y+1, ..., x) goroutines do not block
    // when sending to the channel, which would cause a leak. It will be
    // garbage collected when all goroutines end and the channel falls
    // out of scope. We receive y values, so only need capacity to receive
    // (x-y) remaining values.
    accChan := make(chan struct{}, x-y)

    // Spawn "x" goroutines
    for i := 0; i < x; i += 1 {
        // Wrap our work function with the local signalling logic
        go func(id int, doneChan chan<- struct{}) {
            fmt.Printf("starting goroutine #%d\n", id)
            doSomeWork()
            fmt.Printf("goroutine #%d completed\n", id)

            // Communicate completion of goroutine
            doneChan <- struct{}{}
        }(i, accChan)
    }

    for doneCount := 0; doneCount < y; doneCount += 1 {
        <-accChan
    }

    // Continue working
    fmt.Println("Carrying on without waiting for more goroutines")
}

避免资源泄漏

由于这不等待[y + 1,...,x)例程完成,因此您应特别注意doSomeWork函数,以消除或最小化工作可能无限期阻塞的风险,这也会导致泄漏。尽可能消除对I / O进行无限阻塞(包括通道操作)或陷入无限循环的可能性。

当不再需要它们的结果来使它们脱离执行时,您可以使用context向其他goroutine发出信号。

答案 1 :(得分:1)

WaitGroup实际上并不等待goroutine,而是等待直到其内部计数器达到零为止。如果您只关心自己所需的goroutine数,并且仅在自己关心的goroutine中调用Add(),那么Done()只会阻塞直到您关心的goroutine完成。您完全控制逻辑和流程,Wait()的“允许”没有任何限制。

答案 2 :(得分:1)

您要跟踪的是这些特定于y的例程,还是x之外的任何y?条件是什么?

更新:

1。如果您可以控制任何选择matching y例程的条件,则:

如果您无法在goroutine之外检查您的条件,则可以根据您的条件从goroutine内部执行wp.wg.Add(1)wp.wg.Done(),方法是将其作为指针参数传递到goroutine中。

类似下面的示例代码。如果您提供要执行的操作的更多详细信息,将会更加具体。

func sampleGoroutine(z int, b string, wg *sync.WaitGroup){

    defer func(){
        if contition1{
            wg.Done()
        }
    }

    if contition1 {
        wg.Add(1)
        //do stuff
    }
}

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < x; i++ {
        go sampleGoroutine(1, "one", &wg)
    }
    wg.Wait()
}

2。如果您无法控制哪一个,只需要first y

根据您的评论,您无法选择任何特定的goroutine,但最先选择的goroutine。如果您想以通用方式进行操作,则可以使用以下适合您的用例的自定义waitGroup实现。 (不过,它不是复制安全的。也不需要wg.Add(int)方法)

type CountedWait struct {
    wait  chan struct{}
    limit int
}

func NewCountedWait(limit int) *CountedWait {
    return &CountedWait{
        wait:  make(chan struct{}, limit),
        limit: limit,
    }
}

func (cwg *CountedWait) Done() {
    cwg.wait <- struct{}{}
}

func (cwg *CountedWait) Wait() {
    count := 0
    for count < cwg.limit {
        <-cwg.wait
        count += 1
    }
}

可用于以下用途:

func sampleGoroutine(z int, b string, wg *CountedWait) {

    success := false

    defer func() {
        if success == true {
            fmt.Printf("goroutine %d finished successfully\n", z)
            wg.Done()
        }
    }()

    fmt.Printf("goroutine %d started\n", z)
    time.Sleep(time.Second)

    if rand.Intn(10)%2 == 0 {
        success = true
    }
}

func main() {
    x := 10
    y := 3
    wg := NewCountedWait(y)

    for i := 0; i < x; i += 1 {
        // Wrap our work function with the local signalling logic
        go sampleGoroutine(i, "something", wg)
    }

    wg.Wait()

    fmt.Printf("%d out of %d goroutines finished successfully.\n", y, x)
}

3。您还可以将{2与context合并,以确保其余goroutine不会泄漏 您可能无法在play.golang上运行此程序,因为它睡眠时间较长。

以下是示例输出: (请注意,可能有超过y = 3个Goroutine标记为Done,但是您只等到3个完成)

goroutine 9 started goroutine 0 started goroutine 1 started goroutine 2 started goroutine 3 started goroutine 4 started goroutine 5 started goroutine 5 marking done goroutine 6 started goroutine 7 started goroutine 7 marking done goroutine 8 started goroutine 3 marking done continuing after 3 out of 10 goroutines finished successfully. goroutine 9 will be killed, bcz cancel goroutine 8 will be killed, bcz cancel goroutine 6 will be killed, bcz cancel goroutine 1 will be killed, bcz cancel goroutine 0 will be killed, bcz cancel goroutine 4 will be killed, bcz cancel goroutine 2 will be killed, bcz cancel

播放链接

  1. https://play.golang.org/p/l5i6X3GClBq
  2. https://play.golang.org/p/Bcns0l9OdFg
  3. https://play.golang.org/p/rkGSLyclgje