-
Notifications
You must be signed in to change notification settings - Fork 2
/
go_timer.html
232 lines (223 loc) · 13.6 KB
/
go_timer.html
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<!-- 2021-01-23 Sat 18:03 -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>go中的定时器timer</title>
<meta name="generator" content="Org mode" />
<link rel="stylesheet" type="text/css" href="./css/org-css.css"/>
<link rel="stylesheet" type="text/css" href="./css/gitalk.css"/> <script src="./js/gitalk.min.js"></script>
</head>
<body>
<div id="org-div-home-and-up">
<a accesskey="h" href="index.html"> UP </a>
|
<a accesskey="H" href="index.html"> HOME </a>
</div><div id="content">
<h1 class="title">go中的定时器timer</h1>
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#orgc66beaf">1. 使用方法</a></li>
<li><a href="#org56e722c">2. 内部实现</a></li>
<li><a href="#orgc13592d">3. 其他实现方法</a></li>
</ul>
</div>
</div>
<div id="outline-container-orgc66beaf" class="outline-2">
<h2 id="orgc66beaf"><span class="section-number-2">1</span> 使用方法</h2>
<div class="outline-text-2" id="text-1">
<p>
golang中定时器的使用接口在time包中,其中最主要两个接口是time.After跟,time.AfterFunc其中第一个After接口返回一个chan Time, 当时间到时可以读出Timer, AfterFunc接受一个方法,当时间到时执行这个方法。
</p>
<div class="org-src-container">
<pre class="src src-go"><span style="color: #89DDFF;">package</span> main
<span style="color: #89DDFF;">import</span> (
<span style="color: #c3e88d;">"time"</span>
<span style="color: #c3e88d;">"fmt"</span>
)
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">main</span>() {
<span style="color: #ffcb6b;">a</span> := time.<span style="color: #82aaff;">After</span>(2 * time.Second)
<- a
fmt.<span style="color: #82aaff;">Println</span>(<span style="color: #c3e88d;">"timer receive"</span>)
time.<span style="color: #82aaff;">AfterFunc</span>(2 * time.Second, <span style="color: #89DDFF;">func</span>(){
fmt.<span style="color: #82aaff;">Println</span>(<span style="color: #c3e88d;">"timer receive"</span>)
})
}
</pre>
</div>
<p>
After其实跟AfterFunc差不多,只不过其中的func参数是先通道中写数据
</p>
</div>
</div>
<div id="outline-container-org56e722c" class="outline-2">
<h2 id="org56e722c"><span class="section-number-2">2</span> 内部实现</h2>
<div class="outline-text-2" id="text-2">
<p>
由于After跟AfterFunc差不多,这里主要看看AfterFunc的实现
</p>
<div class="org-src-container">
<pre class="src src-go"> <span style="color: #676E95;">//</span><span style="color: #676E95;">time/sleep.go</span>
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">AfterFunc</span>(<span style="color: #ffcb6b;">d</span> <span style="color: #c792ea;">Duration</span>, <span style="color: #ffcb6b;">f</span> <span style="color: #89DDFF;">func</span>()) *<span style="color: #c792ea;">Timer</span> {
<span style="color: #ffcb6b;">t</span> := &<span style="color: #c792ea;">Timer</span>{
<span style="color: #f78c6c;">r</span>: <span style="color: #c792ea;">runtimeTimer</span>{
<span style="color: #f78c6c;">when</span>: <span style="color: #82aaff;">when</span>(d),
<span style="color: #f78c6c;">f</span>: goFunc,
<span style="color: #f78c6c;">arg</span>: f,
},
}
<span style="color: #82aaff;">startTimer</span>(&t.r)
<span style="color: #89DDFF;">return</span> t
}
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">goFunc</span>(<span style="color: #ffcb6b;">arg</span> <span style="color: #89DDFF;">interface</span>{}, <span style="color: #ffcb6b;">seq</span> <span style="color: #c792ea;">uintptr</span>) {
<span style="color: #89DDFF;">go</span> arg.(<span style="color: #89DDFF;">func</span>())()
}
</pre>
</div>
<p>
AfterFunc很简单,就是把参数封装为runtimeTimer,然后启动timer(把timer添加到队列中), 这部分代码在runtime/time.go中,注意这里goFunc新启动了一个goroutine来执行用户的任务,这样用户的func就不会堵塞timer
</p>
<div class="org-src-container">
<pre class="src src-go"><span style="color: #676E95;">//</span><span style="color: #676E95;">runtime/time.go</span>
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">startTimer</span>(<span style="color: #ffcb6b;">t</span> *<span style="color: #c792ea;">timer</span>) {
<span style="color: #89DDFF;">if</span> raceenabled {
<span style="color: #82aaff;">racerelease</span>(unsafe.<span style="color: #82aaff;">Pointer</span>(t))
}
<span style="color: #82aaff;">addtimer</span>(t)
}
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">addtimer</span>(<span style="color: #ffcb6b;">t</span> *<span style="color: #c792ea;">timer</span>) {
<span style="color: #82aaff;">lock</span>(&timers.lock)
<span style="color: #82aaff;">addtimerLocked</span>(t)
<span style="color: #82aaff;">unlock</span>(&timers.lock)
}
<span style="color: #676E95;">// </span><span style="color: #676E95;">Add a timer to the heap and start or kick the timer proc.</span>
<span style="color: #676E95;">// </span><span style="color: #676E95;">If the new timer is earlier than any of the others.</span>
<span style="color: #676E95;">// </span><span style="color: #676E95;">Timers are locked.</span>
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">addtimerLocked</span>(<span style="color: #ffcb6b;">t</span> *<span style="color: #c792ea;">timer</span>) {
<span style="color: #676E95;">// </span><span style="color: #676E95;">when must never be negative; otherwise timerproc will overflow</span>
<span style="color: #676E95;">// </span><span style="color: #676E95;">during its delta calculation and never expire other runtime·timers.</span>
<span style="color: #89DDFF;">if</span> t.when < 0 {
t.when = 1<<63 - 1
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">添加time到全局timer</span>
t.i = <span style="color: #82aaff;">len</span>(timers.t)
timers.t = <span style="color: #82aaff;">append</span>(timers.t, t)
<span style="color: #676E95;">//</span><span style="color: #676E95;">使用最小堆算法维护timer队列</span>
<span style="color: #82aaff;">siftupTimer</span>(t.i)
<span style="color: #676E95;">//</span><span style="color: #676E95;">如果是第一个</span>
<span style="color: #89DDFF;">if</span> t.i == 0 {
<span style="color: #676E95;">// </span><span style="color: #676E95;">siftup moved to top: new earliest deadline.</span>
<span style="color: #676E95;">//</span><span style="color: #676E95;">如果在sleep中, 唤醒</span>
<span style="color: #89DDFF;">if</span> timers.sleeping {
timers.sleeping = <span style="color: #f78c6c;">false</span>
<span style="color: #82aaff;">notewakeup</span>(&timers.waitnote)
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">如果在调度中, 等待</span>
<span style="color: #89DDFF;">if</span> timers.rescheduling {
timers.rescheduling = <span style="color: #f78c6c;">false</span>
<span style="color: #82aaff;">goready</span>(timers.gp, 0)
}
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">如果timer还没创建,则创建</span>
<span style="color: #89DDFF;">if</span> <span style="color: #89DDFF; font-weight: bold;">!</span>timers.created {
timers.created = <span style="color: #f78c6c;">true</span>
<span style="color: #89DDFF;">go</span> <span style="color: #82aaff;">timerproc</span>()
}
}
<span style="color: #89DDFF;">func</span> <span style="color: #82aaff;">timerproc</span>() {
timers.gp = <span style="color: #82aaff;">getg</span>()
<span style="color: #89DDFF;">for</span> {
<span style="color: #82aaff;">lock</span>(&timers.lock)
timers.sleeping = <span style="color: #f78c6c;">false</span>
<span style="color: #ffcb6b;">now</span> := <span style="color: #82aaff;">nanotime</span>()
<span style="color: #ffcb6b;">delta</span> := <span style="color: #82aaff;">int64</span>(-1)
<span style="color: #89DDFF;">for</span> {
<span style="color: #89DDFF;">if</span> <span style="color: #82aaff;">len</span>(timers.t) == 0 {
delta = -1
<span style="color: #89DDFF;">break</span>
}
<span style="color: #ffcb6b;">t</span> := timers.t[0]
<span style="color: #676E95;">//</span><span style="color: #676E95;">得到剩余时间, 还没到时间就sleep</span>
delta = t.when - now
<span style="color: #89DDFF;">if</span> delta > 0 {
<span style="color: #89DDFF;">break</span>
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">如果是周期性的就算下一次时间</span>
<span style="color: #89DDFF;">if</span> t.period > 0 {
<span style="color: #676E95;">// </span><span style="color: #676E95;">leave in heap but adjust next time to fire</span>
t.when += t.period * (1 + -delta/t.period)
<span style="color: #676E95;">//</span><span style="color: #676E95;">最小堆下沉</span>
<span style="color: #82aaff;">siftdownTimer</span>(0)
} <span style="color: #89DDFF;">else</span> {
<span style="color: #676E95;">// </span><span style="color: #676E95;">remove from heap</span>
<span style="color: #676E95;">//</span><span style="color: #676E95;">删除将要执行的timer,(最小堆算法)</span>
<span style="color: #ffcb6b;">last</span> := <span style="color: #82aaff;">len</span>(timers.t) - 1
<span style="color: #89DDFF;">if</span> last > 0 {
timers.t[0] = timers.t[last]
timers.t[0].i = 0
}
timers.t[last] = <span style="color: #f78c6c;">nil</span>
timers.t = timers.t[:last]
<span style="color: #89DDFF;">if</span> last > 0 {
<span style="color: #82aaff;">siftdownTimer</span>(0)
}
t.i = -1 <span style="color: #676E95;">// </span><span style="color: #676E95;">mark as removed</span>
}
<span style="color: #ffcb6b;">f</span> := t.f
<span style="color: #ffcb6b;">arg</span> := t.arg
<span style="color: #ffcb6b;">seq</span> := t.seq
<span style="color: #82aaff;">unlock</span>(&timers.lock)
<span style="color: #89DDFF;">if</span> raceenabled {
<span style="color: #82aaff;">raceacquire</span>(unsafe.<span style="color: #82aaff;">Pointer</span>(t))
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">执行函数调用函数</span>
<span style="color: #82aaff;">f</span>(arg, seq)
<span style="color: #82aaff;">lock</span>(&timers.lock)
}
<span style="color: #676E95;">//</span><span style="color: #676E95;">继续下一个,因为可能下一个timer也到时间了</span>
<span style="color: #89DDFF;">if</span> delta < 0 || faketime > 0 {
<span style="color: #676E95;">// </span><span style="color: #676E95;">No timers left - put goroutine to sleep.</span>
timers.rescheduling = <span style="color: #f78c6c;">true</span>
<span style="color: #82aaff;">goparkunlock</span>(&timers.lock, <span style="color: #c3e88d;">"timer goroutine (idle)"</span>, traceEvGoBlock, 1)
<span style="color: #89DDFF;">continue</span>
}
<span style="color: #676E95;">// </span><span style="color: #676E95;">At least one timer pending. Sleep until then.</span>
timers.sleeping = <span style="color: #f78c6c;">true</span>
<span style="color: #82aaff;">noteclear</span>(&timers.waitnote)
<span style="color: #82aaff;">unlock</span>(&timers.lock)
<span style="color: #676E95;">//</span><span style="color: #676E95;">没到时间,睡眠delta时间</span>
<span style="color: #82aaff;">notetsleepg</span>(&timers.waitnote, delta)
}
}
</pre>
</div>
</div>
</div>
<div id="outline-container-orgc13592d" class="outline-2">
<h2 id="orgc13592d"><span class="section-number-2">3</span> 其他实现方法</h2>
<div class="outline-text-2" id="text-3">
<p>
以前也看过erlang的timer的实现,不过erlang的实现是使用时间轮的方式,不同的时间挂在不同的时间刻度上,而golang使用的最小堆维护的一个timer队列。总的来说golang的实现方式比较简单,erlang的方式比较复杂,但是erlang在支持比较大量的timer情况下比较好,效率比较高。而go比较费时,每次操作最坏情况下都是logn(n为时间队列长度).
</p>
</div>
</div>
</div>
<div id="postamble" class="status">
<div id="gitalk" /> <script> var gitalk = new Gitalk({
clientID: 'f30e66bb5ab9089aa742',
clientSecret: '5d256d445447bd4db16540c2aab0e0884218ed12',
repo: 'guidao.github.io',
owner: 'guidao',
admin: ['guidao'],
id: location.pathname, // Ensure uniqueness and length less than 50
distractionFreeMode: false // Facebook-like distraction free mode
})
gitalk.render('gitalk') </script>
</div>
</body>
</html>