채널을 읽지 않고 닫혔는지 여부를 확인하는 방법은 무엇입니까?
이것은 @Jimt가 작성한 Go의 작업자 및 컨트롤러 모드의 좋은 예입니다 . " golang에서 다른 goroutine을 일시 중지하고 다시 시작할 수있는 우아한 방법이 있습니까? "
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()
종료 할 때 채널을 닫는 것입니다. 컨트롤러가 닫힌 채널을 발견하면 해당 채널을 건너 뛰고 아무것도하지 않습니다. 하지만이 상황에서 채널이 이미 닫혔는지 여부를 확인하는 방법을 찾을 수 없습니다. 컨트롤러에서 채널을 읽으려고하면 컨트롤러가 차단 될 수 있습니다. 그래서 지금은 매우 혼란 스럽습니다.
추신 : 제기 된 패닉을 복구하는 것은 내가 시도한 것이지만 패닉을 일으킨 고 루틴을 닫을 것입니다. 이 경우 컨트롤러가되므로 쓸모가 없습니다.
그래도 Go 팀이 다음 버전의 Go에서이 기능을 구현하는 것이 유용하다고 생각합니다.
해키 방식으로 제기 된 패닉을 복구하여 쓰기를 시도하는 채널에 대해 수행 할 수 있습니다. 그러나 읽기 채널이 읽히지 않고 닫혀 있는지 확인할 수 없습니다.
당신은
- 결국 "true"값을 읽습니다 (
v <- c
). - "참"값 및 '닫히지 않음'표시기 (
v, ok <- c
) 읽기 - 0 값 및 '닫힌'표시기 (
v, ok <- c
) 읽기 - 채널에서 영구적으로 읽기 (
v <- c
) 차단됩니다.
기술적으로 마지막 하나만 채널에서 읽지 않지만 거의 사용되지 않습니다.
채널과 상호 작용하지 않고 채널이 열려 있는지 여부를 알아야하는 안전한 애플리케이션을 작성할 수있는 방법은 없습니다.
원하는 작업을 수행하는 가장 좋은 방법은 두 개의 채널을 사용하는 것입니다. 하나는 작업을위한 것이고 다른 하나는 상태를 변경하려는 욕구를 나타내는 것입니다 (중요한 경우 해당 상태 변경 완료).
채널은 저렴합니다. 복잡한 디자인 오버로딩 의미론은 그렇지 않습니다.
[또한]
<-time.After(1e9)
작성하는 데 정말 혼란스럽고 분명하지 않은 방법입니다.
time.Sleep(time.Second)
일을 단순하게 유지하면 모든 사람이 이해할 수 있습니다.
이 답변이 너무 늦었다는 것을 알고 있습니다.이 솔루션을 작성했습니다. 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
문서에서 :
내장 기능 닫기로 채널을 닫을 수 있습니다. 수신 연산자의 다중 값 할당 양식은 채널이 닫히기 전에 수신 된 값이 전송되었는지 여부를보고합니다.
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
}
}
아마도 내가 뭔가를 놓치고 있지만 이것을 처리하는 간단하고 올바른 방법은 "중지됨"을 채널 (go-routine 종료)에 보내고 채널을 닫고 nil로 설정하는 것입니다.
If you think you need to check for a closed channel without reading it then there is a problem with your design. (Note that there are other problems with the code such as the "busy looping" of paused workers.)
Well, you can use default
branch to detect it, for a closed channel will be selected, for example: the following code will select default
, channel
, channel
, the first select is not blocked.
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)
}
it's easier to check first if the channel has elements, that would ensure the channel is alive.
func isChanClosed(ch chan interface{}) bool {
if len(ch) == 0 {
select {
case _, ok := <-ch:
return !ok
}
}
return false
}
If you listen this channel you always can findout that channel was closed.
case state, opened := <-ws:
if !opened {
// channel was closed
// return or made some final work
}
switch state {
case Stopped:
But remember, you can not close one channel two times. This will raise panic.
'IT박스' 카테고리의 다른 글
0에 가까운 부동 값이 0으로 나누기 오류를 일으킬 수 있습니까? (0) | 2020.12.08 |
---|---|
Rails 콘솔을 사용하여 테이블에서 열을 제거하는 방법 (0) | 2020.12.08 |
패키지 버전을 setup.py 및 패키지와 공유하는 올바른 방법은 무엇입니까? (0) | 2020.12.08 |
Julia에서 @printf가 함수 대신 매크로 인 이유는 무엇입니까? (0) | 2020.12.08 |
Android 지원 중단 아파치 모듈 (HttpClient, HttpResponse 등) (0) | 2020.12.08 |