Zaman kullanmadan tüm gorutinlerin bitmesi nasıl beklenir?


111

Bu kod, çağrılan yürütülebilir dosya olarak aynı klasördeki tüm xml dosyalarını seçer ve geri arama yöntemindeki her sonuca eşzamansız olarak işlem uygular (aşağıdaki örnekte, yalnızca dosyanın adı yazdırılır).

Ana yöntemin çıkmasını önlemek için uyku yöntemini kullanmaktan nasıl kaçınırım? Kafamı kanalların etrafına dolamada sorunlar yaşıyorum (sonuçları senkronize etmek için bunun gerektiğini varsayıyorum) bu yüzden her türlü yardım için minnettarım!

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)
}

Yanıtlar:


175

Sync.WaitGroup'u kullanabilirsiniz . Bağlantılı örnekten alıntı yapmak:

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) 'i go rutininin dışında yapmanız için herhangi bir neden var mı? Erteleme wg.Done () 'dan hemen önce içeride yapabilir miyiz?
doymuş

19
oturdu, evet, bunun bir nedeni var, sync.WaitGroup'ta açıklandı.Doküman ekle: 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

18
Bu kodu uyarlamak bana uzun bir hata ayıklama oturumuna neden oldu çünkü benim gorutinim adlandırılmış bir işlevdi ve WaitGroup'a bir değer olarak geçmek onu kopyalayacak ve wg.Done () 'u etkisiz hale getirecektir. Bu, bir işaretçi & wg iletilerek düzeltilebilirken, bu tür hataları önlemenin daha iyi bir yolu, WaitGroup değişkenini ilk etapta bir işaretçi olarak bildirmektir: wg := new(sync.WaitGroup)yerine var wg sync.WaitGroup.
Robert Jack Will

Sanırım wg.Add(len(urls))satırın hemen üstüne yazmak geçerli for _, url := range urls, ekle'yi yalnızca bir kez kullandığınızda daha iyi olduğuna inanıyorum.
Victor

@RobertJackWill: Güzel not! BTW, bu belgelerde ele alınmıştır : "Bir WaitGroup ilk kullanımdan sonra kopyalanmamalıdır. Çok kötü Go'nun bunu zorlama yolu yoktur . Aslında, go vetbu durumu algılar ve" func passes lock by değere "uyarısı verir : sync.WaitGroup, sync.noCopy içerir ".
Brent Bradburn

57

WaitGroups, kesinlikle bunu yapmanın kanonik yoludur. Sırf bütünlük adına, yine de, WaitGroups tanıtılmadan önce yaygın olarak kullanılan çözüm burada. Temel fikir, "Bittim" demek için bir kanal kullanmak ve ana gorutinin ortaya çıkan her rutinin tamamlandığını bildirene kadar beklemesini sağlamaktır.

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
Düz kanallarla bir çözüm görmek güzel. Ek bir bonus: eğer doSomething()bir sonuç döndürürse, bunu kanala koyabileceğinizden daha fazla ve sonuçları ikinci döngüde toplayıp işleyebilirsiniz (hazır olur olmaz)
andras

5
Yalnızca başlamak istediğiniz gorutin miktarını zaten biliyorsanız çalışır. Ya bir tür html tarayıcısı yazıyorsanız ve sayfadaki her bağlantı için gorutinleri özyinelemeli bir şekilde başlatıyorsanız?
shinydev

Ne olursa olsun bunu bir şekilde takip etmeniz gerekecek. WaitGroups ile biraz daha kolay çünkü her yeni bir gorutin oluşturduğunuzda, önce yapabilirsiniz wg.Add(1)ve böylece onları takip eder. Kanallarla biraz daha zor olurdu.
joshlf

c engelleyecektir çünkü tüm go rutinleri ona erişmeye çalışacaktır ve arabelleğe alınmayacaktır
Edwin Ikechukwu Okonkwo

"Blok" derken, programın kilitleneceğini kastediyorsanız, bu doğru değildir. Kendiniz çalıştırmayı deneyebilirsiniz. Bunun nedeni, yazan tek cgorutinin, okuyan ana gorutinden farklı olmasıdır c. Böylelikle ana gorutin, kanaldan bir değer okumak için her zaman hazırdır, bu, gorutinlerden biri kanala bir değer yazmak için mevcut olduğunda gerçekleşir. Haklısınız, eğer bu kod gorutinler üretmediyse, bunun yerine her şeyi tek bir gorutinde çalıştırırsa, kilitlenecektir.
joshlf

8

sync.WaitGroup burada size yardımcı olabilir.

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

Her ne kadar sync.waitGroup(wg) kanonik yolu ileriye, bu sizin en azından birkaçını yapmak gerektirir wg.Addsenden önce aramalar wg.Waittamamlandı herkes için. Bu, özyinelemeli aramaların sayısını önceden bilmediğiniz ve aramaları yönlendiren verileri almanın biraz zaman aldığı bir web gezgini gibi basit şeyler için uygun olmayabilir wg.Add. Sonuçta, ilk alt sayfa grubunun boyutunu bilmeden önce ilk sayfayı yüklemeniz ve ayrıştırmanız gerekir.

Ben kaçınarak kanallarını kullanarak bir çözüm yazdım waitGroupbenim çözümde web gezgini - Go Tur egzersiz. Bir veya daha fazla go-rutin başlatıldığında, numarayı childrenkanala gönderirsiniz . Bir geçiş rutini tamamlamak üzere olduğu her seferinde 1, donekanala bir gönderirsiniz . Çocukların toplamı yapılanların toplamına eşit olduğunda, bitmiş oluruz.

Geriye kalan tek endişem, resultskanalın kodlanmış boyutu , ancak bu (mevcut) bir Go sınırlaması.


// 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
            }
        }
    }
}

Çözüm için tam kaynak kodu


1

İşte WaitGroup kullanan bir çözüm.

İlk olarak, 2 yardımcı yöntem tanımlayın:

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()
}

Ardından, aşağıdaki çağrıları değiştirin callback:

go callback(fileName)

Yardımcı program işlevinize bir çağrı ile:

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

Son adım, bu satırı sizin mainyerine kendi başınıza ekleyin sleep. Bu, program durmadan önce ana iş parçacığının tüm rutinlerin bitmesini beklemesini sağlayacaktır.

func main() {
  // ...
  util.WaitForAllNodes()
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.