Go:sync의 동시 모드WaitGroup

13969 단어 go

This article was originally an email from my Go newsletter. If you are interested in learning a little bit about Go every week please consider subscribing.


병렬 코드를 처리할 때, 대다수 사람들이 주목하는 첫 번째 일은, 그들의 나머지 코드는 병렬 코드가 완성된 후에 다시 계속되지 않는다는 것이다.예를 들어, 종료하기 전에 몇 개의 서비스에 메시지를 보내려면 다음 코드부터 시작하겠습니다.
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func notify(services ...string) {
    for _, service := range services {
        go func(s string) {
            fmt.Printf("Starting to notifing %s...\n", s)
            time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
            fmt.Printf("Finished notifying %s...\n", s)
        }(service)
    }
    fmt.Println("All services notified!")
}

func main() {
    notify("Service-1", "Service-2", "Service-3")
    // Running this outputs "All services notified!" but we
    // won't see any of the services outputting their finished messages!
}
만약 우리가 이 코드를 실행할 때 약간의 수면을 취해 지연을 시뮬레이션한다면, 우리는 "모든 서비스를 알림!"을 볼 수 있을 것이다.메시지가 출력되었지만 "완료된 알림..."이 없습니다.메시지가 출력됩니다. 이것은 프로그램이 이 메시지가 발송된 후에 꺼지지 않을 것을 나타냅니다.이것은 문제가 될 것이다.
이 문제를 해결하는 방법의 하나는 사용sync.WaitGroup이다.이것은 표준 라이브러리에서 제공하는 유형으로, 쉽게 말할 수 있다. "나는 N 개의 임무를 병렬적으로 실행하고, 그것들이 완성되기를 기다린 후에 나의 코드를 계속해야 한다."sync.WaitGroup를 사용하려면 우리는 대체로 네 가지 일을 해야 한다.
  • 신고sync.WaitGroup
  • WaitGroup 대기열에 추가
  • Wait Group 대기열이 0이 될 때까지 기다린 다음 계속
  • 각 goroutine에서 대기열의 항목을 완성된 것으로 표시
  • 아래의 코드는 이 점을 보여 줍니다. 우리는 당신이 읽은 후에 코드를 토론할 것입니다.
    func notify(services ...string) {
        var wg sync.WaitGroup
    
        for _, service := range services {
            wg.Add(1)
            go func(s string) {
                fmt.Printf("Starting to notifing %s...\n", s)
                time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
                fmt.Printf("Finished notifying %s...\n", s)
                wg.Done()
            }(service)
        }
    
        wg.Wait()
        fmt.Println("All services notified!")
    }
    
    코드의 처음에, 우리는 성명 동기화를 통해 실현했다. (1)그룹을 기다립니다.Gooroutine을 호출하기 전에 이 동작을 실행합니다. 따라서 모든 Goroutine에 사용할 수 있습니다.
    그런 다음 WaitGroup 대기열에 항목을 추가해야 합니다.우리는 호출Add(n)을 통해 이 점을 실현했다. 그 중에서 n는 우리가 대기열에 추가하고자 하는 항목 수이다.이것은 우리가 다섯 개의 임무를 기다려야 한다는 것을 알면 Add(5)를 한 번 호출하거나 이런 상황에서 순환하는 모든 교체 호출Add(1)을 선택할 수 있다는 것을 의미한다.이 두 가지 방법 모두 매우 잘 작동할 수 있으며 위의 코드는 쉽게 다음과 같이 교체할 수 있다.
    wg.Add(len(services))
    for _, service := range services {
      go func(s string) {
        fmt.Printf("Starting to notifing %s...\n", s)
        time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
        fmt.Printf("Finished notifying %s...\n", s)
        wg.Done()
      }(service)
    }
    
    어떤 경우든지, 즉시 실행될 수 있도록 병렬 코드 이외에 Add () 를 호출하는 것을 권장합니다.만약 우리가 이것을goroutine에 넣는다면, 이 프로그램은 작업 그룹에 도착할 수 있을 것이다.goroutine가 실행되기 전에 () 줄을 기다립니다. 이 경우 wg입니다.Wait()는 기다림 없이 이전과 같은 위치에 있을 것입니다.이것은 Go 놀이공원에 표시됩니다: https://play.golang.org/p/Yl4f_5We6s7
    WaitGroup 대기열의 항목을 완료됨으로 표시해야 합니다.이를 위해, 우리는 Done() 을 호출합니다. Add() 와는 달리, 파라미터는 받아들일 수 없습니다. Wait Group 대기열에 많은 항목이 있기 때문에 호출해야 합니다.이것은 goroutine에서 실행되는 코드에 달려 있기 때문에 Done()에 대한 호출은 우리가 기다리고 싶은 goroutine에서 실행되어야 합니다.만약 우리가 for 순환에서 Done() 를 호출하지 않고, 고로틴에서 호출한다면, 고로틴이 실제 실행되기 전에 모든 작업을 완료된 것으로 표시합니다.이것은 Go 놀이공원에 표시됩니다: https://play.golang.org/p/i2vu2vGjYgB
    마지막으로 WaitGroup에서 대기하고 있는 모든 프로젝트가 완료될 때까지 기다려야 합니다.우리는 이 점을 실행하기 위해 호출 Wait() 을 사용합니다. 이로 인해 Wait Group의 대기열이 제거될 때까지 프로그램이 그곳에서 기다릴 것입니다.
    주의해야 할 것은 우리가 고로틴스에서 어떤 결과를 수집하는 것에 관심이 없을 때 이런 모델이 가장 효과적이라는 것이다.만약 우리가 모든goroutine에서 데이터를 되돌려야 하는 상황에 처해 있다는 것을 발견한다면, 통로를 사용하여 이러한 정보를 전달하는 것이 더욱 쉬울 것이다.예를 들어 다음 코드는waitgroup 예시와 매우 비슷하지만, 완성된 후에 모든 goroutine에서 메시지를 받는 채널을 사용합니다.
    func notify(services ...string) {
        res := make(chan string)
        count := 0
    
        for _, service := range services {
            count++
            go func(s string) {
                fmt.Printf("Starting to notifing %s...\n", s)
                time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
                res <- fmt.Sprintf("Finished %s", s)
            }(service)
        }
    
        for i := 0; i < count; i++ {
            fmt.Println(<-res)
        }
    
        fmt.Println("All services notified!")
    }
    
    우리는 왜 채널을 계속 사용하지 않습니까?마지막 예를 바탕으로 우리는 모든 일을 동시에 완성할 수 있다.Wait Group에는 채널이 하나 있는데 왜 새로운 유형을 사용해야 합니까?
    간단한 대답은goroutine에서 되돌아오는 데이터에 관심이 없을 때sync.WaitGroup가 더 잘 알 수 있다는 것이다.그것은 다른 개발자들에게 우리는 단지 하나의 고로틴이 완성되기를 기다리고 싶을 뿐이고, 하나의 통로는 이러한 고로틴의 결과에 대해 흥미를 느끼고 있음을 나타낸다.
    이번엔 이렇게 됐어.미래의 글에서 나는 더 많은 병행 모델을 소개할 것이다. 우리는 이 주제를 계속 확장할 수 있다.만약 공교롭게도 특정한 용례가 있고 그것을 공유하고 싶다면 (또는 그것에 관한 글을 써 달라고 요청하면) send me an email.

    좋은 웹페이지 즐겨찾기