有时候我看到的代码中,在 goroutine 的最后没有调用 wg.Done()。

huangapple go评论79阅读模式
英文:

Sometimes I see code where wg.Done() is not being called in the end of the goroutine

问题

我刚开始学习Golang,请谅解并耐心解答我的问题。

根据我理解,wg.Done()的调用是告诉WaitGroup我的goroutine已经完成,这样WaitGroup的等待调用就会结束。让我们考虑一种情况,只有一个goroutine在运行,所以通常我们使用defer关键字来延迟调用wg.Done(),这样它会在goroutine块的末尾被调用。

我遇到了下面的代码片段,其中wg.Done()在中途被调用,我很困惑为什么会在这里调用,而且当我在中途使用wg.Done()时,之后的代码没有被调用。所以考虑到这一点,我对下面的代码中wg.Done()的调用感到困惑。

func startNetworkDaemon() *sync.WaitGroup {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        connPool := warmServiceConnCache()

        server, err := net.Listen("tcp", "localhost:8080")
        if err != nil {
            log.Fatalf("cannot listen: %v", err)
        }
        defer server.Close()

        wg.Done()

        for {
            conn, err := server.Accept()
            if err != nil {
                log.Printf("cannot accept connection: %v", err)
                continue
            }
            svcConn := connPool.Get()
            fmt.Fprintln(conn, "")
            connPool.Put(svcConn)
            conn.Close()
        }
    }()
    return &wg
}

还有另一个代码示例,在下面的代码中,如果给wg.Done()方法添加defer关键字,我会得到一个死锁错误。有人能解释一下原因吗?

func usingBroadcast() {

  beeper := sync.NewCond(&sync.Mutex{})
  var wg sync.WaitGroup
  wg.Add(1)

  miniFunc(func() {
    fmt.Println("mini1")  
    wg.Done() 
  }, beeper) 

  beeper.Broadcast() 
  wg.Wait()
  
}

func miniFunc(fn func(), beeper *sync.Cond) {

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
      wg.Done()
      beeper.L.Lock()
      fmt.Println("i am waiting")
      beeper.Wait()
      beeper.L.Unlock()
      fn()
      
    }()

    wg.Wait()
}
英文:

I am new to the Golang, please bear me and kindly clarify my query.

what I understood from wg.Done() call, this is an indication to WaitGroip that my goroutine is done so that wait call by WaitGroup will end, let's consider a scenario where we have only 1 goroutine running, So generally we use defer keyword for wg.Done() so it will get called at the end of the goroutine block.

I came across below code snippet, where wg.Done() it's being called in the midway, I am confused why it's being called here and also when I practiced code using wg.Done() in the midway, code after that it's not been called, So with that in mind I am confused in the below codebase wg.Done() is being called in the mid-way.

func startNetworkDaemon() *sync.WaitGroup {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        connPool := warmServiceConnCache()

        server, err := net.Listen("tcp", "localhost:8080")
        if err != nil {
            log.Fatalf("cannot listen: %v", err)
        }
        defer server.Close()

        wg.Done()

        for {
            conn, err := server.Accept()
            if err != nil {
                log.Printf("cannot accept connection: %v", err)
                continue
            }
            svcConn := connPool.Get()
            fmt.Fprintln(conn, "")
            connPool.Put(svcConn)
            conn.Close()
        }
    }()
    return &wg
}

Another code sample as well, in the below code if add defer keyword to the wg.Done() method, I am getting a deadlock error. Can someone explain the reason for this...

func usingBroadcast() {

  beeper := sync.NewCond(&sync.Mutex{})
  var wg sync.WaitGroup
  wg.Add(1)

  miniFunc(func() {
    fmt.Println("mini1")  
	wg.Done() 
  }, beeper) 

  beeper.Broadcast() 
  wg.Wait()
  
}

func miniFunc(fn func(), beeper *sync.Cond) {

    var wg sync.WaitGroup
	wg.Add(1)
	go func() {
	  wg.Done()
	  beeper.L.Lock()
	  fmt.Println("i am waiting")
	  beeper.Wait()
	  beeper.L.Unlock()
	  fn()
	  
	}()

	wg.Wait()
}

答案1

得分: 1

一个等待组(waitgroup)会等待所有添加到组中的任务完成。这些任务可以是goroutine,也可以是其他类型的任务。在第一个代码片段中,等待组似乎用于捕获goroutine完成初始化的状态。一旦goroutine调用net.Listen,它就会通知等待的goroutine初始化已完成。

在第二个示例中,存在一个潜在的等待,并且在等待之前通知了等待组。这似乎是一种确保在通知等待组之前goroutine已经开始运行的方案。

英文:

