Как проверить канал закрыт или нет, не прочитав его?
Это хороший пример рабочего режима и режима контроллера в Go, написанный @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])
, следующий контроллер времени записывает в него, вызывает панику, так как go не может записываться в закрытый канал. Если вы используете некоторый мьютекс, чтобы защитить его, то он будет зависеть от workers[i] <- Running
поскольку worker
ничего не читает с канала, и запись будет заблокирована, а мьютекс вызовет мертвую блокировку. Вы также можете дать больший буфер для работы в качестве рабочего процесса, но это не достаточно хорошо.
Поэтому я думаю, что лучший способ решить это - worker()
закрытый канал при выходе, если контроллер обнаружит канал закрытым, он перепрыгнет через него и ничего не сделает. Но я не могу найти, как проверить, что канал уже закрыт или нет в этой ситуации. Если я попытаюсь прочитать канал в контроллере, контроллер может быть заблокирован. Поэтому я сейчас очень смущен.
PS: Восстановление поднятой паники - это то, что я пробовал, но она закроет горутин, который вызвал панику. В этом случае он будет контроллером, поэтому он не будет использоваться.
Тем не менее, я считаю полезным для команды Go для реализации этой функции в следующей версии Go.
Ответы
Ответ 1
В хакерском режиме это может быть сделано для каналов, к которым один пытается написать, восстанавливая повышенную панику. Но вы не можете проверить, закрыт ли канал чтения, не читая его.
Либо вы
- в конечном счете, прочитает от него "истинное" значение (
v <- c
) - прочитайте "истинное" значение и индикатор "не закрыт" (
v, ok <- c
) - прочитайте нулевое значение и "закрытый" индикатор (
v, ok <- c
) - будет блокироваться в канале, читаемом вечно (
v <- c
)
Только последний технически не читает с канала, но мало использует.
Ответ 2
Там нет способа написать безопасное приложение, где вам нужно знать, открыт ли канал, не взаимодействуя с ним.
Лучший способ сделать то, что вы хотите сделать, - это два канала - один для работы, а один - желание изменить состояние (а также завершение этого изменения состояния, если это важно).
Каналы дешевы. Сложная семантика перегрузки дизайна - нет.
[также]
<-time.After(1e9)
это действительно запутанный и неочевидный способ писать
time.Sleep(time.Second)
Держите вещи простыми, и каждый (включая вас) может их понять.
Ответ 3
Я знаю, что этот ответ настолько запоздал, что я написал это решение, Hacking Go run-time, Это не безопасность, он может сработать:
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
Ответ 4
Может быть, мне что-то не хватает, но кажется, что простой и правильный способ справиться с этим - отправить "остановлен" на канал (который завершает рутинную процедуру), закрыть канал и установить его на нуль.
Если вы считаете, что вам нужно проверить закрытый канал, не прочитав его, тогда возникнет проблема с вашим дизайном. (Обратите внимание, что есть другие проблемы с кодом, такие как "занятая петля" приостановленных работников.)
Ответ 5
Из документации:
Канал может быть закрыт при закрытой функции. Многозначная форма присваивания оператора приема сообщает, было ли отправлено полученное значение до закрытия канала.
https://golang.org/ref/spec#Receive_operator
Пример Golang in Action показывает этот случай:
// 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
}
}
Ответ 6
Ну, вы можете использовать ветвь по 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)
}
Ответ 7
Если вы слушаете этот канал, вы всегда можете узнать, что канал был закрыт.
case state, opened := <-ws:
if !opened {
// channel was closed
// return or made some final work
}
switch state {
case Stopped:
Но помните, вы не можете закрыть один канал два раза. Это вызовет панику.
Ответ 8
сначала проверить, если канал имеет элементы, которые гарантируют, что канал жив.
func isChanClosed(ch chan interface{}) bool {
if len(ch) == 0 {
select {
case _, ok := <-ch:
return !ok
}
}
return false
}