这个函数经常用于轮询客户端缓存是否初始化成功。
周期性检查
- 周期性检查:
time.NewTicker会周期性会往tick.C中输入一个值 - 超时设置:
timer := time.NewTimer(timeout)设置了超时时间,从运行这个函数开始算起的时间间隔。
// poller returns a WaitFunc that will send to the channel every interval until
// timeout has elapsed and then closes the channel.
//
// Over very short intervals you may receive no ticks before the channel is
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
// it would be the caller's responsibility to close the done channel.
// Failure to do so would result in a leaked goroutine.
//
// Output ticks are not buffered. If the channel is not ready to receive an
// item, the tick is skipped.
func poller(interval, timeout time.Duration) WaitFunc {
return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
ch := make(chan struct{})
go func() {
defer close(ch)
tick := time.NewTicker(interval)
defer tick.Stop()
var after <-chan time.Time
if timeout != 0 {
// time.After is more convenient, but it
// potentially leaves timers around much longer
// than necessary if we exit early.
timer := time.NewTimer(timeout)
after = timer.C
defer timer.Stop()
}
for {
select {
case <-tick.C:
// If the consumer isn't ready for this signal drop it and
// check the other channels.
select {
case ch <- struct{}{}:
default:
}
case <-after:
return
case <-done:
return
}
}
}()
return ch
})
}
Context的用法
这里是一个很经典的context.WithCancel的使用场景, 传入的ParentCh如果停止了, 则调用Context的cancel函数,其他go routine就会通过ctx.Done()感知到退出信号。
// contextForChannel derives a child context from a parent channel.
//
// The derived context's Done channel is closed when the returned cancel function
// is called or when the parent channel is closed, whichever happens first.
//
// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked.
func contextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
select {
case <-parentCh:
cancel()
case <-ctx.Done():
}
}()
return ctx, cancel
}
为了演示这个退出信号怎么传入,下面Code部分的main函数可改成:
这个样例会:
- 接受系统的Ctrl+C信号,然后传递给
stopCh - PollImmediateUntil退出轮询,然后传递给
stopGrace stopGrace被for select语句捕捉到退出主流程。
Unbuffered Channel写入会阻塞,等待其他go routine读取; 而 Buffered Channel 到达容量前都不会阻塞;
func main() {
// Buffered Channel Group
stop:=make(chan os.Signal,1)
stopCh:=make(chan struct{},1)
// 优雅停机
stopGrace:=make(chan struct{},1)
defer fmt.Println("stopped,close signal")
defer close(stop)
defer close(stopCh)
defer close(stopGrace)
// catch up ctrl + c interrupt
signal.Notify(stop,os.Interrupt)
go func() {
err:=PollImmediateUntil(3*time.Second, func() (done bool, err error) {
fmt.Println("[INFO] doing something time consuming")
if rand.Intn(10) % 3 == 0{
// mimic do something time consuming
fmt.Println("success sync the cache")
time.Sleep(1*time.Second)
return true,nil
}
return false, nil
},stopCh)
if err != nil {
fmt.Print(err.Error())
}
stopGrace<- struct{}{}
}()
for {
select {
case s:= <-stop:
fmt.Println("got signal:",s)
stopCh<- struct{}{}
case <-stopGrace:
fmt.Println("stop gracefully")
return
default:
}
}
}
Code
下面这个是将其从源码拿出来,可作为工具函数使用。
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"time"
)
func main() {
stopCh:=make(chan struct{})
err:=PollImmediateUntil(3*time.Second, func() (done bool, err error) {
fmt.Println("[INFO] doing something time consuming")
if rand.Intn(10) % 3 == 0{
// mimic do something time consuming
time.Sleep(1*time.Second)
return true,nil
}
return false, nil
},stopCh)
if err != nil {
fmt.Print(err.Error())
}
}
// ConditionFunc returns true if the condition is satisfied, or an error
// if the loop should be aborted.
type ConditionFunc func() (done bool, err error)
// WaitFunc creates a channel that receives an item every time a test
// should be executed and is closed when the last test should be invoked.
type WaitFunc func(done <-chan struct{}) <-chan struct{}
// ErrWaitTimeout is returned when the condition exited without success.
var ErrWaitTimeout = errors.New("timed out waiting for the condition")
// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed.
//
// PollImmediateUntil runs the 'condition' before waiting for the interval.
// 'condition' will always be invoked at least once.
func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
// 先试一次,如果不行,开始周期性轮询
done, err := condition()
if err != nil {
return err
}
if done {
return nil
}
select {
case <-stopCh:
return ErrWaitTimeout
default:
return PollUntil(interval, condition, stopCh)
}
}
// PollUntil tries a condition func until it returns true, an error or stopCh is
// closed.
//
// PollUntil always waits interval before the first run of 'condition'.
// 'condition' will always be invoked at least once.
func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
ctx, cancel := contextForChannel(stopCh)
defer cancel()
return WaitFor(poller(interval, 0), condition, ctx.Done())
}
// contextForChannel derives a child context from a parent channel.
//
// The derived context's Done channel is closed when the returned cancel function
// is called or when the parent channel is closed, whichever happens first.
//
// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked.
func contextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
select {
case <-parentCh:
cancel()
case <-ctx.Done():
}
}()
return ctx, cancel
}
// WaitFor continually checks 'fn' as driven by 'wait'.
//
// WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value
// placed on the channel and once more when the channel is closed. If the channel is closed
// and 'fn' returns false without error, WaitFor returns ErrWaitTimeout.
//
// If 'fn' returns an error the loop ends and that error is returned. If
// 'fn' returns true the loop ends and nil is returned.
//
// ErrWaitTimeout will be returned if the 'done' channel is closed without fn ever
// returning true.
//
// When the done channel is closed, because the golang `select` statement is
// "uniform pseudo-random", the `fn` might still run one or multiple time,
// though eventually `WaitFor` will return.
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
stopCh := make(chan struct{})
defer close(stopCh)
c := wait(stopCh)
for {
select {
case _, open := <-c:
ok, err := fn()
if err != nil {
return err
}
if ok {
return nil
}
if !open {
return ErrWaitTimeout
}
case <-done:
return ErrWaitTimeout
}
}
}
// poller returns a WaitFunc that will send to the channel every interval until
// timeout has elapsed and then closes the channel.
//
// Over very short intervals you may receive no ticks before the channel is
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
// it would be the caller's responsibility to close the done channel.
// Failure to do so would result in a leaked goroutine.
//
// Output ticks are not buffered. If the channel is not ready to receive an
// item, the tick is skipped.
func poller(interval, timeout time.Duration) WaitFunc {
return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
ch := make(chan struct{})
go func() {
defer close(ch)
tick := time.NewTicker(interval)
defer tick.Stop()
var after <-chan time.Time
if timeout != 0 {
// time.After is more convenient, but it
// potentially leaves timers around much longer
// than necessary if we exit early.
timer := time.NewTimer(timeout)
after = timer.C
defer timer.Stop()
}
for {
select {
case <-tick.C:
// If the consumer isn't ready for this signal drop it and
// check the other channels.
select {
case ch <- struct{}{}:
default:
}
case <-after:
return
case <-done:
return
}
}
}()
return ch
})
}