心跳是 并发进程向外界发出信号的一种方式 两种不同类型的心跳: 1。 在一段时间间隔内发出的心跳 2。 在工作单元开始时发出的心跳。
func main() {
doWork := func(done <-chan interface{}, pulseInterval time.Duration) (<-chan interface{}, <-chan time.Time) {
heartbeat := make(chan interface{})
results := make(chan time.Time)
go func() {
defer close(heartbeat)
defer close(results)
//pulseInterval 是心跳间隔。
pulse := time.Tick(pulseInterval)
workGen := time.Tick(2*pulseInterval) //模拟滴答声的channel.
sendPulse := func() {
select{
case heartbeat <- struct {}{}:
default: //注意!! 可能没有人接收我们的心跳。
}
}
sendResult := func(r time.Time) {
for {
select{
case <- done:
return
case <- pulse:
sendPulse()
case results <- r:
return
}
}
}
for{
select{
case <-done:
return
case <-pulse:
sendPulse()
case r := <-workGen:
sendResult(r)
}
}
}()
return heartbeat, results
}
done := make(chan interface{})
//设置了超时时间,10s后 关闭。
time.AfterFunc(10*time.Second, func(){ close(done)})
const timeout = 2*time.Second
heartbeat, results := doWork(done, timeout/2)
for{
select{
case _, ok := <-heartbeat:
if ok == false{
return
}
fmt.Println("pulse")
case r, ok := <-results:
if ok == false{
return
}
fmt.Printf("results %v\n", r.Second())
case <-time.After(timeout):
return
}
}
}
//pulse
//pulse
//results 19
//pulse
//pulse
//results 21
//pulse
//pulse
//results 23
//pulse
//pulse
//results 25
//pulse
//pulse
//模拟异常的 goroutine 两次迭代后停止goroutine, 但却不关闭 heartbeat, results 这两个channel; 来模拟产生异常的goroutine
func doWork2 (done <-chan interface{}, pulseInterval time.Duration)(<-chan interface{}, <-chan time.Time){
heartbeat := make(chan interface{})
results := make(chan time.Time)
go func() {
pulse := time.Tick(pulseInterval)
//workGen :=time.Tick(2*pulseInterval)
workGen :=time.Tick(1*pulseInterval)
sendPulse := func() {
select{
case heartbeat <- struct{}{}:
default:
}
}
sendResult := func(r time.Time) {
for{
select{
case <-pulse:
sendPulse()
case results <- r:
return
}
}
}
for i:=0; i<2; i++{
select{
case <-done:
return
case <-pulse:
sendPulse()
case r := <-workGen:
sendResult(r)
}
}
}()
return heartbeat, results
}
func main(){
done := make(chan interface{})
time.AfterFunc(10*time.Second, func(){close(done)})
const timeout = 10*time.Second //2 s
heartbeat, results := doWork2(done, timeout/2)
for{
select{
case _,ok := <-heartbeat:
if ok == false{
return
}
fmt.Println("pulse")
case r, ok := <- results:
if ok == false{
return
}
fmt.Printf("results %v\n", r)
case <- time.After(timeout):
fmt.Println("worker goroutine is not healthy!")
return
}
}
}
//pulse
//pulse
//worker goroutine is not healthy!
//pulse
//results 2019-04-28 23:39:32.402908 +0800 CST m=+5.001073022
//worker goroutine is not healthy!
/**
在工作单元开始时发出心跳的例子:这种写法真正的好处是 在于测试
func doWork3(done <- chan interface{})(<-chan interface{}, <- chan int){
heartbeatStream := make(chan interface{}, 1)
workStream := make(chan int)
go func(){
defer close(heartbeatStream)
defer close(workStream)
for i:=0; i<10; i++{
select{
case heartbeatStream <- struct {}{}:
default:
}
select{
case <- done:
return
case workStream <- rand.Intn(10) :
}
}
}()
return heartbeatStream, workStream
}
func main(){
done := make(chan interface{})
defer close(done)
heartbeat, results := doWork3(done)
for{
select{
case _, ok := <- heartbeat:
if ok{
fmt.Println("pulse")
} else {
return
}
case r, ok := <- results:
if ok {
fmt.Printf("results %v\n", r)
} else {
return
}
}
}
}
//pulse
//results 1
//pulse
//results 7
//pulse
//results 7
//pulse
//results 9
//pulse
//results 1
//pulse
//results 8
//pulse
//results 5
//pulse
//results 0
//pulse
//results 6
//pulse
//results 0
不好的 为了能测试的 写法
func DoWork4(done <-chan interface{}, nums ...int)(<-chan interface{}, <-chan int){
heartbeat := make(chan interface{}, 1)
intStream := make(chan int)
go func(){
defer close(heartbeat)
defer close(intStream)
//模拟在goroutine 开始工作之前的某种延迟。 在实践中可能是各种各样问题。可能是cpu 负载过高,磁盘抢占,
//网络延迟 造成的延迟。
time.Sleep(2*time.Second)
for _,n := range nums{
select{
case heartbeat <- struct{}{}:
default:
}
select{
case <- done:
return
case intStream <- n:
}
}
}()
return heartbeat, intStream
}
func TestDoWork4_GeneratesAllNumbers(t *testing.T) {
done := make(chan interface{})
defer close(done)
intSlice := []int{0,1,2,3,5}
_, results := DoWork4(done, intSlice...)
for i, expected := range intSlice{
select{
case r:= <- results:
if r != expected{
t.Errorf(
"index %v: expected %v, but received %v,",
i,
expected,
r,
)
}
//这里设置一个合理的超时时间,避免goroutine 陷入 死锁
case <-time.After(1*time.Second) :
t.Fatal("test timed out")
}
}
}
//=== RUN TestDoWork4_GeneratesAllNumbers
//--- FAIL: TestDoWork4_GeneratesAllNumbers (1.00s)
//heart_test.go:29: test timed out
//FAIL
/**
这个测试不够好,因为它是非确定性的。 再者,增加超时时间,这意味着需要很长时间才知道执行失败,从而减慢我们的测试过程
然而利用心跳可以 很容易解决这个问题。这是一个确定性的测试:
func TestDoWork4(t *testing.T) {
done := make(chan interface{})
defer close(done)
intSlice := []int{0,1,2,3,4,5}
heartbeat, reuslts := DoWork4(done, intSlice...)
<- heartbeat
i:=0
for r := range reuslts{
if expected := intSlice[i]; r!= expected{
t.Errorf("index %v: expected %v, but received %v,", i, expected, r)
}
i++
}
}
/**
分析:因为有了心跳,就可以安全的编写测试 而不需要加入超时机制。
更完美的间隔心跳 :Dowork5
func DoWork5(done <-chan interface{}, pulseInterval time.Duration, nums ...int)(<-chan interface{}, <-chan int){
heartbeat := make(chan interface{}, 1)
intStream := make(chan int)
go func() {
defer close(heartbeat)
defer close(intStream)
time.Sleep(2*time.Second)
pulse := time.Tick(pulseInterval)
numLoop:
for _, n := range nums{
for {
select{
case <- done:
return
case <-pulse:
select{
case heartbeat <- struct {}{}:
fmt.Println("sendHeart")
default:
}
//fmt.Println("jump")
//case intStream <- n:
case intStream <- realWork(n):
//fmt.Println("work")
//time.Sleep(3*time.Second)
continue numLoop
}
}
}
}()
return heartbeat, intStream
}
func realWork(n int) int{
fmt.Println("n : ", n)
fmt.Println("work start")
time.Sleep(3*time.Second)
fmt.Println("work end")
return 2*n
}
func main(){
done := make(chan interface{})
defer close(done)
intSlice := []int{0,1,2,3,5}
const timeout = 2*time.Second
heartbeat, results := DoWork5(done, timeout/2, intSlice...)
for{
select{
case _, ok := <- heartbeat:
if ok{
fmt.Println("pulse")
} else {
return
}
case r, ok := <- results:
if ok {
fmt.Printf("results %v\n", r)
} else {
return
}
}
}
}
//n : 0
//work start
//work end
//sendHeart
//n : 0
//work start
//pulse
//work end
//sendHeart
//n : 0
//work start
//pulse
//work end
//sendHeart
//pulse
//n : 0
//work start
//work end
//sendHeart
//n : 0
//work start
//pulse
//work end
//sendHeart
//n : 0
//work start
//pulse
//work end
//sendHeart
//n : 0
//work start
//pulse
//work end
//n : 1
//work start
//results 0
//work end
//sendHeart
//n : 1
//work start
//pulse
//work end
//n : 2
//work start
//results 2
//work end
//sendHeart
//n : 2
//work start
//pulse
//work end
//sendHeart
//n : 2
//work start
//pulse
//work end
//n : 3
//work start
//results 4
//work end
//n : 5
//work start
//results 6
//work end
//sendHeart
//n : 5
//work start
//pulse
//work end
//sendHeart
//n : 5
//work start
//pulse
//work end
//results 10
/**
分析 为什么 n 会重复多次 ,难道 <- n ; n不适合 用函数? n多次 是因为 没有成功将 返回值 放入channel ?
*/
func TestDoWork5(t *testing.T) {
done := make(chan interface{})
defer close(done)
intSlice := []int{0,1,2,3,5}
const timeout = 2*time.Second
heartbeat, results := DoWork5(done, timeout/2, intSlice...)
//等待第一次心跳到底,来确认goroutine 已经进入了循环
<-heartbeat
fmt.Println("get first heart")
//r, ok:= <- results
//if ok {
// fmt.Printf("get result %v ", r)
//}
i:=0
for{
fmt.Println("get start for")
select{
case r, ok:= <- results:
fmt.Println("work result")
//time.Sleep(3*time.Second);
if ok == false{
return
} else if expected := intSlice[i]; r!= expected{
t.Errorf(
"index %v: expected %v, but received %v,",
i,
expected,
r,
)
}
i++
//接收心跳防止 超时
case <- heartbeat:
fmt.Println("pulse")
case <- time.After(timeout):
t.Fatal("test timed out")
}
}
}
//=== RUN TestDoWork5
//work start
//work end
//sendHeart
//work start
//get first heart
//get start for
//--- FAIL: TestDoWork5 (7.01s)
//heart_test.go:113: test timed out
//FAIL