Как проверить канал закрыт или нет не читая?
это хороший пример режима рабочих и контроллера в Go, написанном @Jimt, в ответ на "есть какой-то элегантный способ, чтобы сделать паузу и возобновить любая другая горутина в 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: Stoppedn", id)
return
case Running:
fmt.Printf("Worker %d: Runningn", id)
case Paused:
fmt.Printf("Worker %d: Pausedn", 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])
, в следующий раз контроллер пишет в него вызовет панику, так как go не может писать в закрытый канал. Если вы используете некоторые мьютекс, чтобы защитить его, то он будет застрял на workers[i] <- Running
С worker
не читает ничего из канала, и запись будет заблокирована, а мьютекс вызовет мертвую блокировку. Вы также можете дать больший буфер для канала в качестве обходного пути, но этого недостаточно.
поэтому я думаю, что лучший способ решить это worker()
закрыть канал при выходе, если контроллер обнаружит канал закрытым, он перепрыгнет через него и ничего не сделает. Но я не могу найти, Как проверить, что канал уже закрыт или не в этой ситуации. Если я попытаюсь прочитать канал в контроллере, контроллер может быть заблокирован. Так что сейчас я в замешательстве.
PS: восстановление поднятой паники - это то, что я пробовал, но он закроет goroutine, который поднял панику. В этом случае это будет контроллер, поэтому это бесполезно.
тем не менее, я думаю, что для Go team полезно реализовать эту функцию в следующей версии Go.
8 ответов
хакерским способом это можно сделать для каналов, на которые пытаются писать, восстанавливая поднятую панику. Но вы не можете проверить, закрыт ли канал чтения без чтения из него.
либо
- в конечном итоге прочитайте значение " true "из него (
v <- c
) - прочитайте значение" true "и индикатор " not closed" (
v, ok <- c
) - считайте нулевое значение и индикатор' closed' (
v, ok <- c
) - будет блокировать в канале чтения навсегда (
v <- c
)
только последний технически не читает с канала, но это мало пользы.
нет способа написать безопасное приложение, в котором вам нужно знать, открыт ли канал без взаимодействия с ним.
лучший способ сделать то, что вы хотите сделать, - это два канала: один для работы, а другой для указания желания изменить состояние (а также завершения этого изменения состояния, если это важно).
каналы дешевые. Сложная конструкция перегружает семантику.
[также]
<-time.After(1e9)
- это правда запутанный и неочевидный способ написать
time.Sleep(time.Second)
держите вещи простыми, и каждый (включая вас) может их понять.
Я знаю, что этот ответ так поздно, я написал это решение, взлом бежать-времени, это не безопасность, это может произойти сбой:
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
}
https://gist.github.com/youssifsayed/ca0cfcf9dc87905d37a4fee7beb253c2
может быть, мне чего-то не хватает, но кажется, что простой и правильный способ справиться с этим-отправить "остановленный" канал (который завершает GO-routine), закрыть канал и установить его на ноль.
Если вы думаете, что вам нужно проверить закрытый канал, не читая его, то есть проблема с вашим дизайном. (Обратите внимание, что есть и другие проблемы с кодом, такие как "цикл занятости" приостановленных работников.)
из документации:
канал может быть закрыт с помощью встроенной функции закрытия. Многозначная форма назначения оператора получения сообщает, Было ли передано полученное значение до закрытия канала.
https://golang.org/ref/spec#Receive_operator
пример Golang в действии показывает этот случай:
// 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
}
}
Ну, вы можете использовать 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)
}
Если вы слушаете этот канал вы всегда сможете узнать, что канал был закрыт.
case state, opened := <-ws:
if !opened {
// channel was closed
// return or made some final work
}
switch state {
case Stopped:
но помните, вы не можете закрыть один канал в два раза. Это вызовет панику.
проще сначала проверить, есть ли у канала элементы, которые гарантировали бы, что канал жив.
func isChanClosed(ch chan interface{}) bool {
if len(ch) == 0 {
select {
case _, ok := <-ch:
return !ok
}
}
return false
}