To jest dobry przykład trybu pracowników i kontrolera w Go napisany przez @Jimt, w odpowiedzi na pytanie „ Czy istnieje jakiś elegancki sposób na wstrzymanie i wznowienie innych goroutine w golang? ”
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])
}
}
Ale ten kod ma również problem: jeśli chcesz usunąć kanał roboczy w workers
momencie worker()
wyjścia, nastąpi martwa blokada.
Jeśli ty close(workers[i])
, następnym razem, gdy kontroler zapisze do niego, wywoła panikę, ponieważ go nie może pisać do zamkniętego kanału. Jeśli użyjesz jakiegoś mutexa do ochrony, to utknie, workers[i] <- Running
ponieważ worker
nie czyta niczego z kanału i zapis zostanie zablokowany, a mutex spowoduje martwą blokadę. Możesz również dać większy bufor do kanału jako obejście, ale to nie wystarczy.
Myślę więc, że najlepszym sposobem rozwiązania tego problemu jest worker()
zamknięcie kanału przy wyjściu, jeśli sterownik znajdzie kanał zamknięty, przeskoczy nad nim i nic nie zrobi. Ale nie mogę znaleźć, jak sprawdzić, czy kanał jest już zamknięty, czy nie w tej sytuacji. Jeśli spróbuję odczytać kanał w kontrolerze, kontroler może być zablokowany. Więc na razie jestem bardzo zdezorientowany.
PS: Próbowałem odzyskać podniesioną panikę, ale zamknie to gorutynę, która wywołała panikę. W tym przypadku będzie to kontroler, więc nie ma sensu.
Mimo to myślę, że dla zespołu Go przydatne będzie zaimplementowanie tej funkcji w następnej wersji Go.
Odpowiedzi:
W hackerski sposób można to zrobić dla kanałów, do których próbuje się pisać, odzyskując wywołaną panikę. Ale nie możesz sprawdzić, czy kanał odczytu jest zamknięty bez czytania z niego.
Albo będziesz
v <- c
)v, ok <- c
)v, ok <- c
)v <- c
)Tylko ten ostatni technicznie nie czyta z kanału, ale to mało przydatne.
źródło
controller
to bez sensu :)Nie ma sposobu, aby napisać bezpieczną aplikację, w której musisz wiedzieć, czy kanał jest otwarty, bez interakcji z nim.
Najlepszym sposobem na zrobienie tego, co chcesz, są dwa kanały - jeden do pracy, a drugi do wskazania chęci zmiany stanu (a także zakończenia zmiany tego stanu, jeśli jest to ważne).
Kanały są tanie. Złożona semantyka przeciążania projektu nie jest.
[również]
<-time.After(1e9)
to naprawdę zagmatwany i nieoczywisty sposób pisania
Postaraj się, aby wszystko było proste, a każdy (w tym Ty) może je zrozumieć.
źródło
Wiem, że ta odpowiedź jest tak późna, napisałem to rozwiązanie, Hacking Go run-time , To nie jest bezpieczeństwo, może się zawiesić:
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 }
źródło
go vet
zwraca „możliwe niewłaściwe użycie unsafe.Pointer” w ostatnim wierszureturn *(*uint32)(unsafe.Pointer(cptr)) > 0
icptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
czy istnieje możliwość zrobienia tego bez pliku unsafe.Pointer w tych wierszach?Cóż, można użyć
default
oddział wykryć go do zamkniętego kanału zostanie wybrany, na przykład: poniższy kod wybierzedefault
,channel
,channel
, pierwszy wybór nie jest zablokowany.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) }
Wydruki
2018/05/24 08:00:00 1.default 2018/05/24 08:00:01 2.channel 2018/05/24 08:00:01 3.channel
źródło
Oprócz zamknięcia kanału możesz ustawić wartość zero. W ten sposób możesz sprawdzić, czy jest zero.
przykład na placu zabaw: https://play.golang.org/p/v0f3d4DisCz
edycja: jest to właściwie złe rozwiązanie, jak pokazano w następnym przykładzie, ponieważ ustawienie kanału na zero w funkcji spowodowałoby jej uszkodzenie : https://play.golang.org/p/YVE2-LV9TOp
źródło
Z dokumentacji:
Kanał można zamknąć funkcją wbudowaną close. Formularz przypisania wielowartościowego operatora odbioru informuje, czy odebrana wartość została wysłana przed zamknięciem kanału.
https://golang.org/ref/spec#Receive_operator
Przykład Golang in Action pokazuje ten przypadek:
// 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 } }
źródło
łatwiej jest najpierw sprawdzić, czy kanał ma elementy, które zapewniłyby, że kanał żyje.
func isChanClosed(ch chan interface{}) bool { if len(ch) == 0 { select { case _, ok := <-ch: return !ok } } return false }
źródło
if
ciała,len(ch)
może być wszystko. (np. goroutine na innym rdzeniu wysyła wartość do kanału, zanim wybrany spróbuje odczytać).Jeśli słuchasz tego kanału, zawsze możesz dowiedzieć się, że kanał został zamknięty.
case state, opened := <-ws: if !opened { // channel was closed // return or made some final work } switch state { case Stopped:
Pamiętaj jednak, że nie możesz zamknąć jednego kanału dwa razy. To wzbudzi panikę.
źródło
close(z)
będę wywoływany przez pracownika zamiast kontrolera.