Este es un buen ejemplo del modo de trabajadores y controlador en Go escrito por @Jimt, en respuesta a " ¿Hay alguna forma elegante de pausar y reanudar cualquier otra rutina de gor en 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])
}
}
Pero este código también tiene un problema: si desea eliminar un canal de trabajador workers
cuando worker()
sale, se produce un bloqueo.
Si usted close(workers[i])
, la próxima vez que el controlador escriba en él, se producirá un pánico, ya que go no puede escribir en un canal cerrado. Si usa algún mutex para protegerlo, se bloqueará workers[i] <- Running
ya worker
que no lee nada del canal y la escritura se bloqueará, y el mutex provocará un bloqueo muerto. También puede dar un búfer más grande al canal como solución temporal, pero no es lo suficientemente bueno.
Así que creo que la mejor manera de resolver esto es worker()
cerrar el canal cuando sale, si el controlador encuentra un canal cerrado, lo saltará y no hará nada. Pero no encuentro cómo comprobar que un canal ya está cerrado o no en esta situación. Si intento leer el canal en el controlador, es posible que el controlador esté bloqueado. Entonces estoy muy confundido por ahora.
PD: Recuperar el pánico levantado es lo que he intentado, pero cerrará la rutina que provocó el pánico. En este caso será el controlador, por lo que no sirve de nada.
Aún así, creo que es útil para el equipo de Go implementar esta función en la próxima versión de Go.
Respuestas:
De una manera hacky, se puede hacer para los canales en los que se intenta escribir recuperando el pánico generado. Pero no puede verificar si un canal de lectura está cerrado sin leerlo.
O lo harás
v <- c
)v, ok <- c
)v, ok <- c
)v <- c
)Solo el último técnicamente no lee del canal, pero eso es de poca utilidad.
fuente
controller
así que no sirve :)No hay forma de escribir una aplicación segura en la que necesite saber si un canal está abierto sin interactuar con él.
La mejor manera de hacer lo que desea hacer es con dos canales: uno para el trabajo y otro para indicar el deseo de cambiar de estado (así como la finalización de ese cambio de estado si es importante).
Los canales son baratos. La semántica de sobrecarga de diseño complejo no lo es.
[además]
<-time.After(1e9)
es una forma realmente confusa y poco obvia de escribir
Mantenga las cosas simples y todos (incluido usted) podrán entenderlas.
fuente
Sé que esta respuesta es muy tarde, escribí esta solución, Hacking Go run-time , no es seguridad, puede fallar:
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 }
fuente
go vet
devuelve "posible uso indebido de unsafe.Pointer" en la última líneareturn *(*uint32)(unsafe.Pointer(cptr)) > 0
ycptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
hay una opción para hacerlo sin unsafe.Pointer en esas líneas?Bueno, se puede utilizar
default
la rama de detectarlo, por un canal cerrado serán seleccionados, por ejemplo: el siguiente código seleccionarádefault
,channel
,channel
, el primer seleccionado no está bloqueado.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) }
Huellas dactilares
2018/05/24 08:00:00 1.default 2018/05/24 08:00:01 2.channel 2018/05/24 08:00:01 3.channel
fuente
Puede establecer su canal en cero además de cerrarlo. De esa forma, puede comprobar si es nulo.
ejemplo en el patio de recreo: https://play.golang.org/p/v0f3d4DisCz
editar: Esta es en realidad una mala solución, como se demuestra en el siguiente ejemplo, porque configurar el canal en nil en una función lo rompería: https://play.golang.org/p/YVE2-LV9TOp
fuente
De la documentación:
Un canal puede cerrarse con la función incorporada cerrar. El formulario de asignación de valores múltiples del operador de recepción informa si se envió un valor recibido antes de que se cerrara el canal.
https://golang.org/ref/spec#Receive_operator
El ejemplo de Golang en acción muestra este caso:
// 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 } }
fuente
es más fácil verificar primero si el canal tiene elementos, eso garantizaría que el canal esté activo.
func isChanClosed(ch chan interface{}) bool { if len(ch) == 0 { select { case _, ok := <-ch: return !ok } } return false }
fuente
if
cuerpo,len(ch)
podría ser cualquier cosa. (por ejemplo, una goroutine en otro núcleo envía un valor al canal antes de que su selección intente leer).Si escucha este canal, siempre podrá descubrir que el canal estaba cerrado.
case state, opened := <-ws: if !opened { // channel was closed // return or made some final work } switch state { case Stopped:
Pero recuerde, no puede cerrar un canal dos veces. Esto aumentará el pánico.
fuente
close(z)
trabajador me llamará en lugar del controlador.