Go的第16课——timer,ticker
Timer 开启定时任务 Timer顾名思义,就是定时器的意思,可以实现一些定时操作,内部也是通过channel来实现的。
虽然可以使用 sleep
实现,但是这个对象可以让我们细化到某个操作到某个操作之间间隔确定的时间,比如我的顺序是要求 A B C,要求 A 和 C 至少间隔两秒的时间。如果使用 sleep 的话就不太方便操作,因为 B 也会消耗一定时间,如果直接 sleep 2 可能会导致等待多余的时间,那么我们就可以在A事件开始后设定 timer,在 C 开始前读出 timer 的 channel,这样就可以保证了,而且写法非常简便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "time" ) func main () { timer := time.NewTimer(time.Second * 2 ) fmt.Printf("time:%v\n" , time.Now()) t1 := <-timer.C fmt.Printf("t1:%v" , t1) }
还有一种写法就是 time.After(s)
等同于 time.NewTimer(s).C
。
上面的代码等价下来就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "time" ) func main () { timer := time.After(time.Second * 2 ) fmt.Printf("time:%v\n" , time.Now()) t1 := <-timer fmt.Printf("t1:%v" , t1) }
结束定时任务 使用 timer 对象的 Stop 方法,如果子协程正在阻塞的时候被 Stop 则协程结束。
如果子协程读一个已经被 Stop 的 timer 的 time channel 则也会结束,如果是主协程被结束则导致死锁。
重新设置定时任务 使用 timer 对象的 reset 方法改变定时的时间,不是根据当前时间来算,而是根据被创建的时间来算,比如当前等了 1.5 秒,我 Reset(1) 就会马上放开阻塞的状态。
Ticker Timer只执行一次,Ticker可以周期的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "fmt" "time" ) var ticker *time.Tickerfunc PrintNow () { fmt.Printf("Now %v\n" , time.Now()) } func test () { var count = 0 for _ = range ticker.C { PrintNow() if count == 5 { ticker.Stop break } count++ } } func main () { ticker = time.NewTicker(time.Second * 1 ) test() }
定时周期性随机发送数据 很简单,使用 select 配合 channel 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "time" ) var IntChan = make (chan int )var ticker *time.Tickerfunc send () { for _ = range ticker.C { select { case IntChan <- 1 : case IntChan <- 2 : case IntChan <- 3 : case IntChan <- 4 : case IntChan <- 5 : } } } func main () { ticker = time.NewTicker(time.Second * 1 ) go send() var sum = 0 for { v := <-IntChan fmt.Printf("recv: %v\n" , v) sum += v fmt.Printf("sum: %v\n" , sum) } }
原子操作 指不能被打断的操作。
atomic提供的原子操作能够确保任一时刻只有一个goroutine对变量进行操作,善用atomic能够避免程序中出现大量的锁操作。
atomic常见操作有:
下面将分别介绍这些操作。
增减操作 atomic包中提供了如下以Add为前缀的增减操作:
1 2 3 4 5 func AddInt32 (addr *int32 , delta int32 ) (new int32 )func AddInt64 (addr *int64 , delta int64 ) (new int64 )func AddUint32 (addr uint32 , delta uint32 ) (new uint32 )func AddUint64 (addr uint64 , delta uint64 ) (new uint64 )func AddUintptr (addr uintptr , delta uintptr ) (new uintptr )
就拿之前锁那一章的代码来测试,我们把加减使用这些接口替代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport ( "fmt" "sync" "sync/atomic" ) var wg sync.WaitGroupvar lock sync.Mutexvar k int64 var locktest bool = false func add () { defer wg.Done() if locktest { lock.Lock() defer lock.Unlock() } atomic.AddInt64(&k, 1 ) } func sub () { defer wg.Done() if locktest { lock.Lock() defer lock.Unlock() } atomic.AddInt64(&k, -1 ) } func main () { for i := 0 ; i < 1000 ; i++ { wg.Add(2 ) go add() go sub() } wg.Wait() fmt.Printf("last value:%v\n" , k) }
把 locktest 置为 false,我们可以发现最终值永远为0。
其余操作遇到再说吧,知道最基本的剩下的基本都可以发挥的。