これは、@ Jimtによって記述されたGoのワーカーとコントローラーモードの良い例です。「 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])
}
}
ただし、このコードには問題もあります。worker()
が終了したときにworkers
のワーカーチャネルを削除すると、デッドロックが発生します。
close(workers[i])
の場合、goが閉じたチャネルに書き込むことができないため、次回コントローラが書き込みを行うとパニックが発生します。ミューテックスを使用して保護すると、worker
がチャネルから何も読み取っていないため、workers[i] <- Running
に固定され、書き込みがブロックされ、ミューテックスがデッドロックを引き起こします。回避策としてチャネルに大きなバッファを与えることもできますが、それでは十分ではありません。
したがって、これを解決する最善の方法は、worker()
終了時にチャネルを閉じることです。コントローラがチャネルを閉じていることを検出すると、それを飛び越えて何もしません。しかし、この状況でチャンネルが既に閉じられているかどうかを確認する方法が見つかりません。コントローラでチャネルを読み取ろうとすると、コントローラがブロックされる可能性があります。だから今のところとても混乱しています。
PS:発生したパニックを回復することは私が試したことですが、パニックを発生させたゴルーチンを閉じます。この場合、コントローラーになりますので、使い物になりません。
それでも、Goチームがこの機能をGoの次のバージョンに実装することは有用だと思います。
ハッキングされた方法で、発生したパニックを回復することによって書き込みを試みるチャネルに対して実行できます。ただし、読み取りチャネルを読み取らずに閉じているかどうかは確認できません。
どちらか
v <- c
)v, ok <- c
)v, ok <- c
)を読み取りますv <- c
)技術的には最後の1つだけがチャネルから読み取れませんが、それはほとんど役に立ちません。
対話せずにチャネルが開いているかどうかを知る必要がある安全なアプリケーションを作成する方法はありません。
やりたいことを行う最善の方法は、2つのチャネルを使用することです。1つは仕事用で、もう1つは状態を変更したいという意向を示します(それが重要な場合は状態の変更を完了します)。
チャンネルは安いです。セマンティクスをオーバーロードする複雑な設計はそうではありません。
[また]
<-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
ドキュメントから:
チャネルは、組み込み関数closeで閉じることができます。受信演算子の複数値の割り当てフォームは、チャネルが閉じる前に受信値が送信されたかどうかを報告します。
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)
}
たぶん私は何かを見逃しているかもしれませんが、これを処理するための簡単で正しい方法は、チャネルに「停止」を送信して(ゴールーチンを終了する)、チャネルを閉じてnilに設定することです。
閉じたチャネルを読み取らずに確認する必要があると思われる場合は、設計に問題があります。 (一時停止したワーカーの「ビジーループ」など、コードには他の問題があることに注意してください。)