এন চ্যানেল শুনতে কিভাবে? (গতিশীল নির্বাচন বিবৃতি)


116

দুটি গোরোটাইন কার্যকর করার একটি অন্তহীন লুপ শুরু করতে, আমি নীচের কোডটি ব্যবহার করতে পারি:

এই ছবিটি পাওয়ার পরে এটি একটি নতুন গোরটাইন শুরু করবে এবং চিরকাল চলবে।

c1 := make(chan string)
c2 := make(chan string)

go DoStuff(c1, 5)
go DoStuff(c2, 2)

for ; true;  {
    select {
    case msg1 := <-c1:
        fmt.Println("received ", msg1)
        go DoStuff(c1, 1)
    case msg2 := <-c2:
        fmt.Println("received ", msg2)
        go DoStuff(c2, 9)
    }
}

আমি এখন এন গোরোটাইনগুলির জন্য একই আচরণ করতে চাই, তবে নির্বাচিত বিবৃতিটি কীভাবে এ ক্ষেত্রে দেখবে?

এই কোড বিটটি দিয়েই আমি শুরু করেছি, তবে নির্বাচিত বিবৃতিটি কীভাবে কোড করা যায় তা নিয়ে আমি বিভ্রান্ত

numChans := 2

//I keep the channels in this slice, and want to "loop" over them in the select statemnt
var chans = [] chan string{}

for i:=0;i<numChans;i++{
    tmp := make(chan string);
    chans = append(chans, tmp);
    go DoStuff(tmp, i + 1)

//How shall the select statment be coded for this case?  
for ; true;  {
    select {
    case msg1 := <-c1:
        fmt.Println("received ", msg1)
        go DoStuff(c1, 1)
    case msg2 := <-c2:
        fmt.Println("received ", msg2)
        go DoStuff(c2, 9)
    }
}

4
আমি মনে করি আপনি চ্যানেল মাল্টিপ্লেক্সিংটি যা চাইছেন তা। golang.org/doc/effective_go.html#chan_of_chan মূলত, আপনার একক চ্যানেল শোনা যায় এবং তারপরে মূল চ্যানেলে একাধিক শিশু চ্যানেল থাকে। সম্পর্কিত এসও প্রশ্ন: স্ট্যাকওভারফ্লো.com
ব্রেন্ডেন

উত্তর:


152

আপনি প্রতিচ্ছবি প্যাকেজ Selectথেকে ফাংশনটি ব্যবহার করে এটি করতে পারেন :

func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)

সিলেক্ট কেসগুলির তালিকা দ্বারা বর্ণিত একটি নির্বাচন অপারেশন কার্যকর করে। গো সিলেক্ট স্টেটমেন্টের মতো এটি কমপক্ষে একটি ক্ষেত্রে এগিয়ে যাওয়া অবধি অবরুদ্ধ করে, অভিন্ন সিউডো-এলোমেলো পছন্দ করে, এবং তারপরে সেই কেসটি কার্যকর করে। এটি নির্বাচিত কেসটির সূচক ফেরত দেয় এবং যদি সেই কেসটি কোনও প্রাপ্তি অপারেশন হয় তবে প্রাপ্ত মান এবং একটি বুলিয়ান ইঙ্গিত দেয় যে মানটি চ্যানেলের একটি প্রেরণের সাথে মিল রয়েছে কিনা (চ্যানেলটি বন্ধ থাকায় শূন্য মানের প্রাপ্ত হিসাবে বিপরীতে)।

আপনি SelectCaseস্ট্রাইকগুলির একটি অ্যারে পাস করেন যা চ্যানেলটি বেছে নেওয়ার জন্য চিহ্নিত করে, অপারেশনের দিকনির্দেশনা এবং প্রেরণ ক্রিয়াকলাপের ক্ষেত্রে প্রেরণের মান।

সুতরাং আপনি এই মত কিছু করতে পারে:

cases := make([]reflect.SelectCase, len(chans))
for i, ch := range chans {
    cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)}
}
chosen, value, ok := reflect.Select(cases)
// ok will be true if the channel has not been closed.
ch := chans[chosen]
msg := value.String()

আপনি এখানে আরও উদ্বেগিত উদাহরণ সহ পরীক্ষা করতে পারেন: http://play.golang.org/p/8zwvSk4kjx


4
এই জাতীয় নির্বাচনের ক্ষেত্রে সংখ্যার কি ব্যবহারিক সীমা রয়েছে? যেটি যদি আপনি এর বাইরে চলে যান, তবে অভিনয়টি মারাত্মকভাবে প্রভাবিত হবে?
ম্যাক্সিম ভ্লাদিমিরস্কি

4
হতে পারে এটি আমার অদক্ষতা, তবে আপনি যখন চ্যানেলটির মাধ্যমে জটিল কাঠামো প্রেরণ করছেন এবং গ্রহণ করছেন তখন এই প্যাটার্নটি কাজ করা সত্যিই কঠিন। টিম অলক্লেয়ার যেমনটি বলেছিলেন, তেমন একটি ভাগ করা "সমষ্টি" চ্যানেল পাস করা আমার ক্ষেত্রে অনেক সহজ ছিল।
বোরা এম। আল্পার

90

আপনি প্রতিটি চ্যানেলটিকে জোরআউটিনে মুড়ে দিয়ে এটি সম্পাদন করতে পারেন যা একটি ভাগ করা "সমষ্টি" চ্যানেলে বার্তাগুলিকে "ফরোয়ার্ড" করে। উদাহরণ স্বরূপ:

agg := make(chan string)
for _, ch := range chans {
  go func(c chan string) {
    for msg := range c {
      agg <- msg
    }
  }(ch)
}

select {
case msg <- agg:
    fmt.Println("received ", msg)
}

আপনার যদি বার্তাটি কোন চ্যানেল থেকে উদ্ভূত হয়েছে তা জানতে হলে, এটি সামগ্রিক চ্যানেলে ফরোয়ার্ড করার আগে আপনি কোনও অতিরিক্ত তথ্য দিয়ে স্ট্রাক্টে এটি মোড়ানো করতে পারেন।

আমার (সীমাবদ্ধ) পরীক্ষায়, এই পদ্ধতিটি প্রতিফলিত প্যাকেজটি ব্যবহার করে ব্যাপকভাবে সম্পাদন করে:

$ go test dynamic_select_test.go -test.bench=.
...
BenchmarkReflectSelect         1    5265109013 ns/op
BenchmarkGoSelect             20      81911344 ns/op
ok      command-line-arguments  9.463s

বেঞ্চমার্ক কোড এখানে


2
আপনার বেঞ্চমার্ক কোডটি ভুল, আপনাকে একটি বেঞ্চমার্কের মধ্যে লুপb.N করতে হবে । অন্যথায় ফলাফল (যা b.Nআপনার আউটপুটে 1 এবং 2000000000 দ্বারা বিভক্ত ) সম্পূর্ণ অর্থহীন হবে।
ডেভ সি 16

2
@ ডেভিসি আপনাকে ধন্যবাদ! উপসংহার পরিবর্তন হয় না, তবে ফলাফলগুলি আরও বুদ্ধিমান।
টিম অ্যালক্লেয়ার

1
প্রকৃতপক্ষে, কিছু আসল নম্বর পেতে আমি আপনার বেঞ্চমার্ক কোডটিতে একটি দ্রুত হ্যাক করেছি । এই বেঞ্চমার্ক থেকে এখনও খুব ভাল কিছু অনুপস্থিত / ভুল থাকতে পারে তবে আরও জটিল প্রতিবিম্বের কোডটি কেবল এটির জন্য চলেছে সেটি হ'ল সেটআপটি দ্রুত (GOMAXPROCS = 1 সহ) গৌরোটিনগুলির একগুচ্ছ প্রয়োজন হয় না বলে। অন্য প্রতিটি ক্ষেত্রে একটি সরল গোরোটিন মার্জ করা চ্যানেল প্রতিফলন সমাধানটি দূরে সরিয়ে দেয় (মাত্রার 2 ডলার অর্ডার দিয়ে)।
ডেভ সি

2
একটি গুরুত্বপূর্ণ নেতিবাচক দিক ( reflect.Selectপদ্ধতির তুলনায় ) হ'ল যে গোরোটাইনগুলি মার্জ করা বাফার করছে প্রতিটি চ্যানেলে ন্যূনতম একক মান একীকরণ করা হচ্ছে। সাধারণত এটি কোনও সমস্যা হবে না তবে কিছু নির্দিষ্ট অ্যাপ্লিকেশনগুলিতে ডিল ব্রেকার হতে পারে :(।
ডেভ সি

1
একটি বাফার্ড মার্জ চ্যানেল সমস্যাটিকে আরও খারাপ করে তোলে। বিষয়টি হ'ল কেবল প্রতিফলন সমাধানে সম্পূর্ণরূপে আন-বাফার শব্দার্থবিজ্ঞান থাকতে পারে। আমি এগিয়ে গিয়ে পরীক্ষার কোডটি পোস্ট করেছিলাম যার সাথে আমি পৃথক উত্তর হিসাবে পরীক্ষিত ছিলাম (আশাবাদী) আমি কী বলতে চাইছিলাম তা স্পষ্ট করে দিন।
ডেভ সি

