시간을 사용하지 않고 모든 고 루틴이 끝날 때까지 기다리는 방법.


108

이 코드는 호출 된 실행 파일로 동일한 폴더에있는 모든 xml 파일을 선택하고 콜백 메서드의 각 결과에 비동기 적으로 처리를 적용합니다 (아래 예에서는 파일 이름 만 인쇄 됨).

주 메서드가 종료되지 않도록하려면 sleep 메서드를 사용하지 않으려면 어떻게해야합니까? 채널 주위에 머리를 감는 데 문제가 있으므로 (결과를 동기화하는 데 필요한 것이라고 가정합니다) 도움을 주시면 감사하겠습니다!

package main

import (
    "fmt"
    "io/ioutil"
    "path"
    "path/filepath"
    "os"
    "runtime"
    "time"
)

func eachFile(extension string, callback func(file string)) {
    exeDir := filepath.Dir(os.Args[0])
    files, _ := ioutil.ReadDir(exeDir)
    for _, f := range files {
            fileName := f.Name()
            if extension == path.Ext(fileName) {
                go callback(fileName)
            }
    }
}


func main() {
    maxProcs := runtime.NumCPU()
    runtime.GOMAXPROCS(maxProcs)

    eachFile(".xml", func(fileName string) {
                // Custom logic goes in here
                fmt.Println(fileName)
            })

    // This is what i want to get rid of
    time.Sleep(100 * time.Millisecond)
}

답변:


173

sync.WaitGroup 을 사용할 수 있습니다 . 링크 된 예를 인용하면 :

package main

import (
        "net/http"
        "sync"
)

func main() {
        var wg sync.WaitGroup
        var urls = []string{
                "http://www.golang.org/",
                "http://www.google.com/",
                "http://www.somestupidname.com/",
        }
        for _, url := range urls {
                // Increment the WaitGroup counter.
                wg.Add(1)
                // Launch a goroutine to fetch the URL.
                go func(url string) {
                        // Decrement the counter when the goroutine completes.
                        defer wg.Done()
                        // Fetch the URL.
                        http.Get(url)
                }(url)
        }
        // Wait for all HTTP fetches to complete.
        wg.Wait()
}

11
이동 루틴 외부에서 wg.Add (1)를 수행해야하는 이유는 무엇입니까? wg.Done () 지연 직전에 내부에서 할 수 있습니까?
05

18
sat, 예, 이유가 있습니다. sync.WaitGroup. 문서 추가 : Note that calls with positive delta must happen before the call to Wait, or else Wait may wait for too small a group. Typically this means the calls to Add should execute before the statement creating the goroutine or other event to be waited for. See the WaitGroup example.
wobmene

15
이 코드를 적용하면 고 루틴이 명명 된 함수 였고 WaitGroup을 값으로 전달하면이를 복사하고 wg.Done ()을 비 효과적으로 만들기 때문에 긴 디버깅 세션이 발생했습니다. 포인터 & wg를 전달하여이 문제를 해결할 수 있지만 이러한 오류를 방지하는 더 좋은 방법은 처음에 WaitGroup 변수를 포인터로 선언하는 wg := new(sync.WaitGroup)것입니다 var wg sync.WaitGroup.
로버트 잭 윌

wg.Add(len(urls))줄 바로 위에 쓰는 것이 유효하다고 생각합니다 for _, url := range urls. 한 번만 추가를 사용하면 더 좋습니다.
Victor

@RobertJackWill : 좋은 메모! BTW, 이것은 문서 에서 다룹니다 : "WaitGroup은 처음 사용한 후에 복사해서는 안됩니다. 너무 나쁜 Go는 이것을 시행 할 방법이 없습니다 . 그러나 실제로 go vet는이 경우를 감지하고"func pass lock by value "로 경고합니다. : sync.WaitGroup에 sync.noCopy가 포함되어 있습니다. "
Brent

56

WaitGroups는 확실히이를 수행하는 표준 방법입니다. 하지만 완전성을 위해 WaitGroups가 도입되기 전에 일반적으로 사용 된 솔루션은 다음과 같습니다. 기본 아이디어는 채널을 사용하여 "완료"라고 말하고 생성 된 각 루틴이 완료를보고 할 때까지 메인 고 루틴이 대기하도록하는 것입니다.

func main() {
    c := make(chan struct{}) // We don't need any data to be passed, so use an empty struct
    for i := 0; i < 100; i++ {
        go func() {
            doSomething()
            c <- struct{}{} // signal that the routine has completed
        }()
    }

    // Since we spawned 100 routines, receive 100 messages.
    for i := 0; i < 100; i++ {
        <- c
    }
}

9
일반 채널로 솔루션을 보니 반갑습니다. 추가 보너스 : 경우 doSomething()반환 몇몇 결과, 당신은 채널이를 넣을 수 있습니다, 당신은 (즉시 준비로) 수집 및 루프에 대한 두 번째의 결과를 처리 할 수있는 것보다
안드라스

4
시작하려는 고 루틴의 양을 이미 알고있는 경우에만 작동합니다. 어떤 종류의 HTML 크롤러를 작성하고 페이지의 모든 링크에 대해 반복적 인 방식으로 고 루틴을 시작한다면 어떨까요?
shinydev

