एक चैनल पर एक से अधिक गोरोतेन्स सुनना


84

मेरे पास एक ही चैनल पर एक से अधिक goroutines प्राप्त करने का प्रयास है। ऐसा लगता है कि चैनल पर प्राप्त होने वाले अंतिम गोरोइन का मूल्य प्राप्त होता है। क्या यह भाषा की कल्पना में कहीं है या यह अपरिभाषित व्यवहार है?

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

आउटपुट:

goroutine 4

खेल के मैदान पर उदाहरण

संपादित करें:

मुझे बस एहसास हुआ कि यह जितना मैंने सोचा था उससे कहीं अधिक जटिल है। संदेश सभी गोरोइट्स के आस-पास से गुजरता है।

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

आउटपुट:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

खेल के मैदान पर उदाहरण


6
मैंने आपका आखिरी स्निपेट और (मेरी बड़ी राहत के लिए) इसे केवल आउटपुट किया original, hi from 4...
चांग कियान

1
@ चेंगक्यूयन time.Sleep(time.Millisecond)चैनल भेजने और प्राप्त करने के बीच जोड़ने से पुराना व्यवहार वापस आ जाता है।
इलिया चोली

जवाबों:


78

हां, यह जटिल है, लेकिन अंगूठे के नियमों के एक जोड़े हैं जो चीजों को बहुत अधिक सीधा महसूस करना चाहिए।

  • उन चैनलों के लिए औपचारिक तर्कों का उपयोग करना पसंद करें, जिन्हें आप वैश्विक दायरे में चैनलों तक पहुँचने के बजाय गो-रूटीन में पास करते हैं। आप इस तरह से अधिक संकलक जाँच कर सकते हैं, और बेहतर प्रतिरूपकता भी।
  • एक विशेष गो-रुटीन ('मुख्य' एक सहित) में एक ही चैनल पर पढ़ना और लिखना दोनों से बचें । अन्यथा, गतिरोध एक बहुत बड़ा जोखिम है।

इन दो दिशानिर्देशों को लागू करते हुए, आपके कार्यक्रम का एक वैकल्पिक संस्करण यहां दिया गया है। यह मामला एक चैनल पर कई लेखकों और एक पाठक को प्रदर्शित करता है:

c := make(chan string)

for i := 1; i <= 5; i++ {
    go func(i int, co chan<- string) {
        for j := 1; j <= 5; j++ {
            co <- fmt.Sprintf("hi from %d.%d", i, j)
        }
    }(i, c)
}

for i := 1; i <= 25; i++ {
    fmt.Println(<-c)
}

http://play.golang.org/p/quQn7xePLw

यह एक ही चैनल पर पांच गो-राईटिंग लिखता है, हर एक पाँच बार लिखता है। मुख्य गो-रुटीन सभी पच्चीस संदेशों को पढ़ता है - आप देख सकते हैं कि वे जिस क्रम में दिखाई देते हैं वह अक्सर अनुक्रमिक नहीं होता है (अर्थात सुस्पष्ट स्पष्ट है)।

यह उदाहरण गो चैनलों की एक विशेषता को प्रदर्शित करता है: कई लेखकों को एक चैनल साझा करना संभव है; गो स्वचालित रूप से संदेशों को इंटरलेव करेगा।

एक चैनल पर एक लेखक और कई पाठकों के लिए एक ही लागू होता है, जैसा कि दूसरे उदाहरण में देखा गया है:

c := make(chan int)
var w sync.WaitGroup
w.Add(5)

for i := 1; i <= 5; i++ {
    go func(i int, ci <-chan int) {
        j := 1
        for v := range ci {
            time.Sleep(time.Millisecond)
            fmt.Printf("%d.%d got %d\n", i, j, v)
            j += 1
        }
        w.Done()
    }(i, c)
}

for i := 1; i <= 25; i++ {
    c <- i
}
close(c)
w.Wait()

इस दूसरे उदाहरण में मुख्य गोरोइन पर लगाया गया एक प्रतीक्षा शामिल है, जो अन्यथा शीघ्रता से बाहर निकल जाएगी और अन्य पांच गोरोइनों को जल्दी समाप्त करने का कारण बनेगी ( इस सुधार के लिए ओलोव के लिए धन्यवाद )

