noblog 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmds/generate.js +3 -3
- package/dist/cmds/generate.js.map +1 -1
- package/dist/index.js +24 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/nomd/nomd.d.ts.map +1 -1
- package/dist/lib/nomd/nomd.js +16 -1
- package/dist/lib/nomd/nomd.js.map +1 -1
- package/dist/lib/notion/client.d.ts +1 -0
- package/dist/lib/notion/client.d.ts.map +1 -1
- package/dist/lib/notion/client.js +16 -0
- package/dist/lib/notion/client.js.map +1 -1
- package/package.json +44 -28
- package/output/00097769-2aac-4b6f-8f37-d9b658d46b2e +0 -435
- package/output/1707063b-7c0a-4572-9526-f7db2f7bbc49 +0 -162
- package/output/1ed588e1-2604-4e33-9685-722b03f66374 +0 -89
- package/output/2b1c0d09-40f7-4151-a053-11391f775ece +0 -33
- package/output/336b6a5a-f383-4d6c-8fe5-54c9a918c4c0 +0 -264
- package/output/37b77a0a-e233-405a-93f5-a60a808eee35 +0 -72
- package/output/3ac660cb-9503-4e88-8939-5aee30c98351 +0 -24
- package/output/5bc1b19a-8350-48b3-aa19-8073561ad45b +0 -109
- package/output/765976b5-7790-497b-8634-4be1d4770e16 +0 -111
- package/output/90f2cb44-90ab-40b7-a736-02bfb83a314c +0 -1167
- package/output/99a7b0c5-f9de-403c-95ec-eba3d700a195 +0 -49
- package/output/c844b66d-0bf3-4f89-bbe1-5974f2de454b +0 -193
- package/output/cd6299f7-54bb-4160-8735-16e629422a57 +0 -193
- package/output/d462cfca-0a39-4b65-8430-4eae35edbb76 +0 -27
- package/output/daafaad2-2570-43da-8e9e-f95a1b29ca17 +0 -12
- package/output/e45ef6bf-6c30-4fb2-9efc-a1984e5dc530 +0 -84
- package/output/sub/05009ca9-cb39-4356-8f24-c0db39cb4661 +0 -64
- package/output/sub/0b903359-2337-4ad9-adef-e77b69142581 +0 -328
- package/output/sub/18f0fae1-b7ca-40ea-b725-4e69404429dc +0 -109
- package/output/sub/232ae8ce-5ec3-4737-a18e-077774b84b1e +0 -940
- package/output/sub/34f4d4af-812e-4e56-a6dd-deb96e7f605c +0 -3
- package/output/sub/44331753-a264-40a4-b86f-eafd676b3da0 +0 -509
- package/output/sub/4bac355b-24f0-49e7-a317-e4f1cad63c79 +0 -65
- package/output/sub/4d6663a2-e93a-402e-89f8-a47cccc0c8cf +0 -76
- package/output/sub/5771c956-25bf-4714-bc2e-65959c9156d9 +0 -52
- package/output/sub/6ff91a18-494e-4ffc-a33b-b9fd2c36f43f +0 -42
- package/output/sub/81e4a2b0-05e2-4013-bd41-14b144979d91 +0 -0
- package/output/sub/99ef1497-0b94-490f-b9a0-f54b0e64194d +0 -6
- package/output/sub/9a33b8cc-101a-4f6a-9852-115595cd166b +0 -172
- package/output/sub/9c060d29-39a2-432b-b06b-b0bd91989a52 +0 -1159
- package/output/sub/a45b383c-d894-4480-8322-9c5d028722e3 +0 -9
- package/output/sub/b875bfe4-3c1e-4a8f-a56c-27a48ab874ca +0 -438
- package/output/sub/b8cbb8f9-f702-4a43-943c-02a6e39683d5 +0 -222
- package/output/sub/e339a686-bbb1-41d9-b6b5-18111e72a68c +0 -1392
- package/output/sub/e7931a8e-45ca-444a-8d6f-e6fed9415c26 +0 -110
- package/output/sub/e859552b-c597-49ed-9ede-fab320fafd87 +0 -150
- package/output/sub/f4237e72-fdce-44a4-b62c-659dce24b1ee +0 -107
- package/output/sub/ff7ea97c-55ac-471c-902f-b7a7bd101ee6 +0 -0
- package/src/cmds/generate.ts +0 -53
- package/src/index.ts +0 -22
- package/src/lib/nomd/cvter.ts +0 -143
- package/src/lib/nomd/nomd.ts +0 -88
- package/src/lib/nosm/default.ts +0 -0
- package/src/lib/nosm/nosm.ts +0 -3
- package/src/lib/notion/client.ts +0 -59
- package/src/lib/notion/object.ts +0 -176
- package/src/lib/notion/request.ts +0 -42
- package/tsconfig.json +0 -35
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
## G-M-P模型概览
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
G-M-P分别代表:
|
|
9
|
-
|
|
10
|
-
- G - Goroutine,Go协程,是参与调度与执行的最小单位
|
|
11
|
-
- M - Machine,指的是系统级线程
|
|
12
|
-
- P - Processor,指的是逻辑处理器,P关联了的本地可运行G的队列(也称为LRQ),最多可存放256个G。
|
|
13
|
-
|
|
14
|
-
GMP调度流程大致如下:
|
|
15
|
-
|
|
16
|
-
- 线程M想运行任务就需得获取 P,即与P关联。
|
|
17
|
-
- 然从 P 的本地队列(LRQ)获取 G
|
|
18
|
-
- 若LRQ中没有可运行的G,M 会尝试从全局队列(GRQ)拿一批G放到P的本地队列,
|
|
19
|
-
- 若全局队列也未找到可运行的G时候,M会随机从其他 P 的本地队列偷一半放到自己 P 的本地队列。
|
|
20
|
-
- 拿到可运行的G之后,M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
|
|
21
|
-
|
|
22
|
-
### 调度的生命周期
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-

