Dies ist ein gutes Beispiel für den Worker & Controller-Modus in Go, geschrieben von @Jimt, als Antwort auf " Gibt es eine elegante Möglichkeit, eine andere Goroutine in Golang anzuhalten und fortzusetzen? "
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])
}
}
Dieser Code hat jedoch auch ein Problem: Wenn Sie einen Worker-Kanal in entfernen möchten workers
, wenn worker()
Ausfahrten, Tot - Schloss geschieht.
Wenn Sie das close(workers[i])
nächste Mal in den Controller schreiben, wird eine Panik ausgelöst, da go nicht in einen geschlossenen Kanal schreiben kann. Wenn Sie zum Schutz einen Mutex verwenden, bleibt dieser haftenworkers[i] <- Running
da worker
der Kanal nichts liest und das Schreiben blockiert wird und der Mutex eine Deadlock-Funktion verursacht. Sie können dem Kanal auch einen größeren Puffer als Workaround geben, aber er ist nicht gut genug.
Ich denke, der beste Weg, dies zu lösen, ist worker()
Schließen des Kanals beim Beenden. Wenn der Controller einen geschlossenen Kanal findet, springt er darüber und tut nichts. Ich kann jedoch nicht feststellen, wie überprüft werden kann, ob ein Kanal in dieser Situation bereits geschlossen ist oder nicht. Wenn ich versuche, den Kanal im Controller zu lesen, ist der Controller möglicherweise blockiert. Ich bin also vorerst sehr verwirrt.
PS: Ich habe versucht, die erhöhte Panik wiederherzustellen, aber sie wird die Goroutine schließen, die die Panik ausgelöst hat. In diesem Fall handelt es sich um einen Controller, der keinen Nutzen hat.
Dennoch denke ich, dass es für das Go-Team nützlich ist, diese Funktion in der nächsten Version von Go zu implementieren.
Antworten:
Auf hackige Weise kann dies für Kanäle geschehen, auf die man zu schreiben versucht, indem man die erhöhte Panik wieder herstellt. Sie können jedoch nicht überprüfen, ob ein Lesekanal geschlossen ist, ohne daraus zu lesen.
Entweder du wirst
v <- c
)v, ok <- c
)v, ok <- c
)v <- c
)Nur der letzte liest technisch gesehen nicht aus dem Kanal, aber das nützt wenig.
quelle
controller
so sein, dass es keinen Sinn macht :)Es gibt keine Möglichkeit, eine sichere Anwendung zu schreiben, in der Sie wissen müssen, ob ein Kanal geöffnet ist, ohne mit ihm zu interagieren.
Der beste Weg, um das zu tun, was Sie tun möchten, sind zwei Kanäle - einer für die Arbeit und einer, um den Wunsch anzuzeigen, den Status zu ändern (sowie den Abschluss dieser Statusänderung, wenn dies wichtig ist).
Kanäle sind billig. Komplexe Designüberladungssemantik ist nicht.
[ebenfalls]
<-time.After(1e9)
ist eine wirklich verwirrende und nicht offensichtliche Art zu schreiben
Halten Sie die Dinge einfach und jeder (einschließlich Sie) kann sie verstehen.
quelle
Ich weiß, dass diese Antwort so spät ist, ich habe diese Lösung geschrieben, Hacking Go-Laufzeit , Es ist keine Sicherheit, Es kann abstürzen:
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 }
quelle
go vet
returns „möglichen Missbrauch von unsafe.Pointer“ auf der letzten Zeilereturn *(*uint32)(unsafe.Pointer(cptr)) > 0
undcptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
gibt es eine Option, ohne unsafe.Pointer in diese Richtung zu tun?Nun, können Sie den
default
Zweig zu erfassen, für einen geschlossenen Kanal ausgewählt werden, zum Beispiel: der folgende Code wähltdefault
,channel
,channel
die erste Auswahl wird nicht blockiert.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) }
Druckt
2018/05/24 08:00:00 1.default 2018/05/24 08:00:01 2.channel 2018/05/24 08:00:01 3.channel
quelle
Aus der Dokumentation:
Ein Kanal kann mit der eingebauten Funktion schließen geschlossen werden. Das mehrwertige Zuweisungsformular des Empfangsoperators gibt an, ob ein empfangener Wert gesendet wurde, bevor der Kanal geschlossen wurde.
https://golang.org/ref/spec#Receive_operator
Ein Beispiel von Golang in Aktion zeigt diesen Fall:
// 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 } }
quelle
Sie können Ihren Kanal zusätzlich zum Schließen auf Null setzen. Auf diese Weise können Sie überprüfen, ob es Null ist.
Beispiel auf dem Spielplatz: https://play.golang.org/p/v0f3d4DisCz
Bearbeiten: Dies ist tatsächlich eine schlechte Lösung, wie im nächsten Beispiel gezeigt, da das Setzen des Kanals auf Null in einer Funktion ihn beschädigen würde: https://play.golang.org/p/YVE2-LV9TOp
quelle
Es ist einfacher, zuerst zu überprüfen, ob der Kanal Elemente enthält, die sicherstellen, dass der Kanal lebt.
func isChanClosed(ch chan interface{}) bool { if len(ch) == 0 { select { case _, ok := <-ch: return !ok } } return false }
quelle
if
Körper kommst,len(ch)
könnte alles sein. (zB sendet eine Goroutine auf einem anderen Kern einen Wert an den Kanal, bevor Ihre Auswahl versucht zu lesen).Wenn Sie diesen Kanal hören, können Sie immer feststellen, dass dieser Kanal geschlossen wurde.
case state, opened := <-ws: if !opened { // channel was closed // return or made some final work } switch state { case Stopped:
Denken Sie jedoch daran, dass Sie einen Kanal nicht zweimal schließen können. Dies wird Panik auslösen.
quelle
close(z)
werde vom Arbeiter anstelle des Controllers angerufen.