어떻게 든 상관없이 이것을 추적해야합니다. WaitGroups를 사용하면 새 고 루틴을 생성 할 때마다 먼저 수행 할 수 wg.Add(1)있으므로이를 추적 하기 때문에 조금 더 쉽습니다 . 채널을 사용하면 다소 어려울 것입니다.
joshlf

C 모든 이동 루틴이 액세스하려고하기 때문에 차단, 그것은 언 버퍼의 뜻
에드윈 Ikechukwu 오콘

"차단"이란 프로그램이 교착 상태가된다는 의미라면 사실이 아닙니다. 직접 실행 해 볼 수 있습니다. 그 이유는 쓰기를하는 유일한 고 루틴이 c에서 읽는 메인 고 루틴과 다르기 때문입니다 c. 따라서 메인 고 루틴은 항상 채널에서 값을 읽을 수 있으며, 고 루틴 중 하나를 사용하여 채널에 값을 쓸 수있을 때 발생합니다. 이 코드가 고 루틴을 생성하지 않고 대신 단일 고 루틴에서 모든 것을 실행하면 교착 상태가된다는 것이 맞습니다.
joshlf

8

sync.WaitGroup 이 여기에서 도움을 드릴 수 있습니다.

package main

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


func wait(seconds int, wg * sync.WaitGroup) {
    defer wg.Done()

    time.Sleep(time.Duration(seconds) * time.Second)
    fmt.Println("Slept ", seconds, " seconds ..")
}


func main() {
    var wg sync.WaitGroup

    for i := 0; i <= 5; i++ {
        wg.Add(1)   
        go wait(i, &wg)
    }
    wg.Wait()
}

1

sync.waitGroup(wg)는 표준 전달 방법 이지만 모두 완료 wg.Add하려면 먼저 호출 중 일부를 수행해야합니다 wg.Wait. 이는 웹 크롤러와 같은 간단한 작업에서는 가능하지 않을 수 있습니다. 웹 크롤러는 미리 재귀 호출 수를 모르고 호출을 유도하는 데이터를 검색하는 데 시간이 걸립니다 wg.Add. 결국 첫 번째 하위 페이지 배치의 크기를 알기 전에 첫 번째 페이지를로드하고 구문 분석해야합니다.

waitGroup내 솔루션에서 Tour of Go-웹 크롤러 연습을 피하면서 채널을 사용하여 솔루션을 작성했습니다 . 하나 이상의 go-routine이 시작될 때마다 번호를 children채널로 보냅니다 . 이동 루틴이 완료 되려고 할 1때마다 done채널에 를 보냅니다 . 자녀의 합이 완료의 합과 같으면 완료된 것입니다.

내 유일한 관심사는 results채널 의 하드 코딩 된 크기 이지만 (현재) Go 제한 사항입니다.


// recursionController is a data structure with three channels to control our Crawl recursion.
// Tried to use sync.waitGroup in a previous version, but I was unhappy with the mandatory sleep.
// The idea is to have three channels, counting the outstanding calls (children), completed calls 
// (done) and results (results).  Once outstanding calls == completed calls we are done (if you are
// sufficiently careful to signal any new children before closing your current one, as you may be the last one).
//
type recursionController struct {
    results  chan string
    children chan int
    done     chan int
}

// instead of instantiating one instance, as we did above, use a more idiomatic Go solution
func NewRecursionController() recursionController {
    // we buffer results to 1000, so we cannot crawl more pages than that.  
    return recursionController{make(chan string, 1000), make(chan int), make(chan int)}
}

// recursionController.Add: convenience function to add children to controller (similar to waitGroup)
func (rc recursionController) Add(children int) {
    rc.children <- children
}

// recursionController.Done: convenience function to remove a child from controller (similar to waitGroup)
func (rc recursionController) Done() {
    rc.done <- 1
}

// recursionController.Wait will wait until all children are done
func (rc recursionController) Wait() {
    fmt.Println("Controller waiting...")
    var children, done int
    for {
        select {
        case childrenDelta := <-rc.children:
            children += childrenDelta
            // fmt.Printf("children found %v total %v\n", childrenDelta, children)
        case <-rc.done:
            done += 1
            // fmt.Println("done found", done)
        default:
            if done > 0 && children == done {
                fmt.Printf("Controller exiting, done = %v, children =  %v\n", done, children)
                close(rc.results)
                return
            }
        }
    }
}

솔루션의 전체 소스 코드


1

다음은 WaitGroup을 사용하는 솔루션입니다.

먼저 두 가지 유틸리티 메서드를 정의합니다.

package util

import (
    "sync"
)

var allNodesWaitGroup sync.WaitGroup

func GoNode(f func()) {
    allNodesWaitGroup.Add(1)
    go func() {
        defer allNodesWaitGroup.Done()
        f()
    }()
}

func WaitForAllNodes() {
    allNodesWaitGroup.Wait()
}

그런 다음 다음 호출을 바꿉니다 callback.

go callback(fileName)

유틸리티 함수를 호출하면 :

util.GoNode(func() { callback(fileName) })

마지막 단계로 . main대신이 줄을 sleep. 이렇게하면 프로그램이 중지되기 전에 주 스레드가 모든 루틴이 완료되기를 기다리고 있는지 확인할 수 있습니다.

func main() {
  // ...
  util.WaitForAllNodes()
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.