|
|
26
|
-
|
|
27
|
-
- M0 是启动程序后的编号为 0 的主线程,这个 M 对应的实例会在全局变量 runtime.m0 中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0 就和其他的 M 一样了
|
|
28
|
-
- G0 是每次启动一个 M 都会第一个创建的 gourtine,G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0
|
|
29
|
-
|
|
30
|
-
上面生命周期流程说明:
|
|
31
|
-
|
|
32
|
-
- runtime 创建最初的线程 m0 和 goroutine g0,并把两者进行关联(g0.m = m0)
|
|
33
|
-
- 调度器初始化:设置M最大数量,P个数,栈和内存出事,以及创建 GOMAXPROCS个P
|
|
34
|
-
- 示例代码中的 main 函数是 main.main,runtime 中也有 1 个 main 函数
|
|
35
|
-
——runtime.main,代码经过编译后,runtime.main 会调用 main.main,程序启动时会为 runtime.main
|
|
36
|
-
创建 goroutine,称它为 main goroutine 吧,然后把 main goroutine 加入到 P 的本地队列。
|
|
37
|
-
- 启动 m0,m0 已经绑定了 P,会从 P 的本地队列获取 G,获取到 main goroutine。
|
|
38
|
-
- G 拥有栈,M 根据 G 中的栈信息和调度信息设置运行环境
|
|
39
|
-
- M 运行 G
|
|
40
|
-
- G 退出,再次回到 M 获取可运行的 G,这样重复下去,直到 main.main 退出,runtime.main 执行 Defer 和 Panic 处理,或调用 runtime.exit 退出程序。
|
|
41
|
-
|
|
42
|
-
### G-M-P的数量
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
G 的数量:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
理论上没有数量上限限制的。查看当前G的数量可以使用`runtime. NumGoroutine()`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
P 的数量:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
由启动时环境变量 `$GOMAXPROCS` 或者是由`runtime.GOMAXPROCS()` 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
M 的数量:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
go 语言本身的限制:go 程序启动时,会设置 M 的最大数量,默认 10000. 但是内核很难支持这么多的线程数,所以这个限制可以忽略。
|
|
61
|
-
runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量
|
|
62
|
-
一个 M 阻塞了,会创建新的 M。M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
### 调度的流程状态
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-

