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 main

import (
"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:2023-01-09 13:38:25.7887222 +0800 CST m=+0.004199401
t1:2023-01-09 13:38:27.7946783 +0800 CST m=+2.010155501
*/

还有一种写法就是 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 main

import (
"fmt"
"time"
)

func main() {
timer := time.After(time.Second * 2)//创建定时器
fmt.Printf("time:%v\n", time.Now())
t1 := <-timer
fmt.Printf("t1:%v", t1)
}
/*
time:2023-01-09 13:53:35.8115941 +0800 CST m=+0.003149301
t1:2023-01-09 13:53:37.8138489 +0800 CST m=+2.005404101
*/

结束定时任务

使用 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 main

import (
"fmt"
"time"
)

var ticker *time.Ticker

func 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()
}
/*
Now 2023-01-09 14:15:59.9091662 +0800 CST m=+1.004786701
Now 2023-01-09 14:16:00.9206987 +0800 CST m=+2.016319201
Now 2023-01-09 14:16:01.9190377 +0800 CST m=+3.014658201
Now 2023-01-09 14:16:02.9100574 +0800 CST m=+4.005677901
Now 2023-01-09 14:16:03.9197413 +0800 CST m=+5.015361801
Now 2023-01-09 14:16:04.9170794 +0800 CST m=+6.012699901
*/

定时周期性随机发送数据

很简单,使用 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 main

import (
"fmt"
"time"
)

var IntChan = make(chan int)
var ticker *time.Ticker

func 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)
}
}
/*
recv: 2
sum: 2
recv: 2
sum: 4
recv: 1
sum: 5
recv: 3
sum: 8
recv: 2
sum: 10
...
*/

原子操作

指不能被打断的操作。

atomic提供的原子操作能够确保任一时刻只有一个goroutine对变量进行操作,善用atomic能够避免程序中出现大量的锁操作。

atomic常见操作有:

  • 载入
  • 比较并交换cas
  • 交换
  • 存储

下面将分别介绍这些操作。

增减操作

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 main

import (
"fmt"
"sync"
"sync/atomic"
)

var wg sync.WaitGroup
var lock sync.Mutex
var k int64
var locktest bool = false //change here

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)
}
/*
last value:0
*/

把 locktest 置为 false,我们可以发现最终值永远为0。

其余操作遇到再说吧,知道最基本的剩下的基本都可以发挥的。