如何優雅地等待所有的goroutine退出


goroutine和channel是Go語言非常棒的特色,它們提供了一種非常輕便易用的並發能力。但是當您的應用進程中有很多goroutine的時候,如何在主流程中等待所有的goroutine 退出呢?

1 通過Channel傳遞退出信號

Go的一大設計哲學就是:通過Channel共享數據,而不是通過共享內存共享數據。主流程可以通過channel向任何goroutine發送停止信號,就像下面這樣:

func run(done chan int) {
        for {
                select {
                case <-done:
                        fmt.Println("exiting...")
                        done <- 1
                        break
                default:
                }

                time.Sleep(time.Second * 1)
                fmt.Println("do something")
        }
}

func main() {
        c := make(chan int)

        go run(c)

        fmt.Println("wait")
        time.Sleep(time.Second * 5)

        c <- 1
        <-c

        fmt.Println("main exited")
}

  

這種方式可以實現優雅地停止goroutine,但是當goroutine特別多的時候,這種方式不管在代碼美觀上還是管理上都顯得笨拙不堪。

2 使用waitgroup

sync包中的Waitgroup結構,是Go語言為我們提供的多個goroutine之間同步的好刀。下面是官方文檔對它的描述:

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. 
Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

通常情況下,我們像下面這樣使用waitgroup:

  1. 創建一個Waitgroup的實例,假設此處我們叫它wg
  2. 在每個goroutine啟動的時候,調用wg.Add(1),這個操作可以在goroutine啟動之前調用,也可以在goroutine里面調用。當然,也可以在創建n個goroutine前調用wg.Add(n)
  3. 當每個goroutine完成任務后,調用wg.Done()
  4. 在等待所有goroutine的地方調用wg.Wait(),它在所有執行了wg.Add(1)的goroutine都調用完wg.Done()前阻塞,當所有goroutine都調用完wg.Done()之后它會返回。

那么,如果我們的goroutine是一匹不知疲倦的牛,一直孜孜不倦地工作的話,如何在主流程中告知並等待它退出呢?像下面這樣做:

type Service struct {
        // Other things

        ch        chan bool
        waitGroup *sync.WaitGroup
}

func NewService() *Service {
	s := &Service{
                // Init Other things
                ch:        make(chan bool),
                waitGroup: &sync.WaitGroup{},
	}

	return s
}

func (s *Service) Stop() {
        close(s.ch)
        s.waitGroup.Wait()
}

func (s *Service) Serve() {
        s.waitGroup.Add(1)
        defer s.waitGroup.Done()

        for {
                select {
                case <-s.ch:
                        fmt.Println("stopping...")
                        return
                default:
                }
                s.waitGroup.Add(1)
                go s.anotherServer()
	}
}
func (s *Service) anotherServer() {
        defer s.waitGroup.Done()
        for {
                select {
                case <-s.ch:
                        fmt.Println("stopping...")
                        return
                default:
                }

                // Do something
        }
}

func main() {

        service := NewService()
        go service.Serve()

        // Handle SIGINT and SIGTERM.
        ch := make(chan os.Signal)
        signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
        fmt.Println(<-ch)

        // Stop the service gracefully.
        service.Stop()
}

  

是不是方便優雅多了?

Author: Cobbliu

Created: 2015-04-28 Tue 00:24

Emacs 24.4.1 (Org mode 8.2.10)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM