किसी चैनल को बिना पढ़े बंद कर दिया गया है या नहीं इसकी जांच कैसे करें?


82

यह @Jimt द्वारा लिखे गए गो में श्रमिकों और नियंत्रक मोड का एक अच्छा उदाहरण है, " क्या गोलंग में किसी अन्य गोरोइन को रोकने और फिर से शुरू करने के लिए कुछ सुरुचिपूर्ण तरीका है? "

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

// Possible worker states.
const (
    Stopped = 0
    Paused  = 1
    Running = 2
)

// Maximum number of workers.
const WorkerCount = 1000

func main() {
    // Launch workers.
    var wg sync.WaitGroup
    wg.Add(WorkerCount + 1)

    workers := make([]chan int, WorkerCount)
    for i := range workers {
        workers[i] = make(chan int)

        go func(i int) {
            worker(i, workers[i])
            wg.Done()
        }(i)
    }

    // Launch controller routine.
    go func() {
        controller(workers)
        wg.Done()
    }()

    // Wait for all goroutines to finish.
    wg.Wait()
}

func worker(id int, ws <-chan int) {
    state := Paused // Begin in the paused state.

    for {
        select {
        case state = <-ws:
            switch state {
            case Stopped:
                fmt.Printf("Worker %d: Stopped\n", id)
                return
            case Running:
                fmt.Printf("Worker %d: Running\n", id)
            case Paused:
                fmt.Printf("Worker %d: Paused\n", id)
            }

        default:
            // We use runtime.Gosched() to prevent a deadlock in this case.
            // It will not be needed of work is performed here which yields
            // to the scheduler.
            runtime.Gosched()

            if state == Paused {
                break
            }

            // Do actual work here.
        }
    }
}

// controller handles the current state of all workers. They can be
// instructed to be either running, paused or stopped entirely.
func controller(workers []chan int) {
    // Start workers
    for i := range workers {
        workers[i] <- Running
    }

    // Pause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Paused
    }

    // Unpause workers.
    <-time.After(1e9)
    for i := range workers {
        workers[i] <- Running
    }

    // Shutdown workers.
    <-time.After(1e9)
    for i := range workers {
        close(workers[i])
    }
}

लेकिन इस कोड में एक मुद्दा भी है: यदि आप बाहर निकलते workersसमय किसी कार्यकर्ता चैनल को हटाना चाहते हैं worker(), तो डेड लॉक होता है।

यदि आप close(workers[i]), अगली बार नियंत्रक लिखते हैं, तो इससे घबराहट होगी क्योंकि गो बंद चैनल में नहीं लिख सकता है। यदि आप इसे बचाने के लिए कुछ म्यूटेक्स का उपयोग करते हैं, तो यह पर अटक जाएगाworkers[i] <- Running क्योंकि workerचैनल से कुछ भी नहीं पढ़ रहा है और लिखना अवरुद्ध हो जाएगा, और म्यूटेक्स एक मृत लॉक का कारण होगा। आप वर्क-अराउंड के रूप में चैनल को एक बड़ा बफर भी दे सकते हैं, लेकिन यह काफी अच्छा नहीं है।

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

पुनश्च: उठे हुए घबराहट को ठीक करना जो मैंने कोशिश की है, लेकिन यह गोरोइन को बंद कर देगा जिसने घबराहट को बढ़ाया। इस मामले में यह नियंत्रक होगा इसलिए इसका कोई उपयोग नहीं है।

फिर भी, मुझे लगता है कि गो के अगले संस्करण में इस फ़ंक्शन को लागू करने के लिए गो टीम के लिए यह उपयोगी है।


अपने कार्यकर्ता का राज्य संभालें! यदि आप चैनल बंद करते हैं, तो उसे फिर से लिखने की आवश्यकता नहीं है।
जुर्का

यहाँ, मैंने इसे बनाया: github.com/atedja/go-tunnel
आत्जाजा at

जवाबों:


64

हैक किए गए तरीके से यह उन चैनलों के लिए किया जा सकता है जो किसी ने घबराहट को ठीक करके लिखने का प्रयास किया है। लेकिन आप यह नहीं देख सकते हैं कि कोई रीड चैनल इससे पढ़े बिना बंद है या नहीं।

या तो आप करेंगे

  • अंततः इससे "सही" मान पढ़ें ( v <- c)
  • "सही" मान और 'बंद नहीं' संकेतक पढ़ें ( v, ok <- c)
  • शून्य मान और 'बंद' संकेतक ( v, ok <- c) पढ़ें
  • हमेशा के लिए पढ़ने वाले चैनल में ब्लॉक हो जाएगा ( v <- c)

केवल पिछले एक तकनीकी रूप से चैनल से नहीं पढ़ता है, लेकिन यह बहुत कम उपयोग है।


1
बढ़ी हुई घबराहट को पुनर्प्राप्त करना, जो मैंने कोशिश की है, लेकिन यह गोरोइन को बंद कर देगा जिसने दहशत को बढ़ा दिया। इस मामले में यह होगा controllerतो इसका कोई फायदा नहीं है :)
Reck Hou

आप असुरक्षित का उपयोग करके हैक भी लिख सकते हैं और पैकेज को देख सकते हैं मेरा उत्तर देखें
youssif

78

एक सुरक्षित एप्लिकेशन लिखने का कोई तरीका नहीं है जहां आपको यह जानना होगा कि क्या कोई चैनल इसके साथ बातचीत किए बिना खुला है।

सबसे अच्छा तरीका है कि आप जो करना चाहते हैं, वह दो चैनलों के साथ है - एक काम के लिए और एक राज्य को बदलने की इच्छा को इंगित करने के लिए (और साथ ही अगर वह महत्वपूर्ण है तो उस राज्य परिवर्तन को पूरा करने के लिए)।

चैनल सस्ते हैं। जटिल डिजाइन ओवरलोडिंग शब्दार्थ विज्ञान नहीं है।

[भी]

<-time.After(1e9)

लिखने के लिए वास्तव में भ्रामक और गैर-स्पष्ट तरीका है

time.Sleep(time.Second)

चीजों को सरल रखें और हर कोई (आप सहित) उन्हें समझ सकता है।


7

मुझे पता है कि यह उत्तर इतनी देर से आया है, मैंने यह समाधान लिखा है, हैकिंग गो रन-टाइम , यह सुरक्षा नहीं है, यह दुर्घटनाग्रस्त हो सकता है:

import (
    "unsafe"
    "reflect"
)


func isChanClosed(ch interface{}) bool {
    if reflect.TypeOf(ch).Kind() != reflect.Chan {
        panic("only channels!")
    }
    
    // get interface value pointer, from cgo_export 
    // typedef struct { void *t; void *v; } GoInterface;
    // then get channel real pointer
    cptr := *(*uintptr)(unsafe.Pointer(
        unsafe.Pointer(uintptr(unsafe.Pointer(&ch)) + unsafe.Sizeof(uint(0))),
    ))
    
    // this function will return true if chan.closed > 0
    // see hchan on https://github.com/golang/go/blob/master/src/runtime/chan.go 
    // type hchan struct {
    // qcount   uint           // total data in the queue
    // dataqsiz uint           // size of the circular queue
    // buf      unsafe.Pointer // points to an array of dataqsiz elements
    // elemsize uint16
    // closed   uint32
    // **
    
    cptr += unsafe.Sizeof(uint(0))*2
    cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
    cptr += unsafe.Sizeof(uint16(0))
    return *(*uint32)(unsafe.Pointer(cptr)) > 0
}

1
go vetअंतिम पंक्ति पर "असुरक्षित पॉइंटर का संभावित दुरुपयोग" देता है return *(*uint32)(unsafe.Pointer(cptr)) > 0और cptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0))) क्या उन पंक्तियों में असुरक्षित के बिना इसे करने का विकल्प है।
इविवि बार-शीआन

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

1
यह शायद बहुत चालाक है, लेकिन यह एक समस्या के शीर्ष पर एक समस्या को जोड़ने जैसा लगता है
ларослав Рахматуллин

2

ठीक है, आप उपयोग कर सकते हैं default, का चयन किया जाएगा उदाहरण के लिए, यह पता लगाने के लिए एक बंद चैनल के लिए शाखा: निम्नलिखित कोड का चयन करेंगे default, channel, channel, पहले चयन अवरुद्ध नहीं कर रहा है।

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

    go func() {
        select {
        case <-ch:
            log.Printf("1.channel")
        default:
            log.Printf("1.default")
        }
        select {
        case <-ch:
            log.Printf("2.channel")
        }
        close(ch)
        select {
        case <-ch:
            log.Printf("3.channel")
        default:
            log.Printf("3.default")
        }
    }()
    time.Sleep(time.Second)
    ch <- 1
    time.Sleep(time.Second)
}

प्रिंटों

2018/05/24 08:00:00 1.default
2018/05/24 08:00:01 2.channel
2018/05/24 08:00:01 3.channel

3
इस समाधान के साथ एक समस्या है (साथ ही अच्छी तरह से लिखे गए go101.org/article/channel-closing.html जो एक समान समाधान का प्रस्ताव देता है) - यदि आप बफ़र चैनल का उपयोग कर रहे हैं तो यह काम नहीं करता है और इसमें अपठित है डेटा
अंगद

@Angad यह सच है कि यह बंद चैनल का पता लगाने के लिए एक सही समाधान नहीं है। यह पता लगाने के लिए एक सटीक समाधान है कि क्या चैनल पढ़ना अवरुद्ध होगा। (यानी यदि चैनल पढ़ना बंद हो जाएगा, तो हमें पता है कि यह बंद नहीं है; यदि चैनल को पढ़ने से ब्लॉक नहीं होगा, तो हमें पता है कि यह बंद हो सकता है)।
tombrown52

