治癒異常的goroutine
package main
import (
"log"
"time"
"os"
)
/*
治癒異常的goroutine:
在長期運行的後臺程序中,經常會有一些長時間運行的goroutine。
這些goroutine 經常處於阻塞狀態,等待數據以某種方式到達,然後喚醒它們,進行一些處理,再返回一些數據。
如果沒有外部干預,一個goroutine 很容易進入一個不正常的狀態,並且無法恢復。
我們需要建立一個機制來監控你的goroutine 是否處於健康的狀態是很有用的,
當他們變得異常時,就可以儘快重啓。
我們將這種重啓goroutine的過程稱爲治癒。
爲了治癒goroutine ,我們需要使用心跳模式來檢查我們正在監控的goroutine 是否活躍。
心跳的類型取決於你想要監控的內容,但是如果你的goroutine 有可能會產生活鎖,確保心跳包含某些信息,表明該goroutine 在正常的工作而不僅僅是活着。
我們把監控goroutine 的健康這段邏輯稱爲管理員,它監視一個管理區的goroutine,
如果有goroutine 變得不健康,管理員將負責重新啓動這個管理區的goroutine 。
*/
func main() {
log.Println("testhealing")
testhealing()
log.Println()
log.Println("testhealing2")
testhealink2()
}
func testhealing() {
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{}{
switch len(channels) {
case 0:
return nil
case 1:
return channels[0]
}
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2:
select {
case <-channels[0]:
case <-channels[1]:
}
default:
select{
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
case <-or(append(channels[3:],orDone)...):
}
}
}()
return orDone
}
type startGoroutineFn func(
done <-chan interface{},
pulseInterval time.Duration,
)(heartbeat <-chan interface{})
newSteward := func(
timeout time.Duration,
startGoroutine startGoroutineFn,
) startGoroutineFn {
return func(
done <-chan interface{},
pulseInterval time.Duration,
) (<-chan interface{}) {
heartbeat := make(chan interface{})
go func() {
defer close(heartbeat)
var wardDone chan interface{}
var wardHeartbeat <-chan interface{}
startWard := func() {
wardDone = make(chan interface{})
wardHeartbeat = startGoroutine(or(wardDone,done),timeout/2)
}
startWard()
pulse := time.Tick(pulseInterval)
monitorLoop:
for{
timeoutSignal := time.After(timeout)
for {
select {
case <-pulse:
select {
case heartbeat<- struct {}{}:
default:
}
case <-wardHeartbeat:
continue monitorLoop
case <-timeoutSignal:
log.Println("steward:ward unhealthy;restarting")
close(wardDone)
startWard()
continue monitorLoop
case <-done:
return
}
}
}
}()
return heartbeat
}
}
/*
· pulseInterval time.Duration,
)(heartbeat <-chan interface{})
在這裏,我們定義一個可以監控和重啓的goroutine 的信號,
我們看到了熟悉的channel ,以及來自心跳模式的pulseInterval 和heartbeat
·timeout time.Duration,
startGoroutine startGoroutineFn,
) startGoroutineFn {
在這,我們看到一個管理員監控goroutine 需要timeout 變量,
還有一個函數startGoroutine 來啓動他監控的goroutine
管理員本身會返回一個startGoroutineFn ,表示管理員本身也是可監控的
·startWard := func() {
在這裏,我們定義了一個閉包,他實現了一個統一的startWard 方法來啓動我們正在監視的goroutine
·wardDone = make(chan interface{})
這是我們創建的一個新的channel ,如果我們需要發出一個停止的信號,就會通過他傳入goroutine 中
·wardHeartbeat = startGoroutine(or(wardDone,done),timeout/2)
在這,我們啓動將要監控的goroutine ,如果管理員被停止了,或者管理員想要停止goroutine
我們希望這些信息都能傳遞給管理區裏的goroutine ,所有我們把兩個done channel 用邏輯 or 包裝一下。
我們設定心跳間隔時間是超時時間的一半
·timeoutSignal := time.After(timeout)
for {
這裏我們的內部循環,他確保管理員可以發出自己的心跳
·case <-wardHeartbeat:
在這,如果我們收到goroutine 的心跳,將繼續執行監控的循環
·case <-timeoutSignal:
這裏如果我們在暫停期間沒有收到管理區裏goroutine 的心跳,
我們會要求管理區裏的goroutine 停下來,並啓動一個新的goroutine ,然後我們繼續監控
*/
log.SetOutput(os.Stdout)
log.SetFlags(log.Ltime|log.LUTC)
doWork := func(
done <-chan interface{},
_ time.Duration,
) <-chan interface{}{
log.Println("ward:hello, i,m irresponsible!")
go func() {
<-done
log.Println("ward:i am halting.")
}()
return nil
}
doWorkWithSteward := newSteward(1*time.Second,doWork)
done := make(chan interface{})
time.AfterFunc(5*time.Second, func() {
log.Println("main:halting steward and ward.")
close(done)
})
for range doWorkWithSteward(done,2*time.Second){
}
log.Println("Done")
/*
·go func() {
<-done
在這裏,我們看到這個goroutine 沒有做任何事情,只是等待被取消,他也沒有發出任何心跳
·doWorkWithSteward := newSteward(4*time.Second,doWork)
這裏創建了一個函數,爲goroutine doWork 創建一個管理員,我們設置doWork 的超時時間爲4秒
·time.AfterFunc(9*time.Second, func() {
在這我們設置9秒後停止管理員和goroutine ,這樣我們的測試就會結束。
·for range doWorkWithSteward(done,4*time.Second){
最後,我們啓動管理員並在其心跳範圍內防止我們的測試停止
*/
}
func testhealink2(){
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{}{
switch len(channels) {
case 0:
return nil
case 1:
return channels[0]
}
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2:
select {
case <-channels[0]:
case <-channels[1]:
}
default:
select{
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
case <-or(append(channels[3:],orDone)...):
}
}
}()
return orDone
}
orDone := func(
done <-chan interface{},
valueStream <-chan interface{},
)<-chan interface{}{
doneStream := make(chan interface{})
go func() {
defer close(doneStream)
for{
select {
case <-done:
return
case val,ok := <-valueStream:
if ok == false{
return
}
doneStream<-val
}
}
}()
return doneStream
}
//橋接channel
bridge := func(
done <-chan interface{},
chanStream <-chan <-chan interface{},
)<-chan interface{}{
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for{
var stream <-chan interface{}
select{
case maybeStream,ok := <-chanStream:
if ok == false{
return
}
stream = maybeStream
case <-done:
return
}
for val := range orDone(done,stream){
select{
case <-done:
case valueStream <-val:
}
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
intStream <-chan interface{},
num int,
) <-chan interface {}{
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i :=0;i<num;i++{
select {
case <-done:
return
case takeStream<-<-intStream:
}
}
}()
return takeStream
}
type startGoroutineFn func(
done <-chan interface{},
pulseInterval time.Duration,
)(heartbeat <-chan interface{})
newSteward := func(
timeout time.Duration,
startGoroutine startGoroutineFn,
) startGoroutineFn {
return func(
done <-chan interface{},
pulseInterval time.Duration,
) (<-chan interface{}) {
heartbeat := make(chan interface{})
go func() {
defer close(heartbeat)
var wardDone chan interface{}
var wardHeartbeat <-chan interface{}
startWard := func() {
wardDone = make(chan interface{})
wardHeartbeat = startGoroutine(or(wardDone,done),timeout/2)
}
startWard()
pulse := time.Tick(pulseInterval)
monitorLoop:
for{
timeoutSignal := time.After(timeout)
for {
select {
case <-pulse:
select {
case heartbeat<- struct {}{}:
default:
}
case <-wardHeartbeat:
continue monitorLoop
case <-timeoutSignal:
log.Println("steward:ward unhealthy;restarting")
close(wardDone)
startWard()
continue monitorLoop
case <-done:
return
}
}
}
}()
return heartbeat
}
}
doWorkFn := func(
done <-chan interface{},
intList ...int,
) (startGoroutineFn,<-chan interface{}){
intChanStream := make(chan (<-chan interface{}))
intStream := bridge(done,intChanStream)
doWork := func(
done <-chan interface{},
pulseInterval time.Duration,
) <-chan interface {}{
intStream := make(chan interface{})
heartbeat := make(chan interface{})
go func() {
defer close(intStream)
select {
case intChanStream <- intStream:
case <-done:
return
}
pulse := time.Tick(pulseInterval)
for{
valueLoop:
for _,intVal := range intList{
if intVal < 0{
log.Printf("negative value: %v\n",intVal)
return
}
for{
select {
case <- pulse:
select {
case heartbeat<- struct {}{}:
default:
}
case intStream<-intVal:
continue valueLoop
case <-done:
return
}
}
}
}
}()
return heartbeat
}
return doWork,intStream
}
/*
·) (startGoroutineFn,<-chan interface{}){
在這裏,我們填入一些我們管理區所需的參數,並返回我們管理區用來通信的channel
·intChanStream := make(chan (<-chan interface{}))
這裏我們創建了作爲橋接模式一部分的channel
·) <-chan interface {}{
我們創建一個將被管理員啓動和監控的閉包
·intStream := make(chan interface{})
這是我們實例化channel 的地方,我們將利用這些channel 與管理區中的goroutine 通信
·case intChanStream <- intStream:
這裏我們把我們即將用來通信的channel 通知給bridge
·if intVal < 0{
log.Printf("negative value: %v\n",intVal)
當我們處理到負數時,在這裏打印出一個錯誤信息,然後從goroutine 中返回
*/
log.SetFlags(log.Ltime|log.LUTC)
log.SetOutput(os.Stdout)
done := make(chan interface{})
defer close(done)
doWork,intStream := doWorkFn(done,1,2,-1,3,4,5)
doWorkWithSteward := newSteward(1*time.Millisecond,doWork)
doWorkWithSteward(done,1*time.Hour)
for intVal := range take(done,intStream,6){
log.Printf("Received:%v\n",intVal)
}
/*
·doWork,intStream := doWorkFn(done,1,2,-1,3,4,5)
這裏我們創建管理區的函數,允許他結束我們的可變整數切片,並返回一個用來返回的流
·doWorkWithSteward := newSteward(1*time.Millisecond,doWork)
我們創建一個管理員,用來監聽doWork 閉包,
因爲我們希望能儘快知道失敗的信息,所以我們將監聽時間間隔設置爲1 毫秒
·doWorkWithSteward(done,1*time.Hour)
我們通知管理員啓動管理區並開始監控
·for intVal := range take(done,intStream,6){
最後,使用我們開發的一段管道代碼,並從intStream 中取出前6 個值
*/
}