22

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

পদ্ধতির মধ্যে তিনটি প্রধান পার্থক্য রয়েছে:

  • জটিলতা. যদিও এটি আংশিকভাবে পাঠকের পছন্দ হতে পারে তবে আমি চ্যানেলটির কাছে আরও বুদ্ধিমান, সোজা-এগিয়ে এবং পাঠযোগ্য read

  • কর্মক্ষমতা. আমার জিওন এমডি 64 সিস্টেমে গোরআটাইনস + চ্যানেলগুলি প্রায় দুই অর্ধ মাত্রার দ্বারা প্রতিফলন সমাধান সম্পাদন করে (গোয়ে সাধারণত প্রতিচ্ছবি প্রায়শই ধীর হয় এবং যখন একেবারে প্রয়োজন হয় তখনই ব্যবহার করা উচিত)। অবশ্যই, ফলাফলগুলি প্রক্রিয়াকরণ বা ইনপুট চ্যানেলগুলিতে মান লেখার ক্ষেত্রে যদি কোনও উল্লেখযোগ্য বিলম্ব হয় তবে এই পারফরম্যান্সের পার্থক্যটি সহজেই তুচ্ছ হয়ে উঠতে পারে।

  • অবরুদ্ধকরণ / বাফারিং শব্দার্থবিজ্ঞান। এর গুরুত্বটি ব্যবহারের ক্ষেত্রে নির্ভর করে। বেশিরভাগ ক্ষেত্রে তা হয় আসে না বা গোরোটিন মার্জিং সলিউশনে সামান্য অতিরিক্ত বাফারিং থ্রুপুট জন্য সহায়ক হতে পারে। যাইহোক, যদি শব্দার্থবিজ্ঞানের কথাটি আকাঙ্ক্ষিত হয় যে কেবলমাত্র একজন একক লেখককে অবরুদ্ধ করা হয়েছে এবং অন্য কোনও লেখককে অবরুদ্ধ করার আগে এটির মান পুরোপুরি পরিচালিত হয় তবে তা কেবল প্রতিচ্ছবি সমাধানের মাধ্যমেই অর্জন করা যায়।

দ্রষ্টব্য, প্রেরণ চ্যানেলের "আইডি" এর প্রয়োজন না হলে যদি উত্স চ্যানেলগুলি কখনও বন্ধ না হয় তবে উভয় পন্থা সরল করা যায়।

গোরটাইন মার্জ চ্যানেল:

// Process1 calls `fn` for each value received from any of the `chans`
// channels. The arguments to `fn` are the index of the channel the
// value came from and the string value. Process1 returns once all the
// channels are closed.
func Process1(chans []<-chan string, fn func(int, string)) {
    // Setup
    type item struct {
        int    // index of which channel this came from
        string // the actual string item
    }
    merged := make(chan item)
    var wg sync.WaitGroup
    wg.Add(len(chans))
    for i, c := range chans {
        go func(i int, c <-chan string) {
            // Reads and buffers a single item from `c` before
            // we even know if we can write to `merged`.
            //
            // Go doesn't provide a way to do something like:
            //     merged <- (<-c)
            // atomically, where we delay the read from `c`
            // until we can write to `merged`. The read from
            // `c` will always happen first (blocking as
            // required) and then we block on `merged` (with
            // either the above or the below syntax making
            // no difference).
            for s := range c {
                merged <- item{i, s}
            }
            // If/when this input channel is closed we just stop
            // writing to the merged channel and via the WaitGroup
            // let it be known there is one fewer channel active.
            wg.Done()
        }(i, c)
    }
    // One extra goroutine to watch for all the merging goroutines to
    // be finished and then close the merged channel.
    go func() {
        wg.Wait()
        close(merged)
    }()

    // "select-like" loop
    for i := range merged {
        // Process each value
        fn(i.int, i.string)
    }
}

প্রতিবিম্ব নির্বাচন:

// Process2 is identical to Process1 except that it uses the reflect
// package to select and read from the input channels which guarantees
// there is only one value "in-flight" (i.e. when `fn` is called only
// a single send on a single channel will have succeeded, the rest will
// be blocked). It is approximately two orders of magnitude slower than
// Process1 (which is still insignificant if their is a significant
// delay between incoming values or if `fn` runs for a significant
// time).
func Process2(chans []<-chan string, fn func(int, string)) {
    // Setup
    cases := make([]reflect.SelectCase, len(chans))
    // `ids` maps the index within cases to the original `chans` index.
    ids := make([]int, len(chans))
    for i, c := range chans {
        cases[i] = reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(c),
        }
        ids[i] = i
    }

    // Select loop
    for len(cases) > 0 {
        // A difference here from the merging goroutines is
        // that `v` is the only value "in-flight" that any of
        // the workers have sent. All other workers are blocked
        // trying to send the single value they have calculated
        // where-as the goroutine version reads/buffers a single
        // extra value from each worker.
        i, v, ok := reflect.Select(cases)
        if !ok {
            // Channel cases[i] has been closed, remove it
            // from our slice of cases and update our ids
            // mapping as well.
            cases = append(cases[:i], cases[i+1:]...)
            ids = append(ids[:i], ids[i+1:]...)
            continue
        }

        // Process each value
        fn(ids[i], v.String())
    }
}