दोनों उदाहरणों में, किसी भी बफरिंग की आवश्यकता नहीं थी। आमतौर पर केवल प्रदर्शन बढ़ाने वाले के रूप में बफरिंग को देखना एक अच्छा सिद्धांत है। यदि आपका कार्यक्रम बफ़र्स के बिना गतिरोध नहीं करता है , तो यह बफ़र्स के साथ गतिरोध नहीं करेगा (लेकिन इसका रूपांतरण हमेशा सच नहीं होता है)। इसलिए, अंगूठे के एक और नियम के रूप में, बिना बफरिंग शुरू करें, फिर बाद में आवश्यकतानुसार जोड़ें


क्या आपको सभी गोरोइनटिस के लिए इंतजार करने की ज़रूरत नहीं है?
माइलब्राइट

यह निर्भर करता है कि आपका क्या मतलब है। Play.golang.org उदाहरणों पर एक नज़र डालें; उनके पास एक ऐसा mainकार्य है जो एक बार समाप्त होने पर समाप्त हो जाता है, फिर चाहे वह किसी भी अन्य गोरोइन्ट्स का हो। ऊपर दिए गए पहले उदाहरण में, mainअन्य गोरआउट के साथ लॉक-स्टेप है ताकि कोई समस्या न हो। क्योंकि सभी संदेशों के माध्यम से भेजा जाता है दूसरे उदाहरण भी समस्या के बिना काम करता c से पहलेclose समारोह कहा जाता है और ऐसा होता है से पहलेmain goroutine समाप्त। (आप तर्क दे सकते हैं कि कॉलिंग closeमामले में शानदार है, लेकिन यह अच्छा अभ्यास है।)
रिक -777

1
यह मानते हुए कि आप (निर्धारक रूप से) अंतिम उदाहरण में 15 प्रिंटआउट देखना चाहते हैं, आपको प्रतीक्षा करने की आवश्यकता है। यह प्रदर्शित करने के लिए, यहाँ एक ही उदाहरण है, लेकिन एक समय के साथ। प्रिंट के ठीक पहले सो जाएं
olov

और यहाँ एक समय के साथ एक ही उदाहरण है। सो जाओ और गोरटाइन्स के लिए प्रतीक्षा करने के लिए एक WaitGroup के साथ तय किया: play.golang.org/p/ESq9he_WzS
olov

मुझे नहीं लगता कि पहली बार में बफरिंग को छोड़ना एक अच्छी सिफारिश है। बफरिंग के बिना, आप वास्तव में समवर्ती कोड नहीं लिखते हैं, और यह न केवल उस गतिरोध की ओर जाता है, बल्कि यह भी है कि चैनल के दूसरी तरफ से हैंडलिंग परिणाम भेजने के बाद अगले निर्देश पर पहले से ही उपलब्ध है, आप अनजाने में (या किसी नौसिखिए के मामले में जानबूझकर घटना) उस पर भरोसा कर सकते हैं। और एक बार जब आप इस तथ्य पर भरोसा करते हैं कि आपके पास एक परिणाम है, तो इसके लिए विशेष रूप से इंतजार किए बिना, और आप एक बफर जोड़ते हैं, आपके पास एक दौड़ की स्थिति है।
यूजर

25

देर से जवाब, लेकिन मुझे उम्मीद है कि यह भविष्य में दूसरों को लंबे मतदान, "ग्लोबल" बटन की तरह सभी को प्रसारित करने में मदद करता है?

प्रभावी गो समस्या बताते हैं:

जब तक डेटा प्राप्त नहीं होता है तब तक रिसीवर हमेशा अवरुद्ध होता है।

इसका मतलब है कि आपके पास 1 चैनल को सुनने वाले 1 से अधिक गोरोइन नहीं हो सकते हैं और सभी गोरोइनों को समान मूल्य प्राप्त होने की उम्मीद है।

इस कोड उदाहरण को चलाएँ ।

package main

import "fmt"

func main() {
    c := make(chan int)

    for i := 1; i <= 5; i++ {
        go func(i int) {
        for v := range c {
                fmt.Printf("count %d from goroutine #%d\n", v, i)
            }
        }(i)
    }

    for i := 1; i <= 25; i++ {
        c<-i
    }

    close(c)
}

