Go并发模式/设计模式之心跳

心跳是 并发进程向外界发出信号的一种方式

两种不同类型的心跳:
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

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值