|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
从上图我们可以看出来:
|
|
72
|
-
|
|
73
|
-
- 每个P有个局部队列,局部队列保存待执行的goroutine(流程2),当M绑定的P的的局部队列已经满了之后就会把goroutine放到全局队列(流程2-1)
|
|
74
|
-
- 每个P和一个M绑定,M是真正的执行P中goroutine的实体(流程3),M从绑定的P中的局部队列获取G来执行
|
|
75
|
-
- 当M绑定的P的局部队列为空时,M会从全局队列获取到本地队列来执行G(流程3.1),当从全局队列中没有获取到可执行的G时候,M会从其他P的局部队列中偷取G来执行(流程3.2),这种从其他P偷的方式称为**work stealing**
|
|
76
|
-
- 当G因系统调用(syscall)阻塞时会阻塞M,此时P会和M解绑即**hand off**,并寻找新的idle的M,若没有idle的M就会新建一个M(流程5.1)。
|
|
77
|
-
- 当G因channel或者network I/O阻塞时,不会阻塞M,M会寻找其他runnable的G;当阻塞的G恢复后会重新进入runnable进入P队列等待执行(流程5.3)
|
|
78
|
-
|
|
79
|
-
### 调度过程中阻塞
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
GMP模型的阻塞可能发生在下面几种情况:
|
|
83
|
-
|
|
84
|
-
- I/O,select
|
|
85
|
-
- block on syscall
|
|
86
|
-
- channel
|
|
87
|
-
- 等待锁
|
|
88
|
-
- runtime.Gosched()
|
|
89
|
-
|
|
90
|
-
### 用户态阻塞
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
当goroutine因为channel操作或者network I/O而阻塞时(实际上golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G),对应的G会被放置到某个wait队列(如channel的waitq),该G的状态由_Gruning变为_Gwaitting,而M会跳过该G尝试获取并执行下一个G,如果此时没有runnable的G供M运行,那么M将解绑P,并进入sleep状态;当阻塞的G被另一端的G2唤醒时(比如channel的可读/写通知),G被标记为runnable,尝试加入G2所在P的runnext,然后再是P的Local队列和Global队列。
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### 系统调用阻塞
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
当G被阻塞在某个系统调用上时,此时G会阻塞在_Gsyscall状态,M也处于 block on syscall
|
|
100
|
-
状态,此时的M可被抢占调度:执行该G的M会与P解绑,而P则尝试与其它idle的M绑定,继续执行其它G。如果没有其它idle的M,但P的Local队列中仍然有G需要执行,则创建一个新的M;当系统调用完成后,G会重新尝试获取一个idle的P进入它的Local队列恢复执行,如果没有idle的P,G会被标记为runnable加入到Global队列。
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
## G-M-P内部结构
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
### G的内部结构
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
G的内部结构中重要字段如下,完全结构参见[源码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L387)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```go
|
|
113
|
-
type g struct {
|
|
114
|
-
stack stack // g自己的栈
|
|
115
|
-
m *m // 隶属于哪个M
|
|
116
|
-
sched gobuf // 保存了g的现场,goroutine切换时通过它来恢复
|
|
117
|
-
atomicstatus uint32 // G的运行状态
|
|
118
|
-
goid int64
|
|
119
|
-
schedlink guintptr // 下一个g, g链表
|
|
120
|
-
preempt bool //抢占标记
|
|
121
|
-
lockedm muintptr // 锁定的M,g中断恢复指定M执行
|
|
122
|
-
gopc uintptr // 创建该goroutine的指令地址
|
|
123
|
-
startpc uintptr // goroutine 函数的指令地址
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
G的状态有以下9种,可以参见[代码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L15):
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| 状态 | 值 | 含义 |
|
|
132
|
-
| ----------------- | - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
-
| _Gidle | 0 | 刚刚被分配,还没有进行初始化。 |
|
|
134
|
-
| _Grunnable | 1 | 已经在运行队列中,还没有执行用户代码。 |
|
|
135
|
-
| _Grunning | 2 | 不在运行队列里中,已经可以执行用户代码,此时已经分配了 M 和 P。 |
|
|
136
|
-
| _Gsyscall | 3 | 正在执行系统调用,此时分配了 M。 |
|
|
137
|
-
| _Gwaiting | 4 | 在运行时被阻止,没有执行用户代码,也不在运行队列中,此时它正在某处阻塞等待中。Groutine wait的原因有哪些参加[代码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L846) |
|
|
138
|
-
| _Gmoribund_unused | 5 | 尚未使用,但是在 gdb 中进行了硬编码。 |
|
|
139
|
-
| _Gdead | 6 | 尚未使用,这个状态可能是刚退出或是刚被初始化,此时它并没有执行用户代码,有可能有也有可能没有分配堆栈。 |
|
|
140
|
-
| _Genqueue_unused | 7 | 尚未使用。 |
|
|
141
|
-
| _Gcopystack | 8 | 正在复制堆栈,并没有执行用户代码,也不在运行队列中。 |
|
|
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
|
-
G的状态流转:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-

|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
### M的结构
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
M的内部结构,完整结构参见[源码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L452)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```go
|
|
187
|
-
type m struct {
|
|
188
|
-
g0 *g // g0, 每个M都有自己独有的g0
|
|
189
|
-
|
|
190
|
-
curg *g // 当前正在运行的g
|
|
191
|
-
p puintptr // 隶属于哪个P
|
|
192
|
-
nextp puintptr // 当m被唤醒时,首先拥有这个p
|
|
193
|
-
id int64
|
|
194
|
-
spinning bool // 是否处于自旋
|
|
195
|
-
|
|
196
|
-
park note
|
|
197
|
-
alllink *m // on allm
|
|
198
|
-
schedlink muintptr // 下一个m, m链表
|
|
199
|
-
mcache *mcache // 内存分配
|
|
200
|
-
lockedg guintptr // 和 G 的lockedm对应
|
|
201
|
-
freelink *m // on sched.freem
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-

|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
### P的内部结构
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
P的内部结构,完全结构参见[源码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L523)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```go
|
|
216
|
-
type p struct {
|
|
217
|
-
id int32
|
|
218
|
-
status uint32 // P的状态
|
|
219
|
-
link puintptr // 下一个P, P链表
|
|
220
|
-
m muintptr // 拥有这个P的M
|
|
221
|
-
mcache *mcache
|
|
222
|
-
|
|
223
|
-
// P本地runnable状态的G队列,无锁访问
|
|
224
|
-
runqhead uint32
|
|
225
|
-
runqtail uint32
|
|
226
|
-
runq [256]guintptr
|
|
227
|
-
|
|
228
|
-
runnext guintptr // 一个比runq优先级更高的runnable G
|
|
229
|
-
|
|
230
|
-
// 状态为dead的G链表,在获取G时会从这里面获取
|
|
231
|
-
gFree struct {
|
|
232
|
-
gList
|
|
233
|
-
n int32
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
gcBgMarkWorker guintptr // (atomic)
|
|
237
|
-
gcw gcWork
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
P有以下几种状态,参加[源码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L100)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| 状态 | 值 | 含义 |
|
|
246
|
-
| --------- | - | -------------------------------------------- |
|
|
247
|
-
| _Pidle | 0 | 刚刚被分配,还没有进行进行初始化。 |
|
|
248
|
-
| _Prunning | 1 | 当 M 与 P 绑定调用 acquirep 时,P 的状态会改变为 _Prunning。 |
|
|
249
|
-
| _Psyscall | 2 | 正在执行系统调用。 |
|
|
250
|
-
| _Pgcstop | 3 | 暂停运行,此时系统正在进行 GC,直至 GC 结束后才会转变到下一个状态阶段。 |
|
|
251
|
-
| _Pdead | 4 | 废弃,不再使用。 |
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
P的状态流转:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-

|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
### 调度器的内部结构
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
调度器内部结构,完全结构参见[源码](https://github.com/golang/go/blob/5622128a77b4af5e5dc02edf53ecac545e3af730/src/runtime/runtime2.go#L604)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
```go
|
|
285
|
-
type schedt struct {
|
|
286
|
-
|
|
287
|
-
lock mutex
|
|
288
|
-
|
|
289
|
-
midle muintptr // 空闲M链表
|
|
290
|
-
nmidle int32 // 空闲M数量
|
|
291
|
-
nmidlelocked int32 // 被锁住的M的数量
|
|
292
|
-
mnext int64 // 已创建M的数量,以及下一个M ID
|
|
293
|
-
maxmcount int32 // 允许创建最大的M数量
|
|
294
|
-
nmsys int32 // 不计入死锁的M数量
|
|
295
|
-
nmfreed int64 // 累计释放M的数量
|
|
296
|
-
|
|
297
|
-
pidle puintptr // 空闲的P链表
|
|
298
|
-
npidle uint32 // 空闲的P数量
|
|
299
|
-
|
|
300
|
-
runq gQueue // 全局runnable的G队列
|
|
301
|
-
runqsize int32 // 全局runnable的G数量
|
|
302
|
-
|
|
303
|
-
// Global cache of dead G's.
|
|
304
|
-
gFree struct {
|
|
305
|
-
lock mutex
|
|
306
|
-
stack gList // Gs with stacks
|
|
307
|
-
noStack gList // Gs without stacks
|
|
308
|
-
n int32
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// freem is the list of m's waiting to be freed when their
|
|
312
|
-
// m.exited is set. Linked through m.freelink.
|
|
313
|
-
freem *m
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
## 观察调度流程
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
### GODEBUG trace方式
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
GODEBUG 变量可以控制运行时内的调试变量,参数以逗号分隔,格式为:name=val。观察GMP可以使用下面两个参数:
|
|
325
|
-
|
|
326
|
-
- schedtrace:设置 schedtrace=X 参数可以使运行时在每 X 毫秒输出一行调度器的摘要信息到标准 err 输出中。
|
|
327
|
-
- scheddetail:设置 schedtrace=X 和 scheddetail=1 可以使运行时在每 X 毫秒输出一次详细的多行信息,信息内容主要包括调度程序、处理器、OS 线程 和 Goroutine 的状态。
|
|
328
|
-
|
|
329
|
-
```go
|
|
330
|
-
package main
|
|
331
|
-
|
|
332
|
-
import (
|
|
333
|
-
"sync"
|
|
334
|
-
"time"
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
func main() {
|
|
338
|
-
var wg sync.WaitGroup
|
|
339
|
-
|
|
340
|
-
for i := 0; i < 2000; i++ {
|
|
341
|
-
wg.Add(1)
|
|
342
|
-
go func() {
|
|
343
|
-
a := 0
|
|
344
|
-
|
|
345
|
-
for i := 0; i < 1e6; i++ {
|
|
346
|
-
a += 1
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
wg.Done()
|
|
350
|
-
}()
|
|
351
|
-
time.Sleep(100 * time.Millisecond)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
wg.Wait()
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
执行一下命令:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
> GODEBUG=schedtrace=1000 go run ./test.go
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
输出内容如下:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
```go
|
|
369
|
-
SCHED 0ms: gomaxprocs=1 idleprocs=1 threads=4 spinningthreads=0 idlethreads=1 runqueue=0 [0]
|
|
370
|
-
SCHED 1001ms: gomaxprocs=1 idleprocs=1 threads=4 spinningthreads=0 idlethreads=2 runqueue=0 [0]
|
|
371
|
-
SCHED 2002ms: gomaxprocs=1 idleprocs=1 threads=4 spinningthreads=0 idlethreads=2 runqueue=0 [0]
|
|
372
|
-
SCHED 3002ms: gomaxprocs=1 idleprocs=1 threads=4 spinningthreads=0 idlethreads=2 runqueue=0 [0]
|
|
373
|
-
SCHED 4003ms: gomaxprocs=1 idleprocs=1 threads=4 spinningthreads=0 idlethreads=2 runqueue=0 [0]
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
输出内容解释说明:
|
|
378
|
-
|
|
379
|
-
- SCHED XXms: SCHED是调度日志输出标志符。XXms是自程序启动之后到输出当前行时间
|
|
380
|
-
- gomaxprocs: P的数量,等于当前的 CPU 核心数,或者GOMAXPROCS环境变量的值
|
|
381
|
-
- idleprocs: 空闲P的数量,与gomaxprocs的差值即运行中P的数量
|
|
382
|
-
- threads: 线程数量,即M的数量
|
|
383
|
-
- spinningthreads:自旋状态线程的数量。当M没有找到可供其调度执行的 Goroutine 时,该线程并不会销毁,而是出于自旋状态
|
|
384
|
-
- idlethreads:空闲线程的数量
|
|
385
|
-
- runqueue:全局队列中G的数量
|
|
386
|
-
- [0]:表示P本地队列下G的数量,有几个P中括号里面就会有几个数字
|
|
387
|
-
|
|
388
|
-
### Go tool trace方式
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
```go
|
|
392
|
-
func main() {
|
|
393
|
-
// 创建trace文件
|
|
394
|
-
f, err := os.Create("trace.out")
|
|
395
|
-
if err != nil {
|
|
396
|
-
panic(err)
|
|
397
|
-
}
|
|
398
|
-
defer f.Close()
|
|
399
|
-
|
|
400
|
-
// 启动trace goroutine
|
|
401
|
-
err = trace.Start(f)
|
|
402
|
-
if err != nil {
|
|
403
|
-
panic(err)
|
|
404
|
-
}
|
|
405
|
-
defer trace.Stop()
|
|
406
|
-
|
|
407
|
-
// main
|
|
408
|
-
fmt.Println("Hello trace")
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
执行下面命令产生trace文件trace.out:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
> go run test.go
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
执行下面命令,打开浏览器,打开控制台查看。
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
> go tool trace trace.out
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
### 总结
|
|
426
|
-
|
|
427
|
-
1. Golang的线程模型采用的是混合型线程模型,线程与协程关系是N:M。
|
|
428
|
-
2. Golang混合型线程模型实现采用GMP模型进行调度,G是goroutine,是golang实现的协程,M是OS线程,P是逻辑处理器。
|
|
429
|
-
3. 每一个M都需要与一个P绑定,P拥有本地可运行G队列,M是执行G的单元,M获取可运行G流程是先从P的本地队列获取,若未获取到,则从其他P偷取过来(即work steal),若其他的P也没有则从全局G队列获取,若都未获取到,则M将处于自旋状态,并不会销毁。
|
|
430
|
-
4. 当执行G时候,发生通道阻塞等用户级别阻塞时候,此时M不会阻塞,M会继续寻找其他可运行的G,当阻塞的G恢复之后,重新进入P的队列等待执行,若G进行系统调用时候,会阻塞M,此时P会和M解绑(即hand off),并寻找新的空闲的M。若没有空闲的就会创建一个新的M。
|
|
431
|
-
5. Work Steal和Hand Off保证了线程的高效利用。
|
|
432
|
-
|
|
433
|
-
**G-M-P高效的保证策略有:**
|
|
434
|
-
|
|
435
|
-
- M是可以复用的,不需要反复创建与销毁,当没有可执行的Goroutine时候就处于自旋状态,等待唤醒
|
|
436
|
-
- Work Stealing和Hand Off策略保证了M的高效利用
|
|
437
|
-
- 内存分配状态(mcache)位于P,G可以跨M调度,不再存在跨M调度局部性差的问题
|
|
438
|
-
- M从关联的P中获取G,不需要使用锁,是lock free的
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Overview
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
> **Go 语言的源码文件分为三类:命令源码、库源码、测试源码。**
|
|
12
|
-
> **命令源码文件:**是 Go 程序的入口,包含 func main() 函数,且第一行用 package main 声明属于 main 包。
|
|
13
|
-
> **库源码文件:**主要是各种函数、接口等,例如工具类的函数。
|
|
14
|
-
> **测试源码文件:**以 _test.go 为后缀的文件,用于测试程序的功能和性能。
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`go build` 用来编译指定 packages 里的源码文件以及它们的依赖包,编译的时候会到 `$GoPath/src/package` 路径下寻找源码文件。`go build` 还可以直接编译指定的源码文件,并且可以同时指定多个。
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
通过执行 `go help build` 命令得到 `go build` 的使用方法:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
usage: go build [-o output] [-i] [build flags] [packages]
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
- `o` 只能在编译单个包的时候出现,它指定输出的可执行文件的名字。
|
|
28
|
-
- `i` 会安装编译目标所依赖的包,安装是指生成与代码包相对应的 `.a` 文件,即静态库文件(后面要参与链接),并且放置到当前工作区的 pkg 目录下,且库文件的目录层级和源码层级一致。
|
|
29
|
-
|
|
30
|
-
build flags 参数,`build, clean, get, install, list, run, test` 这些命令共用一套:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| **参数** | **作用** |
|
|
34
|
-
| ------ | -------------------------------------------------------- |
|
|
35
|
-
| -a | 强制重新编译所有涉及到的包,包括标准库中的代码包,这会重写 /usr/local/go 目录下的 `.a` 文件 |
|
|
36
|
-
| -n | 打印命令执行过程,不真正执行 |
|
|
37
|
-
| -p n | 指定编译过程中命令执行的并行数,n 默认为 CPU 核数 |
|
|
38
|
-
| -race | 检测并报告程序中的数据竞争问题 |
|
|
39
|
-
| -v | 打印命令执行过程中所涉及到的代码包名称 |
|
|
40
|
-
| -x | 打印命令执行过程中所涉及到的命令,并执行 |
|
|
41
|
-
| -work | 打印编译过程中的临时文件夹。通常情况下,编译完成后会被删除 |
|
|
42
|
-
| -C dir | 在运行命令前转到路径dir下,对命令中的所有文件均有效 |
|
|
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
|
-
# Issues in `go build`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
这里可以把go和C的编译过程类比,如果只有一个main文件则可以直接:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
go build -o binfile main.go
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
但是如果main中引用了其他**同目录下**,**同package中**的一些**暴露的对象和方法**,则需要:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
go build -o binfile main.go ref1.go ref2.go ...
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
或者更普适的:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
go build .
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
**需要注意的是,****`go test`****可以直接跑,不需要把依赖的包全部写上,**`go build` 会忽略 `*_test.go` 文件。
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# go get & go install
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`go get` 和 `go install` 是 Go 语言中用于获取和安装包的命令,它们之间有以下区别:
|
|
106
|
-
|
|
107
|
-
1. 功能:`go get` 用于获取(下载)远程代码仓库或更新已有的代码仓库,它会自动解析并下载依赖的包。而 `go install` 用于将代码包编译并安装到指定位置,将生成的可执行文件或库文件安装到 `$GOPATH/bin` 或 `$GOPATH/pkg` 目录下。
|
|
108
|
-
2. 使用方式:`go get` 后面通常跟着一个包的导入路径,它会下载并安装该包及其依赖。例如:`go get github.com/example/package`。而 `go install` 后面通常跟着一个包的相对路径或绝对路径,它会编译并安装指定的包。例如:`go install ./path/to/package`。
|
|
109
|
-
3. 安装位置:`go get` 下载的包会被存放在 `$GOPATH/src` 目录下,而 `go install` 会将编译生成的可执行文件或库文件安装到 `$GOPATH/bin` 或 `$GOPATH/pkg` 目录下。
|
|
110
|
-
4. 版本控制:`go get` 可以根据包的版本控制信息(如 Git 标签、GitHub Releases 等)下载指定版本的包。而 `go install` 则不涉及版本控制,它是根据当前代码的状态进行编译安装。
|
|
111
|
-
|
|
112
|
-
总结来说,`go get` 主要用于获取和更新包及其依赖,`go install` 主要用于编译并安装代码包到指定位置。
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# GOPATH
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
指定多个 GOPATH 的方法有两种:
|
|
119
|
-
|
|
120
|
-
1. 使用环境变量:可以通过设置多个 GOPATH 环境变量来指定多个工作空间。每个 GOPATH 的值应该是不同的目录路径,多个路径之间使用操作系统特定的路径分隔符(如 Windows 上的分号 `;`,Unix/Linux 上的冒号 `:`)进行分隔。例如,在 Linux 上可以这样设置两个 GOPATH:
|
|
121
|
-
|
|
122
|
-
```text
|
|
123
|
-
export GOPATH=/path/to/gopath1:/path/to/gopath2
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
2. 使用 Go Modules:如果你的项目使用了 Go Modules(Go 1.11+ 版本),可以在项目的根目录下创建一个 `go.mod` 文件,并使用 `go mod init` 命令初始化模块。在 Go Modules 中,不再需要设置 GOPATH,而是直接在项目目录中管理依赖和版本。每个项目可以独立指定依赖的版本,并且不会受到全局 GOPATH 的影响。使用这种方法,`go get/ go install` 的内容会保存在当前模块的`bin`目录下。
|
|
127
|
-
|
|
128
|
-
# Debug
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
就用gdb!
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`i files` 列出文件和 `Entry point`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
开始你的表演
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# Asm
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
单纯的go汇编
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
go build -gcflags=-S main.go 1> main.S 2>&1
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
完整的编译过程
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
go build -x -work main.go 1> transcript.txt 2>&1
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
在命令行中,`2>&1` 中的 `&` 是用来表示文件描述符的标识符。在这种上下文中,`1` 表示标准输出(stdout),`2` 表示标准错误输出(stderr)。
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
使用 `2>&1` 的语法可以将标准错误输出重定向到与标准输出相同的目标。具体来说,`2>&1` 表示将文件描述符 `2`(标准错误输出)重定向到文件描述符 `1`(标准输出),也就是将标准错误输出合并到标准输出中。
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
**更详细的指令**
|
|
166
|
-
|
|
167
|
-
- 先使用 `go build -gcflags "-N -l" main.go` 生成对应的可执行二进制文件 再使用 `go tool objdump -s "main\." main` 反编译获取对应的汇编
|
|
168
|
-
- 使用 `go tool compile -S -N -l main.go` 这种方式直接输出汇编
|
|
169
|
-
- 使用`go build -gcflags="-N -l -S" main.go` 直接输出汇编
|
|
170
|
-
|
|
171
|
-
`-l` 禁止内联
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`-N` 编译时,禁止优化
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`-S` 输出汇编代码
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`-s “target”` 只输出匹配target的汇编
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# Go bootstrap
|
|
184
|
-
|
|
185
|
-
1. 检查运行平台的CPU,设置好程序运行需要相关标志。
|
|
186
|
-
2. TLS的初始化。
|
|
187
|
-
3. `runtime.args`、`runtime.osinit`、`runtime.schedinit` 三个方法做好程序运行需要的各种变量与调度器。
|
|
188
|
-
4. `runtime.newproc`创建新的goroutine用于绑定用户写的main方法。
|
|
189
|
-
5. `runtime.mstart`开始goroutine的调度。
|
|
190
|
-
|
|
191
|
-
最后用一张图来总结 go bootstrap 过程吧:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-

|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
`main` 函数里执行的一些重要的操作包括:新建一个线程执行 `sysmon` 函数,定期垃圾回收和调度抢占;启动 `gc`;执行所有的 `init` 函数等等。
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
上面是启动过程,看一下退出过程:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
> 当 main 函数执行结束之后,会执行 exit(0) 来退出进程。若执行 exit(0) 后,进程没有退出,main 函数最后的代码会一直访问非法地址:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
```text
|
|
207
|
-
exit(0)
|
|
208
|
-
for {
|
|
209
|
-
var x *int32
|
|
210
|
-
*x = 0
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
> 正常情况下,一旦出现非法地址访问,系统会把进程杀死,用这样的方法确保进程退出。
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
关于程序退出这一段的阐述来自群聊《golang runtime 阅读》,又是一个高阶的读源码的组织,github 主页见参考资料。
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
当然 Go 程序启动这一部分其实还会涉及到 fork 一个新进程、装载可执行文件,控制权转移等问题。还是推荐看前面的两本书,我觉得我不会写得更好,就不叙述了。
|
|
222
|
-
|