A waitgroup waits until all that are added into the group are done. Those things in the waitgroup can be goroutines, or something else. In the first code snippet, it looks like the waitgroup is there to capture the state where the goroutine completed initialization. Once the goroutine calls the net.Listen, it notifies the waiting goroutine that the initialization is complete.

In the second example, there is a potential wait, and the waitgroup is notified before the wait. It looks like a scheme to make sure that before the waitgroup is notified the goroutine started running.

答案2

得分: 0

等待组在这些示例中是不需要的。

如果你查看startNetworkDaemon的调用者,你会发现它立即在返回的等待组上调用了Wait。调用者基本上是在等待监听器的创建。下面是一个完成相同目标的重构示例:

func startNetworkDaemon() {
    connPool := warmServiceConnCache()

    server, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        log.Fatalf("cannot listen: %v", err)
    }

    go func() {
        defer server.Close()
        for {
            conn, err := server.Accept()
            if err != nil {
                log.Printf("cannot accept connection: %v", err)
                continue
            }
            svcConn := connPool.Get()
            fmt.Fprintln(conn, "")
            connPool.Put(svcConn)
            conn.Close()
        }
    }()
}

因为在miniFunc示例中的goroutine在调用Done之前不做任何事情,等待组没有任何作用。通过移除等待组进行重构:

func miniFunc(fn func(), beeper *sync.Cond) {
    go func() {
        beeper.L.Lock()
        fmt.Println("我在等待")
        beeper.Wait()
        beeper.L.Unlock()
        fn()
    }()
}
英文:

The wait group is not needed in the examples.

If you look at the caller of startNetworkDaemon, you will find that it immediately calls Wait on the returned wait group. The caller is basically waiting on the creation on of the listener. Here's a refactoring that accomplishes the same goal:

func startNetworkDaemon() {
    connPool := warmServiceConnCache()

    server, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        log.Fatalf("cannot listen: %v", err)
    }

    go func() {
        defer server.Close()
        for {
            conn, err := server.Accept()
            if err != nil {
                log.Printf("cannot accept connection: %v", err)
                continue
            }
            svcConn := connPool.Get()
            fmt.Fprintln(conn, "")
            connPool.Put(svcConn)
            conn.Close()
        }
    }()
}

Because goroutine does not do anything before calling Done in the miniFunc example, the wait group serves no purpose. Refactor by removing the wait group.

func miniFunc(fn func(), beeper *sync.Cond) {
    go func() {
      beeper.L.Lock()
      fmt.Println("i am waiting")
      beeper.Wait()
      beeper.L.Unlock()
      fn()
    }()
}

答案3

得分: 0

第二个示例展示了同步操作的错误用法。所示示例 可能会发生死锁(点击几次“[Run]”按钮)。避免死锁的一种方法是将 wg.Wait() 操作移到 miniFunc 中,并且让外部调用者确保 miniFunc() 处于 .Waiting 状态 如此片段所示

func usingBroadcast() {

	beeper := sync.NewCond(&sync.Mutex{})
	var wg sync.WaitGroup
	wg.Add(1)

	miniFunc(func() {
		fmt.Println("mini1")
		wg.Done()
	}, beeper)

	beeper.L.Lock() // 调用 Lock / Unlock 确保 goroutine 处于 '.Wait'ing 状态
	beeper.L.Unlock()

	beeper.Broadcast()
	wg.Wait()

}

func miniFunc(fn func(), beeper *sync.Cond) {

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		beeper.L.Lock()
		wg.Done() // 将 wg.Done() 移到 beeper.L.Lock() 之后
		fmt.Println("i am waiting")
		beeper.Wait()
		beeper.L.Unlock()
		fn()

	}()

	wg.Wait()
}
英文:

(not a direct answer to the question, more of a formatted comment)

The second example shows an incorrect usage of sync operations. the shown example may deadlock (click several times on the [Run] button).

One way to avoid deadlock is to move the wg.Wait() operation in miniFunc, and have a way for the external caller to make sure that miniFunc() is in .Waiting state as in this snippet :

func usingBroadcast() {

	beeper := sync.NewCond(&sync.Mutex{})
	var wg sync.WaitGroup
	wg.Add(1)

	miniFunc(func() {
		fmt.Println("mini1")
		wg.Done()
	}, beeper)

	beeper.L.Lock() // call Lock / Unlock to make sure that the goroutine is in the '.Wait'ing state
	beeper.L.Unlock()

	beeper.Broadcast()
	wg.Wait()

}

func miniFunc(fn func(), beeper *sync.Cond) {

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		beeper.L.Lock()
		wg.Done() // move wg.Done() after beeper.L.Lock()
		fmt.Println("i am waiting")
		beeper.Wait()
		beeper.L.Unlock()
		fn()

	}()

	wg.Wait()
}

huangapple
  • 本文由 发表于 2022年9月12日 02:09:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/73681585.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定