কীভাবে সময় ব্যবহার না করে সমস্ত গরোটিনগুলি শেষ হওয়ার অপেক্ষা করবেন? ঘুম?


108

এই কোডটি একই ফোল্ডারে সমস্ত এক্সএমএল ফাইলগুলি নির্বাচন করে, যেহেতু আহ্বান সম্পাদনযোগ্য এবং অযৌক্তিকভাবে কলব্যাক পদ্ধতিতে প্রতিটি ফলাফলের জন্য প্রক্রিয়াকরণ প্রয়োগ করা হয় (নীচের উদাহরণে কেবল ফাইলটির নাম মুদ্রিত হয়েছে)।

আমি কীভাবে ঘুমানোর পদ্ধতিটি মূল পদ্ধতিটি থেকে বেরিয়ে আসা থেকে বাঁচতে পারি? চ্যানেলগুলির চারপাশে আমার মাথা গুটিয়ে ফেলাতে সমস্যা রয়েছে (ফলাফলগুলি সিঙ্ক্রোনাইজ করার জন্য আমি এটি যা গ্রহণ করি তা অনুমান করি) যাতে কোনও সহায়তা প্রশংসিত হয়!

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

আপনি সিঙ্ক.ওয়েটগ্রুপ ব্যবহার করতে পারেন । লিঙ্কযুক্ত উদাহরণ উদ্ধৃত:

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.Done () এর ঠিক আগে আমরা কী এটি ভিতরে করতে পারি?
বসে

18
বসলেন, হ্যাঁ, এর একটি কারণ রয়েছে, এটি সিঙ্কে বর্ণিত হয়েছে Wউইটগ্রুপ doc ডক্স যুক্ত করুন: 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 := new(sync.WaitGroup)পরিবর্তে var wg sync.WaitGroup
রবার্ট জ্যাক

আমার ধারণা , wg.Add(len(urls))এই লাইনের ঠিক উপরে লেখাটি বৈধ for _, url := range urls, আমি বিশ্বাস করি যে আপনি কেবল একবার অ্যাডটি ব্যবহার করেন তাই ভাল।
ভিক্টর

@ রবার্টজ্যাক উইল: ভাল নোট! বিটিডাব্লু , এটি ডক্সগুলিতে আচ্ছাদিত রয়েছে : "প্রথম ব্যবহারের পরে একটি ওয়েটগ্রুপ অবশ্যই অনুলিপি করা উচিত নয় T খুব খারাপ গো এর প্রয়োগ করার উপায় নেই Act আসলে, তবে এই কেসটি go vetসনাক্ত করে এবং" ফানক লক পাস দিয়ে মান দ্বারা লক করে : sync.WaitGroup এ sync.noCopy রয়েছে। "
ব্রেন্ট ব্র্যাডবার্ন

56

ওয়েটগ্রুপগুলি অবশ্যই এটি করার স্বতন্ত্র উপায়। কেবল সম্পূর্ণতার খাতিরে, যদিও, ওয়েটগ্রুপগুলি প্রবর্তনের আগে সাধারণত এই সমাধানটি ব্যবহার করা হত। মূল ধারণাটি হ'ল "আমি সম্পন্ন হয়েছি" বলার জন্য একটি চ্যানেল ব্যবহার করা এবং প্রতিটি স্পাড রুটিনটির সমাপ্তির রিপোর্ট না হওয়া পর্যন্ত মূল গুরোটিন অপেক্ষা করা।

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
এটি কেবল তখনই কাজ করে যদি আপনি ইতিমধ্যে আপনি যে গুরোটিনগুলি শুরু করতে চান তা জানেন। আপনি যদি কোনও প্রকারের এইচটিএমএল ক্রলার লিখতে থাকেন এবং পৃষ্ঠার প্রতিটি লিঙ্কের জন্য পুনরাবৃত্ত পদ্ধতিতে গুরুটাইনগুলি শুরু করেন?
চকচকে দেবদেব

আপনার নির্বিশেষে কোনওভাবে এটি ট্র্যাক করে রাখতে হবে। ওয়েটগ্রুপগুলির সাহায্যে এটি কিছুটা সহজ কারণ প্রতিবার যখন আপনি একটি নতুন গরোটিন উত্থাপন করেন, আপনি প্রথমে wg.Add(1)এটি করতে পারেন এবং এভাবে সেগুলি তাদের নজর রাখবে। চ্যানেলগুলির সাথে এটি কিছুটা শক্ত হয়ে উঠবে।
জোশল্ফ

সি অবরুদ্ধ হবে যেহেতু সমস্ত গোটা রুটিনগুলি এটি অ্যাক্সেস করার চেষ্টা করবে, এবং এটি অপ্রয়োজনীয়
এডউইন

যদি "ব্লক" দ্বারা বোঝানো হয় তবে প্রোগ্রামটি অচল হয়ে যাবে, এটি সত্য নয়। আপনি নিজে এটি চালানোর চেষ্টা করতে পারেন। কারণটি হ'ল যে গোরোটাইনগুলি কেবল লিখতে পারে cসেগুলি মূল গোরোটিন থেকে আলাদা, যা পড়ে c। সুতরাং, চ্যানেলের বাইরে একটি মান পড়ার জন্য মূল গোরোটিন সর্বদা উপলব্ধ থাকে, যা যখন কোনও গোরোটাইন চ্যানেলে কোনও মান লেখার জন্য পাওয়া যায় তখনই ঘটবে। আপনি ঠিক বলেছেন যে এই কোডটি যদি গরোটিনগুলিকে স্প্যান না করে তবে পরিবর্তে সমস্ত কিছু একক গরোটিনে চালিত করে তবে এটি অচল হয়ে যাবে।
জোশল্ফ

8

সিঙ্ক.ওয়েটগ্রুপ আপনাকে এখানে সহায়তা করতে পারে।

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.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

এখানে একটি সমাধান যা ওয়েটগ্রুপকে নিয়োগ দেয়।

প্রথমে 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()
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.