Это хороший пример режима рабочих и контроллеров в 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: 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.
Ответы:
Хакерским способом это можно сделать для каналов, на которые делается попытка записи, восстановив возникшую панику. Но вы не можете проверить, закрыт ли канал чтения, не читая из него.
Либо ты будешь
v <- c
)v, ok <- c
)v, ok <- c
)v <- c
)Только последний технически с канала не читает, но толку от этого мало.
источник
controller
бесполезно :)Невозможно написать безопасное приложение, в котором вам нужно знать, открыт ли канал, без взаимодействия с ним.
Лучший способ сделать то, что вы хотите сделать, - это использовать два канала: один для работы, а другой - для обозначения желания изменить состояние (а также завершения этого изменения состояния, если это важно).
Каналы дешевые. Семантика перегрузки сложного дизайна - нет.
[также]
<-time.After(1e9)
это действительно запутанный и неочевидный способ написать
Делайте вещи простыми, чтобы все (включая вас) могли их понять.
источник
Я знаю, что этот ответ так поздно, я написал это решение, 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 }
источник
go vet
возвращает "возможное неправильное использование unsafe.Pointer" в последней строке,return *(*uint32)(unsafe.Pointer(cptr)) > 0
иcptr += unsafe.Sizeof(unsafe.Pointer(uintptr(0)))
есть ли возможность сделать это без unsafe.Pointer в этих строках?Ну, вы можете использовать
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) }
Печать
2018/05/24 08:00:00 1.default 2018/05/24 08:00:01 2.channel 2018/05/24 08:00:01 3.channel
источник
Вы можете обнулить свой канал в дополнение к его закрытию. Таким образом вы можете проверить, равно ли оно нулю.
пример на детской площадке: https://play.golang.org/p/v0f3d4DisCz
edit: на самом деле это плохое решение, как показано в следующем примере, потому что установка канала на ноль в функции приведет к его поломке: https://play.golang.org/p/YVE2-LV9TOp
источник
Из документации:
Канал можно закрыть с помощью встроенной функции закрытия. Форма многозначного назначения оператора приема сообщает, было ли полученное значение отправлено до закрытия канала.
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 } }
источник
легче сначала проверить, есть ли в канале элементы, которые гарантируют, что канал жив.
func isChanClosed(ch chan interface{}) bool { if len(ch) == 0 { select { case _, ok := <-ch: return !ok } } return false }
источник
if
тело,len(ch)
может быть что угодно. (например, горутина на другом ядре отправляет значение в канал до того, как ваш выбор попытается прочитать).Если вы слушаете этот канал, вы всегда можете узнать, что канал был закрыт.
case state, opened := <-ws: if !opened { // channel was closed // return or made some final work } switch state { case Stopped:
Но помните, нельзя два раза закрыть один канал. Это вызовет панику.
источник
close(z)
он будет вызываться рабочим вместо контроллера.