आपको चैनल पर सुनने के लिए 5 से अधिक गोरोनाइट होने के बावजूद एक बार से अधिक "काउंट 1" नहीं दिखाई देगा। ऐसा तब होता है क्योंकि जब पहला गोरआउट चैनल को ब्लॉक करता है तो अन्य सभी गोरआउट को लाइन में इंतजार करना होगा। जब चैनल को अनब्लॉक किया जाता है, तो काउंट पहले ही प्राप्त हो चुका होता है और चैनल से हटा दिया जाता है, इसलिए लाइन में अगली गोरोइन को अगला काउंट मान प्राप्त होता है।


1
धन्यवाद - अब यह उदाहरण समझ में आता है github.com/goinaction/code/blob/master/chapter6/listing20/…
user31208

आह यह मददगार था। क्या प्रत्येक गो रुटीन के लिए एक चैनल बनाना एक अच्छा विकल्प हो सकता है जिसे जानकारी की आवश्यकता है, फिर आवश्यक होने पर सभी चैनलों पर संदेश भेजें? वह विकल्प है जिसकी मैं कल्पना कर सकता हूं।
thePartyTurtle

9

यह जटिल है।

इसके अलावा, देखें कि क्या होता है GOMAXPROCS = NumCPU+1। उदाहरण के लिए,

package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU() + 1)
    fmt.Print(runtime.GOMAXPROCS(0))
    c := make(chan string)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- ", original"
    fmt.Println(<-c)
}

आउटपुट:

5, original, hi from 4

और, देखें कि बफ़र्ड चैनलों के साथ क्या होता है। उदाहरण के लिए,

package main

import "fmt"

func main() {
    c := make(chan string, 5+1)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- "original"
    fmt.Println(<-c)
}

आउटपुट:

original

आपको इन मामलों की व्याख्या करने में सक्षम होना चाहिए।


7

मैंने मौजूदा समाधानों का अध्ययन किया है और सरल प्रसारण पुस्तकालय https://github.com/grafov/bcast बनाया है ।

    group := bcast.NewGroup() // you created the broadcast group
    go bcast.Broadcasting(0) // the group accepts messages and broadcast it to all members

    member := group.Join() // then you join member(s) from other goroutine(s)
    member.Send("test message") // or send messages of any type to the group 

    member1 := group.Join() // then you join member(s) from other goroutine(s)
    val := member1.Recv() // and for example listen for messages

2
बहुत अच्छा आप वहाँ है! मैंने github.com/asaskevich/EventBus
user

और एक बड़ी बात नहीं है, लेकिन शायद आपको उल्लेख करना चाहिए कि रेडमी में कैसे आगे बढ़ना है।
उपयोगकर्ता

मेमोरी लीक वहाँ
jhvaras

:( क्या आप विवरणों की व्याख्या कर सकते हैं @jhvaras?
अलेक्जेंडर I.Grafov

2

एक चैनल पर कई गोरोइन सुनने के लिए, हाँ, यह संभव है। मुख्य बिंदु स्वयं संदेश है, आप कुछ संदेश को इस तरह परिभाषित कर सकते हैं:

package main

import (
    "fmt"
    "sync"
)

type obj struct {
    msg string
    receiver int
}

func main() {
    ch := make(chan *obj) // both block or non-block are ok
    var wg sync.WaitGroup
    receiver := 25 // specify receiver count

    sender := func() {
        o := &obj {
            msg: "hello everyone!",
            receiver: receiver,
        }
        ch <- o
    }
    recv := func(idx int) {
        defer wg.Done()
        o := <-ch
        fmt.Printf("%d received at %d\n", idx, o.receiver)
        o.receiver--
        if o.receiver > 0 {
            ch <- o // forward to others
        } else {
            fmt.Printf("last receiver: %d\n", idx)
        }
    }

    go sender()
    for i:=0; i<reciever; i++ {
        wg.Add(1)
        go recv(i)
    }

    wg.Wait()
}

उत्पादन यादृच्छिक है:

5 received at 25
24 received at 24
6 received at 23
7 received at 22
8 received at 21
9 received at 20
10 received at 19
11 received at 18
12 received at 17
13 received at 16
14 received at 15
15 received at 14
16 received at 13
17 received at 12
18 received at 11
19 received at 10
20 received at 9
21 received at 8
22 received at 7
23 received at 6
2 received at 5
0 received at 4
1 received at 3
3 received at 2
4 received at 1
last receiver 4
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.