[ গো খেলার মাঠে পূর্ণ কোড ।]


1
এটিও লক্ষণীয় যে গোরাউটাইনস + চ্যানেলগুলির সমাধানগুলি সবকিছু করতে selectবা করতে পারে না reflect.Select। চ্যানেলগুলি থেকে সমস্ত কিছু গ্রাস না করা অবধি গরোটিনগুলি ঘুরতে থাকবে, সুতরাং আপনি Process1প্রস্থান করার তাড়াতাড়ি কোনও পরিষ্কার উপায় নেই । সমস্যাগুলির সম্ভাবনাও রয়েছে যদি আপনার একাধিক পাঠক থাকে, যেহেতু গোরোটাইনগুলি প্রতিটি চ্যানেল থেকে একটি আইটেম বাফার করে, যা ঘটবে না select
জেমস হেনস্ট্রিজ

@ জামেসহেনস্ট্রিজ, থামার বিষয়ে আপনার প্রথম নোটটি সত্য নয়। আপনি প্রক্রিয়া 2 বন্ধ করার ব্যবস্থা ঠিক একইভাবে প্রক্রিয়া 1 বন্ধ করার ব্যবস্থা করবেন; উদাহরণস্বরূপ একটি "স্টপ" চ্যানেল যুক্ত করা হয়েছে যা জোরোটাইনগুলি বন্ধ হওয়া উচিত যখন বন্ধ থাকে। প্রসেস 1 এর জন্য বর্তমানে ব্যবহৃত সরল লুপের পরিবর্তে selectএকটি forলুপের মধ্যে দুটি ক্ষেত্রে প্রয়োজন for range। প্রক্রিয়া 2 এর জন্য অন্য কেসটি আটকে রাখা উচিত casesএবং সেই মানটির বিশেষ হ্যান্ডেল করা উচিত i
ডেভ সি

এটি এখনও সেই সমস্যার সমাধান করতে পারে না যে আপনি চ্যানেলগুলি থেকে মানগুলি পড়ছেন যা স্টপ প্রারম্ভিক ক্ষেত্রে ব্যবহৃত হবে না।
জেমস হেনস্ট্রিজ

0

কেউ এই অনুষ্ঠানটি পাঠাচ্ছে এই ধারণা করে কেন এই পদ্ধতির কাজ হবে না?

func main() {
    numChans := 2
    var chans = []chan string{}

    for i := 0; i < numChans; i++ {
        tmp := make(chan string)
        chans = append(chans, tmp)
    }

    for true {
        for i, c := range chans {
            select {
            case x = <-c:
                fmt.Printf("received %d \n", i)
                go DoShit(x, i)
            default: continue
            }
        }
    }
}

8
এটি একটি স্পিন-লুপ। কোনও ইনপুট চ্যানেলের কোনও মান হওয়ার জন্য অপেক্ষা করার সময় এটি উপলব্ধ সমস্ত সিপিইউ গ্রাস করে। selectএকাধিক চ্যানেলগুলির সম্পূর্ণ বিন্দু (কোনও defaultধারা ছাড়াই ) হ'ল এটি কোনও দক্ষতা ছাড়াই কমপক্ষে প্রস্তুত না হওয়া পর্যন্ত দক্ষতার সাথে অপেক্ষা করে।
ডেভ সি

0

সম্ভবত সহজ বিকল্প:

চ্যানেলের অ্যারে রাখার পরিবর্তে, কেন পৃথক গোরোটাইনগুলিতে চালিত ফাংশনগুলির পরামিতি হিসাবে কেবল একটি চ্যানেলটি পাস করবেন না, এবং তারপরে গ্রাহক গোরোটিনে চ্যানেলটি শুনবেন?

এটি আপনাকে আপনার শ্রোতার কেবলমাত্র একটি চ্যানেলে নির্বাচন করতে, সাধারণ নির্বাচন করার জন্য এবং একাধিক চ্যানেল থেকে বার্তাগুলির সামগ্রিক বার্তাগুলিতে নতুন গোরোটিন তৈরি করা এড়িয়ে চলা অনুমতি দেয়?

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.