0

आप इसे बंद करने के अलावा अपने चैनल को शून्य पर सेट कर सकते हैं। इस तरह से आप जांच सकते हैं कि क्या यह शून्य है।

खेल के मैदान में उदाहरण: https://play.golang.org/p/v0f3d4DisCz

संपादित करें: यह वास्तव में एक बुरा समाधान है जैसा कि अगले उदाहरण में दिखाया गया है, क्योंकि चैनल को एक फ़ंक्शन में शून्य पर सेट करने से यह टूट जाएगा: https://play.golang.org/p/YVE2-LV9TOp


पता द्वारा चैनल पास करें (या पते द्वारा पास की गई संरचना में)
ChuckCottrill

-1

प्रलेखन से:

एक चैनल अंतर्निहित फ़ंक्शन के साथ बंद हो सकता है। प्राप्त ऑपरेटर के बहु-मूल्यवान असाइनमेंट फॉर्म रिपोर्ट करता है कि क्या चैनल बंद होने से पहले एक प्राप्त मूल्य भेजा गया था।

https://golang.org/ref/spec#Receive_operator

गोलंग द्वारा एक्शन में उदाहरण इस मामले को दर्शाता है:

// This sample program demonstrates how to use an unbuffered
// channel to simulate a game of tennis between two goroutines.
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

// wg is used to wait for the program to finish.
var wg sync.WaitGroup

func init() {
    rand.Seed(time.Now().UnixNano())
}

// main is the entry point for all Go programs.
func main() {
    // Create an unbuffered channel.
    court := make(chan int)
    // Add a count of two, one for each goroutine.
    wg.Add(2)
    // Launch two players.
    go player("Nadal", court)
    go player("Djokovic", court)
    // Start the set.
    court <- 1
    // Wait for the game to finish.
    wg.Wait()
}

// player simulates a person playing the game of tennis.
func player(name string, court chan int) {
    // Schedule the call to Done to tell main we are done.
    defer wg.Done()
    for {
        // Wait for the ball to be hit back to us.
        ball, ok := <-court
        fmt.Printf("ok %t\n", ok)
        if !ok {
            // If the channel was closed we won.
            fmt.Printf("Player %s Won\n", name)
            return
        }
        // Pick a random number and see if we miss the ball.
        n := rand.Intn(100)
        if n%13 == 0 {
            fmt.Printf("Player %s Missed\n", name)
            // Close the channel to signal we lost.
            close(court)
            return
        }

        // Display and then increment the hit count by one.
        fmt.Printf("Player %s Hit %d\n", name, ball)
        ball++
        // Hit the ball back to the opposing player.
        court <- ball
    }
}

2
सवाल यह था कि चैनल को पढ़े बिना बंद राज्य की जांच कैसे की जाए, यानी इसे लिखने से पहले।
पीटर

-5

यदि चैनल में तत्व हैं, तो पहले यह जांचना आसान है कि चैनल जीवित है या नहीं।

func isChanClosed(ch chan interface{}) bool {
    if len(ch) == 0 {
        select {
        case _, ok := <-ch:
            return !ok
        }
    }
    return false 
}

3
जैसा कि डस्टिन ने उल्लेख किया है , यह सुरक्षित रूप से करने का कोई तरीका नहीं है। जब तक आप अपने ifशरीर में आते हैं len(ch)तब तक कुछ भी हो सकता है। (उदाहरण के लिए किसी अन्य कोर पर एक गोरोइनिन आपके चयन को पढ़ने से पहले चैनल को एक मान भेजता है)।
डेव सी

-7

यदि आप इस चैनल को सुनते हैं तो आप हमेशा पता लगा सकते हैं कि चैनल बंद था।

case state, opened := <-ws:
    if !opened {
         // channel was closed 
         // return or made some final work
    }
    switch state {
        case Stopped:

लेकिन याद रखें, आप एक चैनल को दो बार बंद नहीं कर सकते। इससे घबराहट बढ़ेगी।


5
मैंने कहा "इसे बिना पढ़े", -1 प्रश्न को ध्यान से न पढ़ने के लिए।
रेक होउ

> पुनश्च: उठे हुए घबराहट को ठीक करना जो मैंने कोशिश की है, लेकिन यह गोरोइन को बंद कर देगा जिसने घबराहट को बढ़ाया। इस मामले में यह नियंत्रक होगा इसलिए इसका कोई उपयोग नहीं है। आप हमेशा func (chan z) {defer func () {// हैंडल पुनर्प्राप्त} करीब (z)}
jurka

लेकिन मुझे नियंत्रक को आरक्षित करना close(z)होगा , और नियंत्रक के बजाय कार्यकर्ता द्वारा बुलाया जाएगा।
रेक होउ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.