समय का उपयोग किए बिना सभी गोरोइटिन के खत्म होने का इंतजार कैसे करें। सो जाएं?


108

यह कोड एक ही फ़ोल्डर में सभी xml फ़ाइलों का चयन करता है, क्योंकि आह्वान निष्पादन योग्य और अतुल्यकालिक कॉलबैक विधि में प्रत्येक परिणाम पर प्रसंस्करण लागू होता है (नीचे उदाहरण में, बस फ़ाइल का नाम प्रिंट आउट है)।

मैं मुख्य विधि से बाहर निकलने के लिए नींद की विधि का उपयोग करने से कैसे बचूँ? मुझे चैनलों के आसपास अपना सिर लपेटने में समस्या है (मुझे लगता है कि यह वही है, जो परिणामों को सिंक्रनाइज़ करने के लिए है) इसलिए किसी भी मदद की सराहना की जाती है!

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) गो रुटीन के बाहर करना होगा? क्या हम इसे defer wg.Done () के ठीक पहले कर सकते हैं?
बैठे

18
बैठ गया, हाँ, इसका एक कारण है, यह सिंक में वर्णित है। WitGroup.Add डॉक्स: 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
इस कोड को अपनाने से मुझे एक लंबा डिबगिंग सत्र शुरू हुआ क्योंकि मेरा गोरआउट एक नामांकित समारोह था और एक मूल्य के रूप में वेटग्रुप में पास होने से यह कॉपी हो जाएगा और wg.Done () अप्रभावी हो जाएगा। जबकि यह एक पॉइंटर और wg पास करके तय किया जा सकता है, इस तरह की त्रुटियों को रोकने के लिए एक बेहतर तरीका यह है कि पहली बार में WaitGroup चर को एक पॉइंटर के रूप में घोषित किया जाए: wg := new(sync.WaitGroup)इसके बजाय var wg sync.WaitGroup
राबर्ट जैक

मुझे लगता है कि यह wg.Add(len(urls))लाइन के ठीक ऊपर लिखने के लिए मान्य है for _, url := range urls, मेरा मानना ​​है कि बेहतर है कि आप केवल एक बार ऐड का उपयोग करें।
विक्टर

@RobertJackWill: अच्छा नोट! BTW, यह डॉक्स में कवर किया गया है : "एक वेटग्रुप को पहले उपयोग के बाद कॉपी नहीं किया जाना चाहिए। बहुत बुरा गो के पास इसे लागू करने का एक तरीका नहीं है । हालांकि, वास्तव में, go vetइस मामले का पता लगाता है और चेतावनी के साथ" फंक पास लॉक द्वारा मूल्य : Sync.WaitGroup में Sync.noCopy शामिल है।
ब्रेंट

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()आप कुछ परिणाम लौटाते हैं, तो आप चैनल पर डाल सकते हैं, और आप लूप के लिए दूसरे में परिणाम इकट्ठा कर सकते हैं और प्रक्रिया कर सकते हैं (जैसे ही वे तैयार हों)
andras

4
यह केवल तभी काम करता है जब आप पहले से ही गोरोतिन की मात्रा जानना चाहते हैं। क्या होगा यदि आप किसी तरह का html क्रॉलर लिख रहे हैं और पृष्ठ पर मौजूद प्रत्येक लिंक के लिए पुनरावर्ती तरीके से गोरूटाइन शुरू करते हैं?
15

आपको इसकी परवाह किए बिना किसी भी तरह का ट्रैक रखने की आवश्यकता होगी। WaitGroups के साथ यह थोड़ा आसान है क्योंकि हर बार जब आप एक नया गोरोइन बिछाते हैं, तो आप सबसे पहले कर सकते हैं wg.Add(1)और इस प्रकार यह उन पर नज़र रखेगा। चैनलों के साथ यह कुछ हद तक कठिन होगा।
22

सी ब्लॉक हो जाएगा क्योंकि सभी गो रूट इसे एक्सेस करने की कोशिश करेंगे, और यह अप्रभावित है
एडविन इकेचुकवु ओकोंकोव

यदि "ब्लॉक," से आपका मतलब है कि कार्यक्रम गतिरोध करेगा, यह सच नहीं है। आप इसे स्वयं चलाने का प्रयास कर सकते हैं। इसका कारण यह है कि लिखने वाले एकमात्र गोरआउट cमुख्य गोरोइनट से भिन्न होते हैं, जो इससे पढ़ता है c। इस प्रकार, मुख्य गोरोइनटाइन हमेशा चैनल से मूल्य पढ़ने के लिए उपलब्ध होता है, जो तब होगा जब चैनल के मान को लिखने के लिए गोरोइटिन में से कोई एक उपलब्ध हो। आप सही कह रहे हैं कि अगर यह कोड गोरोनाइट को नहीं काटता, बल्कि एक ही गोरोइन में सब कुछ चला देता, तो यह गतिरोध हो जाता।
जोशफैल

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अपने समाधान में टूर ऑफ गो - वेब क्रॉलर अभ्यास से बचते हुए चैनलों का उपयोग करके एक समाधान लिखा । हर बार एक या एक से अधिक गो-दिनचर्या शुरू की जाती है, आप childrenचैनल को नंबर भेजते हैं । हर बार जब एक रूटीन पूरा होने वाला होता है, तो आप चैनल 1को भेज देते हैं done। जब बच्चों का योग पूरा होने के योग के बराबर होता है, तो हम किए जाते हैं।

मेरी एकमात्र चिंता resultsचैनल का हार्ड-कोडेड आकार है , लेकिन यह एक (वर्तमान) गो सीमा है।


// 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 को नियोजित करता है।

सबसे पहले, 2 उपयोगिता विधियों को परिभाषित करें:

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()
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.