├── .gitignore
├── LICENSE
├── README.md
└── src
├── .DS_Store
├── chapter01
└── golang.01.md
├── chapter02
└── golang.01.md
├── chapter03
└── golang.01.md
├── chapter04
└── golang.01.md
├── chapter05
└── golang.01.md
├── chapter06
└── golang.01.md
├── chapter07
└── golang.01.md
├── chapter08
└── golang.01.md
├── chapter09
└── golang.01.md
├── chapter10
└── golang.01.md
├── chapter11
└── golang.01.md
├── chapter12
└── golang.01.md
├── chapter13
└── golang.01.md
├── chapter14
└── golang.01.md
├── chapter15
└── golang.01.md
├── chapter16
└── golang.01.md
├── chapter17
└── golang.01.md
├── chapter18
└── golang.01.md
├── chapter19
└── golang.01.md
├── chapter20
└── golang.01.md
└── images
├── 1.jpeg
├── 1.jpg
├── 10.jpg
├── 100.jpg
├── 101.jpg
├── 102.jpg
├── 103.jpg
├── 104.jpg
├── 105.jpg
├── 106.jpg
├── 107.jpg
├── 108.jpg
├── 109.jpg
├── 11.jpg
├── 110.jpg
├── 111.jpg
├── 112.jpg
├── 114.jpg
├── 115.jpg
├── 116.jpg
├── 117.jpg
├── 118.jpg
├── 119.jpg
├── 12.jpg
├── 120.jpg
├── 121.jpg
├── 122.jpg
├── 123.jpg
├── 124.jpg
├── 125.jpg
├── 126.jpg
├── 127.jpg
├── 128.jpg
├── 129.jpg
├── 13.jpg
├── 130.jpg
├── 131.jpg
├── 132.jpg
├── 133.jpg
├── 134.jpg
├── 135.jpg
├── 136.jpg
├── 137.jpg
├── 138.jpg
├── 139.jpg
├── 14.jpg
├── 140.jpg
├── 141.jpg
├── 142.jpg
├── 143.jpg
├── 144.jpg
├── 145.jpg
├── 146.jpg
├── 147.jpg
├── 148.jpg
├── 149.jpg
├── 15.jpg
├── 150.jpg
├── 151.jpg
├── 152.jpg
├── 153.jpg
├── 154.jpg
├── 155.jpg
├── 156.jpg
├── 157.jpg
├── 158.jpg
├── 159.jpg
├── 16.jpg
├── 160.jpg
├── 161.jpg
├── 162.jpg
├── 163.jpg
├── 164.jpg
├── 165.jpg
├── 166.jpg
├── 167.jpg
├── 168.jpg
├── 169.jpg
├── 17.jpg
├── 170.jpg
├── 171.jpg
├── 172.jpg
├── 173.jpg
├── 174.jpg
├── 175.jpg
├── 176.jpg
├── 177.jpg
├── 178.jpg
├── 179.jpg
├── 18.jpg
├── 180.jpg
├── 181.jpg
├── 182.jpg
├── 183.jpg
├── 184.jpg
├── 185.jpg
├── 186.jpg
├── 187.jpg
├── 188.jpg
├── 189.jpg
├── 19.jpg
├── 190.jpg
├── 191.jpg
├── 192.jpg
├── 193.jpg
├── 194.jpg
├── 195.jpg
├── 196.jpg
├── 197.jpg
├── 2.jpg
├── 20.jpg
├── 21.jpg
├── 22.jpg
├── 23.jpg
├── 24.jpg
├── 25.jpg
├── 26.jpg
├── 27.jpg
├── 28.jpg
├── 29.jpg
├── 3.jpg
├── 30.jpg
├── 31.jpg
├── 32.jpg
├── 33.jpg
├── 34.jpg
├── 35.jpg
├── 36.jpg
├── 37.jpg
├── 38.jpg
├── 39.jpg
├── 4.jpg
├── 40.jpg
├── 41.jpg
├── 42.jpg
├── 43.jpg
├── 44.jpg
├── 45.jpg
├── 46.jpg
├── 47.jpg
├── 48.jpg
├── 49.jpg
├── 5.jpg
├── 50.jpg
├── 51.jpg
├── 52.jpg
├── 53.jpg
├── 54.jpg
├── 55.jpg
├── 56.jpg
├── 57.jpg
├── 58.jpg
├── 59.jpg
├── 6.jpg
├── 60.jpg
├── 61.jpg
├── 62.jpg
├── 63.jpg
├── 64.jpg
├── 65.jpg
├── 66.jpg
├── 67.jpg
├── 68.jpg
├── 69.jpg
├── 7.jpg
├── 70.jpg
├── 71.jpg
├── 72.jpg
├── 73.jpg
├── 74.jpg
├── 75.jpg
├── 76.jpg
├── 77.jpg
├── 78.jpg
├── 79.jpg
├── 8.jpg
├── 80.jpg
├── 81.jpg
├── 82.jpg
├── 83.jpg
├── 84.jpg
├── 85.jpg
├── 86.jpg
├── 87.jpg
├── 88.jpg
├── 89.jpg
├── 9.jpg
├── 90.jpg
├── 91.jpg
├── 92.jpg
├── 93.jpg
├── 94.jpg
├── 95.jpg
├── 96.jpg
├── 97.jpg
├── 98.jpg
└── 99.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 | .idea
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### data-structures-questions
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 算法和程序结构是我们学习编程的基础,但是很多的时候,我们很多都是只是在应用,而没有深入的去研究这些,所以自己也在不断的思考和探索,然后分析,学习,总结自己学习的过程,希望可以和大家一起学习和交流下算法!
10 |
11 | #### 目录
12 |
13 | * [网络协议](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter01/golang.01.md)
14 | * [数据结构](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter02/golang.01.md)
15 | * [算法](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter03/golang.01.md)
16 | * [数据库](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter04/golang.01.md)
17 | * [Golang面试问题汇总](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter05/golang.01.md)
18 | * [操作系统解析](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter06/golang.01.md)
19 | * [Golang的堆栈分配](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter07/golang.01.md)
20 | * [计算机网络基础知识](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter08/golang.01.md)
21 | * [Golang内存管理](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter09/golang.01.md)
22 | * [Golang runtime的调度](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter10/golang.01.md)
23 | * [Golang的逃逸分析](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter11/golang.01.md)
24 | * [Redis为什么快](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter12/golang.01.md)
25 | * [Golang性能优化](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter13/golang.01.md)
26 | * [Golang的汇编过程](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter14/golang.01.md)
27 | * [Golang的defer优化](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter15/golang.01.md)
28 | * [Golang中的TCP开发](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter16/golang.01.md)
29 | * [CPU上下文切换](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter17/golang.01.md)
30 | * [汇编的知识](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter18/golang.01.md)
31 | * [Serverless是如何进入我们视野](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter19/golang.01.md)
32 | * [QUIC网络传输协议](https://github.com/KeKe-Li/golang-interview-questions/blob/master/src/chapter20/golang.01.md)
33 |
34 | ### data-structures-questions
35 |
36 | 觉得此文章不错,支持我的话可以给我star ,:star:!如果有问题可以加我的微信,也可以加入我们的交流群一起交流golang技术!
37 |
38 |
39 | ### License
40 | This is free software distributed under the terms of the MIT license
41 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/.DS_Store
--------------------------------------------------------------------------------
/src/chapter01/golang.01.md:
--------------------------------------------------------------------------------
1 | #### 网络协议
2 |
3 | 计算机网络协议技就是网络规则,是各种硬件和软件共同遵循的守则。网络协议融合于其它所有的软件系统中,在网络中协议是无所不在的。网络协议遍及OSI通信模型的各个层次,从比较常见的`TCP/IP`、`HTTP`、`FTP`协议,到`OSPF`、`IGP`等特殊协议,有上千种之多。局域网常用`TCP/IP`、`NetBEUI`、`IPX/SPX`这三种通信协议。`TCP/IP`协议是最重要、最基础、最麻烦的一个,上网时需要详细设置IP地址、网关、子网掩码、DNS服务器等参数,不过随着技术的进步,现在基本是自动获取了。
4 |
5 | `TCP/IP`协议族中互为关联的协议有上百个之多,且都有不同的功能,分布在不同的协议层.
6 |
7 | 常用协议如下:
8 |
9 | 1、UDP:用户数据包协议,位于传输层,和IP协议配合使用,因为不能提供数据包的重传,所以适合传输较短的文件;
10 |
11 | 2、NFS:网络文件服务器,可使多台计算机透明地访问彼此的目录;
12 |
13 | 3、FTP:远程文件传输协议,允许用户将远程主机上的文件拷贝到自己的计算机上;
14 |
15 | 4、SMTP:简单邮政传输协议,用于传输电子邮件;
16 |
17 | 5、Telnet:提供远程登录功能,一台计算机用户可以登录到远程的另一台计算机上,如同在远程主机上直接操作一样.
18 |
19 | 6、HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。 HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
20 |
21 | #### IP 协议
22 |
23 | 路由器对分组进行转发后,就会把数据包传到网络上,数据包最终是要传递到客户端或者服务器上的,那么数据包怎么知道要发往哪里呢?起到关键作用的就是 IP 协议。
24 |
25 | IP 主要分为三个部分,分别是: `IP 寻址`、`路由`和`分包组包`。
26 |
27 | #### IP 地址
28 |
29 | 一个数据包要在网络上传输,那么肯定需要知道这个数据包到底发往哪里,也就是说需要一个目标地址信息,IP 地址就是连接网络中的所有主机进行通信的目标地址,因此,在网络上的每个主机都需要有自己的 IP 地址。
30 |
31 | 在 IP 数据报发送的链路中,有可能链路非常长,比如说由中国发往美国的一个数据报,由于网络抖动等一些意外因素可能会导致数据报丢失,这时我们在这条链路中会放入一些 中转站,一方面能够确保数据报是否丢失,另一方面能够控制数据报的转发,这个中转站就是我们前面聊过的路由器,这个转发过程就是 路由控制。
32 |
33 | 路由控制(Routing) 是指将分组数据发送到最终目标地址的功能,即使网络复杂多变,也能够通过路由控制到达目标地址。因此,一个数据报能否到达目标主机,关键就在于路由器的控制。
34 |
35 | 这里有一个名词,就是跳,因为在一条链路中可能会布满很多路由器,路由器和路由器之间的数据报传送就是跳,比如你和朋友通信,中间就可能会经过路由器 A-> 路由器 B -> 路由器 C 。
36 |
37 |
38 | 那么这个跳转的范围是多大呢?
39 |
40 | 通常这个一跳是指从源 MAC 地址到目标 MAC 地址之间传输帧的区间,那么这个MAC 地址又是什么呢?
41 |
42 | `MAC`地址指的就是计算机的物理地址(Physical Address),它是用来确认网络设备位置的地址。在 OSI 网络模型中,网络层负责 IP 地址的定位,而数据链路层负责 MAC 地址的定位。MAC 地址用于在网络中唯一标示一个网卡,一台设备若有一或多个网卡,则每个网卡都需要并会有一个唯一的 MAC 地址,也就是说 MAC 地址和网卡是紧密联系在一起的。
43 |
44 | 路由器的每一跳都需要询问当前中转的路由器,下一跳应该跳到哪里,从而跳转到目标地址。而不是数据报刚开始发送后,网络中所有的通路都会显示出来,这种多次跳转也叫做`多跳路由`。
45 |
46 | #### IP 地址定义
47 |
48 | 我们现在又两个版本的 IP 地址,`IPv4` 和 `IPv6`,我们首先看一下现如今还在广泛使用的 IPv4 地址。
49 |
50 | `IPv4` 由 32 位正整数来表示,在计算机内部会转化为二进制来处理,但是二进制不符合人类阅读的习惯,所以我们根据易读性的原则把 32 位的 IP 地址以 8 位为一组,分成四组,每组之间以 `.` 进行分割,再将每组转换为十进制数。
51 |
52 | 如下图所示:
53 |
54 |
55 |
56 |
57 |
58 | 面这个 32 位的 IP 地址就会被转换为十进制的 `156.197.1.1`。
59 |
60 | 每个这样 8 位位一组的数字,自然是非负数,其取值范围是 [0,255]。
61 |
62 | IP 地址的总个数有 `2^32` 次幂个,这个数值算下来是 `4294967296` ,大概能允许 43 亿台设备连接到网络。实际上真的如此吗?
63 |
64 | 实际上 IP 不会以主机的个数来配置的,而是根据设备上的 网卡(NIC) 进行配置,每一块网卡都会设置一个或者多个 IP 地址,而且通常一台路由器会有至少两块网卡,所以可以设置两个以上的 IP 地址,所以主机的数量远远达不到 43 亿。
65 |
66 | #### IP 地址构造和分类
67 |
68 | IP 地址由 网络标识 和 主机标识 两部分组成,网络标识代表着网络地址,主机标识代表着主机地址。网络标识在数据链路的每个段配置不同的值。
69 | 网络标识必须保证相互连接的每个段的地址都不重复。而相同段内相连的主机必须有相同的网络地址。IP 地址的 主机标识 则不允许在同一网段内重复出现。
70 |
71 | 例如:我们所在的小区的某一栋楼就相当于是网络标识,某一栋楼的第几户就相当于是我的`主机标识`。这样可以通过xx省xx市xx区xx路xx小区xx栋来定位我的`网络标识`,这一栋的第几户就相当于是我的`主机标识`。
72 |
73 | IP 地址分为五类,分别是 A类、B类、C类、D类、E类,它会根据 IP 地址中的第 1 位到第 4 位的比特对网络标识和主机标识进行分类。
74 |
75 | * A 类:(1.0.0.0 - 126.0.0.0)(默认子网掩码:255.0.0.0 或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类 IP 地址的最前面为 0 ,所以地址的网络号取值于 1~126 之间。一般用于大型网络。
76 |
77 | * B 类:(128.0.0.0 - 191.255.0.0)(默认子网掩码:255.255.0.0 或 0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类 IP 地址的最前面为 10 ,所以地址的网络号取值于 128~191 之间。一般用于中等规模网络。
78 |
79 | * C 类:(192.0.0.0 - 223.255.255.0)(子网掩码:255.255.255.0 或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类 IP 地址的最前面为 110 ,所以地址的网络号取值于 192~223 之间。一般用于小型网络。
80 |
81 | * D 类:是多播地址。该类 IP 地址的最前面为 1110 ,所以地址的网络号取值于 `224~239` 之间。一般用于多路广播用户。
82 |
83 | * E 类:是保留地址。该类 IP 地址的最前面为 1111 ,所以地址的网络号取值于 `240~255` 之间。
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 而根据不同的 IP 范围,就有不同的地总空间分类:
92 |
93 |
94 |
95 |
96 |
97 | #### 子网掩码
98 |
99 | 子网掩码(subnet mask) 又叫做网络掩码,它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的网络。子网掩码是一个`32位`地址,用于屏蔽 IP 地址的一部分以区别网络标识和主机标识。
100 |
101 | 一个 IP 地址只要确定了其分类,也就确定了它的网络标识和主机标识。
102 |
103 | 由此,各个分类所表示的网络标识范围如下:
104 |
105 |
106 |
107 |
108 |
109 | 用 1 表示 IP 网络地址的比特范围,0 表示 IP 主机地址的范围。
110 |
111 | 将他们用十进制表示,那么这三类的表示如下:
112 |
113 |
114 |
115 |
116 |
117 | #### 保留地址
118 |
119 | 在IPv4 的几类地址中,有几个保留的地址空间不能在互联网上使用。这些地址用于特殊目的,不能在局域网外部路由。
120 |
121 |
122 |
123 |
124 |
125 | #### IP 协议版本
126 |
127 | 目前,在全球 Internet 中共存有两个IP版本:IP 版本 4(IPv4)和 IP 版本6(IPv6)。IP 地址由二进制值组成,可驱动 Internet 上所有数据的路由。IPv4 地址的长度为 32 位,而 IPv6 地址的长度为 128 位。
128 |
129 | Internet IP 资源由 Internet 分配号码机构(IANA)分配给区域 Internet 注册表(RIR),例如`APNIC`,该机构负责根 DNS ,IP 寻址和其他 Internet 协议资源。
130 |
131 | 然而IP 协议中非常重要的两个版本就是 IPv4 和 IPv6。
132 |
133 | #### IPv4
134 |
135 | IPv4 的全称是 `Internet Protocol version 4`,是 Internet 协议的第四版。IPv4 是一种无连接的协议,这个协议会尽最大努力交付数据包,也就是说它不能保证任何数据包能到达目的地,也不能保证所有的数据包都会按照正确的顺序到达目标主机,这些都是由上层比如传输控制协议控制的。也就是说,单从 IP 看来,这是一个不可靠的协议。
136 |
137 | IPv4 的数据报格式如下:
138 |
139 |
140 |
141 |
142 |
143 |
144 | IPv4的数据报包括≈:
145 |
146 | * `版本字段(Version)` 占用 4 字节,通信双方使用的版本必须一致,对于 IPv4 版本来说,字段值是 4。
147 |
148 | * `首部长度(Internet Header Length)` 占用 4 字节,首部长度说明首部有多少 32 位(4 字节)。由于 IPv4 首部可能包含不确定的选项,因此这个字段被用来确定数据的偏移量。
149 | 大多数 IP 不包含这个选项,所以一般首部长度设置为 5, 数据报为 20 字节 。
150 |
151 | * `服务类型(Differential Services Codepoint,DSCP)` 占用 6 bit,以便使用不同的 IP 数据报,比如一些低时延、高吞吐量和可靠性的数据报。
152 |
153 | 服务类型如下表所示:
154 |
155 |
156 |
157 |
158 |
159 | * `拥塞通告(Explicit Congestion Notification,ECN)` 占用 2 bit,它允许在不丢弃报文的同时通知对方网络拥塞的发生。ECN 是一种可选的功能,仅当两端都支持并希望使用,且底层网络支持时才被使用。
160 | 最开始 `DSCP` 和 `ECN` 统称为 `TOS`,也就是区分服务,但是后来被细化为了 `DSCP` 和 `ECN`。
161 |
162 | * `数据报长度(Total Length)` 占用 16 字节,这 16 位是包括在数据在内的总长度,理论上数据报的总长度为 2 的 16 次幂 - 1,最大长度是 65535 字节,但是实际上数据报很少有超过 1500 字节的。IP 规定所有主机都必须支持最小 576 字节的报文,但大多数现代主机支持更大的报文。
163 | 当下层的数据链路协议的最大传输单元(MTU)字段的值小于 IP 报文长度时,报文就必须被分片。
164 |
165 | * `标识符(Identification)` 占用 16 字节,这个字段用来标识所有的分片,因为分片不一定会按序到达,所以到达目标主机的所有分片会进行重组,每产生一个数据报,计数器加1,并赋值给此字段。
166 |
167 | * `标志(Flags)` 占用 3 bit,标志用于控制和识别分片,这 3 位分别是
168 |
169 | 0 位:保留,必须为0;
170 |
171 | 1 位:禁止分片(Don’t Fragment,DF),当 DF = 0 时才允许分片;
172 |
173 | 2 位:更多分片(More Fragment,MF),MF = 1 代表后面还有分片,MF = 0 代表已经是最后一个分片。
174 |
175 | 如果 DF 标志被设置为 1 ,但是路由要求必须进行分片,那么这条数据报回丢弃
176 |
177 | * `分片偏移(Fragment Offset)` 占用 13 位,它指明了每个分片相对于原始报文开头的偏移量,以 8 字节作单位。
178 |
179 | * `存活时间(Time To Live,TTL)` 占用 8 位,存活时间避免报文在互联网中迷失,比如陷入路由环路。存活时间以秒为单位,但小于一秒的时间均向上取整到一秒。在现实中,这实际上成了一个跳数计数器:报文经过的每个路由器都将此字段减 1,当此字段等于 0 时,报文不再向下一跳传送并被丢弃,这个字段最大值是 255。
180 |
181 | * 协议(Protocol) 占用 8 位,这个字段定义了报文数据区使用的协议。[协议内容](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml)。
182 |
183 | * `首部校验和(Header Checksum)` 占用 16 位,首部校验和会对字段进行纠错检查,在每一跳中,路由器都要重新计算出的首部检验和并与此字段进行比对,如果不一致,此报文将会被丢弃。
184 |
185 | * `源地址(Source address)` 占用 32 位,它是 IPv4 地址的构成条件,源地址指的是数据报的发送方.
186 |
187 | * `目的地址(Destination address)` 占用 32 位,它是 IPv4 地址的构成条件,目标地址指的是数据报的接收方.
188 |
189 | * `选项(Options)` 是附加字段,选项字段占用 1 - 40 个字节不等,一般会跟在目的地址之后。如果首部长度 > 5,就应该考虑选项字段。
190 |
191 | * `数据` 不是首部的一部分,因此并不被包含在首部检验和中。
192 |
193 | 在 IP 发送的过程中,每个数据报的大小是不同的,每个链路层协议能承载的网络层分组也不一样,有的协议能够承载大数据报,有的却只能承载很小的数据报,不同的链路层能够承载的数据报大小不同。
194 |
195 |
196 |
197 |
198 |
199 | #### IPv4 分片
200 |
201 | 一个链路层帧能承载的最大数据量叫做`最大传输单元(Maximum Transmission Unit, MTU)`,每个 IP 数据报封装在链路层帧中从一台路由器传到下一台路由器。
202 | 因为每个链路层所支持的最大 MTU 不一样,当数据报的大小超过 MTU 后,会在链路层进行分片,每个数据报会在链路层单独封装,每个较小的片都被称为 片(fragement)。
203 |
204 |
205 |
206 |
207 |
208 | 每个片在到达目的地后会进行重组,准确的来说是在运输层之前会进行重组,TCP 和 UDP 都会希望发送完整的、未分片的报文,出于性能的原因,分片重组不会在路由器中进行,而是会在目标主机中进行重组。
209 |
210 | 当目标主机收到从发送端发送过来的数据报后,它需要确定这些数据报中的分片是否是由源数据报分片传递过来的,如果是的话,还需要确定何时收到了分片中的最后一片,并且这些片会如何拼接一起成为数据报。
211 |
212 | 针对这些潜在的问题,IPv4 设计者将 标识、标志和片偏移放在 IP 数据报首部中。当生成一个数据报时,发送主机会为该数据报设置源和目的地址的同时贴上标识号。
213 | 发送主机通常将它发送的每个数据报的标识 + 1。当某路由器需要对一个数据报分片时,形成的每个数据报具有初始数据报的源地址、目标地址和标识号。当目的地从同一发送主机收到一系列数据报时,它能够检查数据报的标识号以确定哪些数据是由源数据报发送过来的。由于 IP 是一种不可靠的服务,分片可能会在网路中丢失,鉴于这种情况,通常会把分片的最后一个比特设置为 0 ,其他分片设置为 1,同时使用偏移字段指定分片应该在数据报的哪个位置。
214 |
215 | #### IPv4 寻址
216 |
217 | IPv4 支持三种不同类型的寻址模式,分别是:
218 |
219 | * 单播寻址模式:在这种模式下,数据只发送到一个目的地的主机。
220 |
221 |
222 |
223 |
224 |
225 | * 广播寻址模式:在此模式下,数据包将被寻址到网段中的所有主机。
226 | 这里客户端发送一个数据包,由所有服务器接收:
227 |
228 |
229 |
230 |
231 |
232 | * 组播寻址模式:此模式是前两种模式的混合,即发送的数据包既不指向单个主机也不指定段上的所有主机.
233 |
234 |
235 |
236 |
237 |
238 | #### IPv6
239 |
240 | 随着端系统接入的越来越多,IPv4 已经无法满足分配了,所以,IPv6 应运而生,IPv6 就是为了解决 IPv4 的地址耗尽问题而被标准化的网际协议。
241 |
242 | IPv4 的地址长度为 4 个 8 字节,即 32字节, 而 IPv6 的地址长度是原来的四倍,也就是 128 比特,一般写成 8 个 16 位字节。
243 |
244 | 从 IPv4 切换到 IPv6 及其耗时,需要将网络中所有的主机和路由器的 IP 地址进行设置,在互联网不断普及的今天,替换所有的 IP 是一个工作量及其庞大的任务。我们后面会说。
245 |
246 | IPv6 的地址是怎样的:
247 |
248 |
249 |
250 |
251 |
252 | * 版本与 IPv4 一样,版本号由 4 位构成,IPv6 版本号的值为 6。
253 |
254 | * 流量类型(Traffic Class) 占用 8 位,它就相当于 IPv4 中的服务类型(Type Of Service)。
255 |
256 | * 流标签(Flow Label) 占用 20 位,这 20字节用于标识一条数据报的流,能够对一条流中的某些数据报给出优先权,或者它能够用来对来自某些应用的数据报给出更高的优先权,只有流标签、源地址和目标地址一致时,才会被认为是一个流。
257 |
258 | * 有效载荷长度(Payload Length) 占用16 位,给出了 IPv6 数据报出基本首部以外的字节数,这 16位字节作为一个无符号整数,它给出了在 IPv6 数据报中跟在首部40 字节数据报首部后面的字节数量。
259 |
260 | * 下一个首部(Next Header) 占用 8位,它用于标识数据报中的内容需要交付给哪个协议,是 TCP 协议还是 UDP 协议。
261 |
262 | * 跳限制(Hop Limit) 占用 8 位,这个字段与 IPv4 的 TTL 意思相同。数据每经过一次路由就会减 1,减到 0 则会丢弃数据。
263 |
264 | * 源地址(Source Address) 占用 128 位 (8 个 16 位 ),表示发送端的 IP 地址。
265 |
266 | * 目标地址(Destination Address) 占用 128 位 (8 个 16 位 ),表示接收端 IP 地址。
267 |
268 | 可以看到,相较于 IPv4 ,IPv6 取消了下面几个字段:
269 |
270 | * 标识符、标志和比特偏移:IPv6 不允许在中间路由器上进行分片和重新组装。这种操作只能在端系统上进行,IPv6 将这个功能放在端系统中,加快了网络中的转发速度。
271 |
272 | * 首部校验和:因为在运输层和数据链路执行了报文段完整性校验工作,IP 设计者大概觉得在网络层中有首部校验和比较多余,所以去掉了。IP 更多专注的是快速处理分组数据。
273 |
274 | * 选项字段:选项字段不再是标准 IP 首部的一部分了,但是它并没有消失,而是可能出现在 IPv6 的扩展首部,也就是下一个首部中。
275 |
276 | #### IPv6 扩展首部
277 |
278 | IPv6 首部长度固定,无法将选项字段加入其中,取而代之的是 IPv6 使用了扩展首部
279 |
280 | 扩展首部通常介于 IPv6 首部与 `TCP/UDP`首部之间,在 IPv4 中可选长度固定为 40 字节,在 IPv6 中没有这样的限制。IPv6 的扩展首部可以是任意长度。扩展首部中还可以包含扩展首部协议和下一个扩展字段。
281 |
282 | IPv6 首部中没有标识和标志字段,对 IP 进行分片时,需要使用到扩展首部。
283 |
284 |
285 |
286 |
287 |
288 | 具体的扩展首部表如下所示:
289 |
290 |
291 |
292 |
293 |
294 | #### IPv6 特点
295 |
296 | IPv6 的特点在 IPv4 中得以实现,但是即便实现了 IPv4 的操作系统,也未必实现了 IPv4 的所有功能。
297 |
298 | IPv6 却将这些功能大众化了,也就表明这些功能在 IPv6 已经进行了实现,这些功能主要有:
299 |
300 | * 地址空间变得更大:这是 IPv6 最主要的一个特点,即支持更大的地址空间。
301 |
302 | * 精简报文结构: IPv6 要比 IPv4 精简很多,IPv4 的报文长度不固定,而且有一个不断变化的选项字段;IPv6 报文段固定,并且将选项字段,分片的字段移到了 IPv6 扩展头中,这就极大的精简了 IPv6 的报文结构。
303 |
304 | * 实现了自动配置:IPv6 支持其主机设备的状态和无状态自动配置模式。这样,没有 DHCP 服务器不会停止跨段通信。
305 |
306 | * 层次化的网络结构:IPv6 不再像 IPv4 一样按照 A、B、C等分类来划分地址,而是通过 IANA -> RIR -> ISP 这样的顺序来分配的。IANA 是国际互联网号码分配机构,RIR 是区域互联网注册管理机构,ISP 是一些运营商(例如电信、移动、联通)。
307 |
308 | * IPSec:IPv6 的扩展报头中有一个认证报头、封装安全净载报头,这两个报头是 IPsec 定义的。通过这两个报头网络层自己就可以实现端到端的安全,而无需像 IPv4 协议一样需要其他协议的帮助。
309 |
310 | * 支持任播:IPv6 引入了一种新的寻址方式,称为任播寻址。
311 |
312 | #### IPv6 地址
313 |
314 | 我们知道,IPv6 地址长度为 128 位,他所能表示的范围是 `2 ^ 128` 次幂,这个数字非常庞大,几乎涵盖了你能想到的所有主机和路由器,那么 IPv6 该如何表示呢?
315 |
316 | 一般我们将 128 比特的 IP 地址以每 16 比特为一组,并用 `:`号进行分隔,如果出现连续的 0 时还可以将 0 省略,并用 :: 两个冒号隔开,记住,一个 IP 地址只允许出现一次两个连续的冒号。
317 |
318 | 下面是一些 IPv6 地址的示例:
319 |
320 | * 二进制数表示
321 |
322 |
323 |
324 |
325 |
326 | * 用十六进制数表示
327 |
328 |
329 |
330 |
331 |
332 | * 出现两个冒号的情况
333 |
334 |
335 |
336 |
337 |
338 | A120 和 4CD 中间的 0 被 `::` 所替换了。
339 |
340 |
--------------------------------------------------------------------------------
/src/chapter02/golang.01.md:
--------------------------------------------------------------------------------
1 | ### 数据结构
2 |
3 | 数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。通常我们的程序设计=数据结构+算法,学好数据结构也是我们学习编程的重要一部分.
4 |
5 | 数据结构,本质上是数据之间的结构关系,或者理解成数据元素相互之间存在的一种或多种特定关系的集合。数据结构中的结构,也就是我们研究的主体对象。在数据结构中我们很少研究数据,因为数据在内存中的表现形式对于我们都是一样的,也就是二进制。传统上,我们把数据结构分为逻辑结构和物理结构。
6 |
7 | 数据结构,按照视点的不同,我们把数据结构分为逻辑结构和物理结构。
8 |
9 | #### 逻辑结构
10 |
11 | 逻辑结构指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后关系,而与他们在计算机中的存储位置无关。
12 |
13 | 逻辑结构分为以下四类:
14 |
15 | 1. 集合结构
16 |
17 | 集合结构中的数据元素同属于一个集合,他们之间是并列的关系,除此之外没有其他关系。
18 |
19 | 2. 线性结构
20 |
21 | 线性结构中的元素存在一对一的相互关系。
22 |
23 | 3. 树形结构
24 |
25 | 树形结构中的元素存在一对多的相互关系。
26 |
27 | 4. 图形结构
28 |
29 | 图形结构中的元素存在多对多的相互关系。
30 |
31 | 这四种类型之间的图形结构是:
32 |
33 |
34 |
35 |
36 |
37 |
38 | #### 物理结构
39 |
40 | 物理结构又叫存储结构,指数据的逻辑结构在计算机存储空间的存放形式。通俗的讲,物理结构研究的是数据在存储器中存放的形式。存储器主要针对于内存而言,像硬盘、软盘、光盘等外部存储器的数据组织通常用文件结构来描述。
41 |
42 | 数据在内存中的存储结构,也就是物理结构,分为两种:顺序存储结构和链式存储结构。
43 |
44 | 1. 顺序存储结构
45 |
46 | 顺序存储结构是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。数组就是顺序存储结构的典型代表。
47 |
48 | 2. 链式存储结构
49 |
50 | 链式存储结构是把数据元素存放在内存中的任意存储单元里,也就是可以把数据存放在内存的各个位置。这些数据在内存中的地址可以是连续的,也可以是不连续的。
51 |
52 | 和顺序存储结构不同的是,链式存储结构的数据元素之间是通过指针来连接的,我们可以通使用指针来找到某个数据元素的位置,然后对这个数据元素进行一些操作。
53 |
54 |
55 |
56 |
57 |
58 |
59 | 因此呢,数据结构是相互之间存在一种或多种特定 关系的数据元素的集合 。 同样是结构,从不同的角度来讨论,会有不同的分类.
60 |
61 | 逻辑结构分为: 集合结构, 线性结构, 树形结构, 图形结构.
62 |
63 | 物理结构分为: 顺序存储结构, 链式存储结构.
64 |
65 |
--------------------------------------------------------------------------------
/src/chapter03/golang.01.md:
--------------------------------------------------------------------------------
1 | ### 算法
2 |
3 | 什么是算法呢?算法是描述解决问题的方法。算法 (Algorithm) 这个单词最早出 现在被斯数学家阿勒·花刺子密在公元 825 年(相当于我们中国的唐朝时期)所写的《印度数字算术》中。
4 |
5 | 如今普遍认可的对算法的定义是:算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。即算法是描述解决问题的方法.
6 |
7 | * [一、算法介绍](#算法介绍)
8 | * [二、算法分析](#算法分析)
9 | * [数学模型](#数学模型)
10 | * [注意事项](#注意事项)
11 | * [ThreeSum](#threesum)
12 | * [倍率实验](#倍率实验)
13 | * [三、排序](#排序)
14 | * [选择排序](#选择排序)
15 | * [冒泡排序](#冒泡排序)
16 | * [插入排序](#插入排序)
17 | * [希尔排序](#希尔排序)
18 | * [归并排序](#归并排序)
19 | * [快速排序](#快速排序)
20 | * [堆排序](#堆排序)
21 | * [小结](#小结)
22 | * [四、并查集](#并查集)
23 | * [Quick Find](#quick-find)
24 | * [Quick Union](#quick-union)
25 | * [加权 Quick Union](#加权-quick-union)
26 | * [路径压缩的加权 Quick Union](#路径压缩的加权-quick-union)
27 | * [比较](#比较)
28 | * [五、栈和队列](#栈和队列)
29 | * [栈](#栈)
30 | * [队列](#队列)
31 | * [六、符号表](#符号表)
32 | * [初级实现](#初级实现)
33 | * [二叉查找树](#二叉查找树)
34 | * [2-3 查找树](#2-3-查找树)
35 | * [红黑树](#红黑树)
36 | * [散列表](#散列表)
37 | * [小结](#小结)
38 | * [七、其它](#其它)
39 | * [汉诺塔](#汉诺塔)
40 | * [哈夫曼编码](#哈夫曼编码)
41 | * [八、算法练习](#算法练习)
42 | * [参考资料](#参考资料)
43 |
44 |
45 | #### 算法介绍
46 |
47 | 算法是求解一个问题所需要的步骤所形成的解决方法,每一步包括一个或者多个操作。无论是现实生活中还是计算机中,解决同一个问题的方法可能有很多种,在这N多种算法中,肯定存在一个执行效率最快的方法,那么这个方法就是最优算法。
48 |
49 | 算法具有五个基本特征:输入、输出、有穷性、确定性和可行性。
50 |
51 | 1. 输入
52 |
53 | 一个算法具有零个或者多个输出。以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件。
54 |
55 | 2. 输出
56 |
57 | 算法至少有一个输出。也就是说,算法一定要有输出。输出的形式可以是打印,也可以使返回一个值或者多个值等。也可以是显示某些提示。
58 |
59 | 3. 有穷性
60 |
61 | 算法的执行步骤是有限的,算法的执行时间也是有限的。
62 |
63 | 4. 确定性
64 |
65 | 算法的每个步骤都有确定的含义,不会出现二义性。
66 |
67 | 5. 可行性
68 |
69 | 算法是可用的,也就是能够解决当前问题。
70 |
71 | 算法的设计要求:
72 |
73 | 1. 正确性
74 |
75 | 对于合法输入能够得到满足的结果,算法能够处理非法处理,并得到合理结果.算法对于边界数据和压力数据都能得到满足的结果。
76 |
77 | 2. 可读性
78 |
79 | 算法要方便阅读,理解和交流,只有自己能看得懂,其它人都看不懂,谈和好算法。
80 |
81 | 3. 健壮性
82 |
83 | 通俗的讲,一个好的算法应该具有捕获异常/处理异常的能力。另外,对于测试人员的压力测试、边界值测试等刁难的测试手段,算法应该能够轻松的扛过去。
84 |
85 | 4. 高性价比
86 |
87 | 利用最少的时间和资源得到满足要求的结果,可以通过(时间复杂度和空间复杂度来判定)。
88 |
89 | 通常判定一种算法的效率可以采用事后统计法和事前分析估算.
90 |
91 | 事后统计法缺点: 必须编写相应的测试程序,严重依赖硬件和运行时的环境,算法的数据采集相当的困难。
92 |
93 | 事前分析估算: 主要取决于问题的规模。
94 |
95 | 这里解释下时间复杂度和空间复杂度.
96 |
97 | 时间复杂度:
98 |
99 | 时间复杂度是对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
100 |
101 | 公式: `T(n) = O( f(n) )` ,其中f(n)是问题规模n的函数,也就是执行某个操作的次数。
102 |
103 | 在没有特殊说明的情况下,我们所分析的时间复杂度都是指最坏的时间复杂度。
104 |
105 |
106 | 空间复杂度:
107 |
108 | 空间复杂度是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
109 |
110 |
111 | 公式: S(n) = O( f(n) ),其中f(n)是在问题规模为n时所占用的内存空间大小。
112 |
113 | 大O表示法同样也适合空间复杂度。
114 |
115 | #### 常见算法
116 |
117 | 我们都知道,线性表分为无序线性表和有序线性表。
118 |
119 | 无序线性表的数据并不是按升序或者降序来排列的,所以在插入和删除时,没有什么必须遵守的规矩而可以插入在数据尾部或者删除在数据尾部(将待删除的数据和最后一个数据交换位置),但是在查找的时候,需要遍历整个数据集,影响了效率。
120 | 有序线性表的数据则想法,查找的时候因为数据有序,可以用二分法、插值法、斐波那契查找法来实现,但是,插入和删除需要维护有序的结构,会耗费大量的时间。
121 | 为了提高插入和删除的效率,二叉排序树登场了。
122 |
123 | 1. 二叉搜索树 (Binary Search Tree)
124 | 2. 平衡二叉查找树 ( Balanced Binary Search Tree )
125 | 3. 红黑树 (Red-Black Tree )
126 | 4. B-树和B+树 (B-Tree )
127 |
128 | * 二叉搜索树 (Binary Search Tree)
129 |
130 | 二叉搜索树的特点:
131 |
132 | * 所有非叶子结点至多拥有两个树(Left和Right);
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 | 先来说一下插入函数,SearchBST(BiTree T, int key,BiTree f,BiTree *p)中指针p具有非常重要的作用:
159 |
160 | * 若查找的key已经有在树中,则p指向该数据结点。
161 | * 若查找的key没有在树中,则p指向查找路径上最后一个结点,而这里的最后一个结点的位置和key应该被放入的位置存在着简单关系(要么当树空时直接插入作为根结点, 要么当树非空时新结点作为查找路径终止结点的左孩子或者右孩子插入 )。
162 |
163 | 二叉搜索树删除:
164 |
165 | * 被删除的节点是叶子节点,这时候只要把这个节点删除,再把指向这个节点的父节点指针置为空就行
166 |
167 | * 被删除的节点有左子树,或者有右子树,而且只有其中一个,那么只要把当前删除节点的父节点指向被删除节点的左子树或者右子树就行。
168 |
169 | * 被删除的节点既有左子树而且又有右子树,这时候需要把左子树的最右边的节点或者右子树最左边的节点提到被删除节点的位置,为什么要这样呢,根据二叉查找树的性质,父节点的指针一定比所有左子树的节点值大而且比右子树的节点的值小,为了删除父节点不破坏二叉查找树的平衡性,应当把左子树最大的节点或者右子树最小的节点放在父节点的位置,这样的话才能维护二叉查找树的平衡性。(我是找的右子树的最小节点)
170 |
171 | 二叉树的删除可以算是二叉树最为复杂的操作,删除的时候要考虑到很多种情况:
172 | 1. 被删除的节点是叶子节点
173 | 2. 被删除的节点只有左子节点
174 | 3. 被删除的节点只有右子节点
175 | 4. 被删除的有两个子节点
176 |
177 | 二叉搜索树的效率总结: 查找最好时间复杂度O(logN),最坏时间复杂度O(N)。 插入删除操作算法简单,时间复杂度与查找差不多。
178 |
179 | * 平衡二叉查找树 ( Balanced Binary Search Tree )
180 |
181 | 平衡二叉查找树 (Height-Balanced Binary Search Tree) 是一种二叉排序树,其中每一个结点的左子树和右子树的高度差不超过1(小于等于1)。
182 | 二叉树的平衡因子 (Balance Factor) 等于该结点的左子树深度减去右子树深度的值称为平衡因子。平衡因子只可能是-1,0,1。
183 | 距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡子树。
184 |
185 | 平衡二叉搜索树就是二叉树的构建过程中,每当插入一个结点,看是不是因为树的插入破坏了树的平衡性,若是,则找出最小不平衡树。在保持二叉树特性的前提下,调整最小不平衡子树中各个结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。因此主要是注意:步步调整,步步平衡 。
186 |
187 |
188 |
189 |
190 |
191 | 左旋和右旋的过程我们可以看到平衡因子从(0,1,2)变为(0,0,0),即是一种将非平衡状态转换为平衡状态的过程,这也是AVL树步步调整的核心。
192 |
193 | 再来观察一种复杂的情况:
194 |
195 |
196 |
197 |
198 |
199 | 新插入一个结点17,使得13的BF(-2)和21的BF(1)符号相反,如果直接左旋,调整后的树就不再是二叉排序树了。因此,正确做法是先在step1中调整符号,然后才能在step2中进行平衡操作。
200 |
201 | 由此,可以总结出平衡操作中非常必要的符号统一操作:
202 | ```markdown
203 | 最小不平衡子树的BF和它的子树的BF符号相反时,就需要对结点先进行一次旋转使得符号相同,再 反向旋转一次 才能够完成平衡操作。
204 | ```
205 |
206 | * 红黑树(Red–black tree)
207 |
208 | 红黑树(Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为"对称二叉B树",它现代的名字源于Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 在 O(log n)时间内完成查找,插入和删除,这里的n是树中元素的数目。
209 |
210 | B/B+ 树就是N叉(N-ary)平衡树了,每个节点可以有更多的孩子,新的值可以插在已有的节点里,而不需要改变树的高度,从而大量减少重新平衡和数据迁移的次数,这非常适合做数据库索引这种需要持久化在磁盘,同时需要大量查询和插入操作的应用。
211 |
212 |
213 | 红黑树用途和好处:
214 |
215 | 红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用,如实时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他数据结构中作为基础模板的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树实现。
216 |
217 | 红黑树在函数式编程中也特别有用,在这里它们是最常用的持久数据结构(persistent data structure)之一,它们用来构造关联数组和集合,每次插入、删除之后它们能保持为以前的版本。除了O(log n)}的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。
218 |
219 | 红黑树是2-3-4树的一种等同。换句话说,对于每个2-3-4树,都存在至少一个数据元素是同样次序的红黑树。在2-3-4树上的插入和删除操作也等同于在红黑树中颜色翻转和旋转。这使得2-3-4树成为理解红黑树背后的逻辑的重要工具,这也是很多介绍算法的教科书在红黑树之前介绍2-3-4树的原因,尽管2-3-4树在实践中不经常使用。
220 |
221 | 红黑树相对于AVL树来说,牺牲了部分平衡性以换取插入或者删除操作时少量的旋转操作,整体来说性能要优于AVL树。
222 |
223 | 红黑树性质:
224 |
225 | 红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外.树中的结点包含5个属性:color、key、left、right和p。如果一个结点没有子结点或父结点,则该结点相应指针属性值为NIL。
226 |
227 | 红黑树要求:
228 |
229 | 1. 节点是红色或黑色。
230 | 2. 根是黑色。
231 | 3. 所有叶子都是黑色(叶子是NIL节点)。
232 | 4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
233 | 5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
234 |
235 |
236 |
237 |
238 |
239 |
240 | 这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
241 |
242 | 要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
243 |
244 | 在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,但是这会改变一些性质并使算法复杂。为此,本文中我们使用"nil叶子"或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。
245 |
246 | 红黑树相比于BST和AVL树有什么优点?
247 |
248 | 红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。
249 | 红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。
250 |
251 | 相比于BST,因为红黑树可以能确保树的最长路径不大于两倍的最短路径的长度,所以可以看出它的查找效果是有最低保证的。在最坏的情况下也可以保证O(logN)的,这是要好于二叉查找树的。因为二叉查找树最坏情况可以让查找达到O(N)。
252 |
253 | 红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高,所以在插入和删除中所做的后期维护操作肯定会比红黑树要耗时好多,但是他们的查找效率都是O(logN),所以红黑树应用还是高于AVL树的。实际上插入,AVL 树和红黑树的速度取决于你所插入的数据.如果你的数据分布较好,则比较宜于采用 AVL树(例如随机产生系列数),但是如果你想处理比较杂乱的情况,则红黑树是比较快的。
254 |
255 | * AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
256 | * 而红黑树是弱平衡的,用非严格的平衡来换取增删节点时候旋转次数的降低;
257 | * 所以简单说,查找的次数远远大于插入和删除,那么选择AVL树;如果搜索、插入删除次数几乎差不多,应该选择RB树。
258 |
259 | 红黑树的应用:
260 | * 在Java中, TreeMap和TreeSet,Java 8中HashMap中TreeNode节点都采用了红黑树实现。
261 | * C++中,STL的map和set也应用了红黑树;
262 | * Linux进程调度Completely Fair Scheduler;
263 | * 用红黑树管理进程控制块epoll在内核中的实现,用红黑树管理事件块;
264 | * Nginx中,用红黑树管理timer等;
265 | * 红黑树的各种操作的时间复杂度是O(lgn),逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,IO次数多查找慢,效率低。
266 |
267 |
268 | #### 算法练习
269 |
270 | * 数组
271 |
272 | 1. 实现一个支持动态扩容的数组
273 | 2. 实现一个大小固定的有序数组,支持动态增删改操作
274 | 3. 实现两个有序数组合并为一个有序数组
275 |
276 | * 链表
277 |
278 | 1. 实现单链表、循环链表、双向链表,支持增删操作
279 | 2. 实现单链表反转
280 | 3. 实现两个有序的链表合并为一个有序链表
281 | 4. 实现求链表的中间结点
282 |
283 | * 栈
284 |
285 | 1. 用数组实现一个顺序栈
286 | 2. 用链表实现一个链式栈
287 | 3. 编程模拟实现一个浏览器的前进、后退功能
288 |
289 | * 队列
290 |
291 | 1. 用数组实现一个顺序队列
292 | 2. 用链表实现一个链式队列
293 | 3. 实现一个循环队列
294 |
295 | * 递归
296 | 1. 编程实现斐波那契数列求值f(n)=f(n-1)+f(n-2)
297 | 2. 编程实现求阶乘n!
298 | 3. 编程实现一组数据集合的全排列
299 |
300 | * 排序
301 |
302 | 1. 实现归并排序、快速排序、插入排序、冒泡排序、选择排序
303 | 2. 编程实现O(n)时间复杂度内找到一组数据的第K大元素
304 |
305 | * 二分查找
306 |
307 | 1. 实现一个有序数组的二分查找算法
308 | 2. 实现模糊二分查找算法(比如大于等于给定值的第一个元素)
309 |
310 | * 散列表
311 |
312 | 1. 实现一个基于链表法解决冲突问题的散列表
313 | 2. 实现一个LRU缓存淘汰算法
314 |
315 | * 字符串
316 |
317 | 1. 实现一个字符集,只包含a~z这26个英文字母的Trie树
318 | 2. 实现朴素的字符串匹配算法
319 |
320 | * 二叉树
321 |
322 | 1. 实现一个二叉查找树,并且支持插入、删除、查找操作
323 | 2. 实现查找二叉查找树中某个节点的后继、前驱节点
324 | 3. 实现二叉树前、中、后序以及按层遍历
325 |
326 | * 堆
327 |
328 | 1. 实现一个小顶堆、大顶堆、优先级队列
329 | 2. 实现堆排序
330 | 3. 利用优先级队列合并K个有序数组
331 | 4. 求一组动态数据集合的最大Top K
332 |
333 | * 图
334 |
335 | 1. 实现有向图、无向图、有权图、无权图的邻接矩阵和邻接表表示方法
336 | 2. 实现图的深度优先搜索、广度优先搜索
337 | 3. 实现Dijkstra算法、A*算法
338 | 4. 实现拓扑排序的Kahn算法、DFS算法
339 |
340 | * 回溯
341 |
342 | 1. 利用回溯算法求解八皇后问题
343 | 2. 利用回溯算法求解0-1背包问题
344 |
345 | * 分治
346 |
347 | 1. 利用分治算法求一组数据的逆序对个数
348 |
349 | * 动态规划
350 |
351 | 1. 0-1背包问题
352 | 2. 最小路径和
353 | 3. 编程实现莱文斯坦最短编辑距离
354 | 4. 编程实现查找两个字符串的最长公共子序列
355 | 5. 编程实现一个数据序列的最长递增子序列
356 |
357 |
358 | #### 参考资料
359 |
360 | * [Visualizations Algorithms](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)
361 | * [Algorithms](https://algs4.cs.princeton.edu/home/)
362 |
--------------------------------------------------------------------------------
/src/chapter07/golang.01.md:
--------------------------------------------------------------------------------
1 | ### Go的堆栈
2 |
3 | 在理解Go的堆栈分配前,我们先理解下什么是堆栈?在计算机中堆栈的概念分为:数据结构的堆栈和内存分配中堆栈。
4 |
5 | 数据结构的堆栈:
6 |
7 | 堆: 堆可以被看成是一棵树,如:堆排序。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。
8 |
9 | 栈: 一种先进后出的数据结构。
10 |
11 | 在内存分配中的堆和栈:
12 |
13 | 栈(操作系统): 由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
14 |
15 | 堆(操作系统): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
16 |
17 | #### 堆栈缓存方式
18 |
19 | 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
20 |
21 | 堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收),所以调用这些对象的速度要相对来得低一些。
22 |
23 | #### 变量是堆(heap)还是堆栈(stack)
24 |
25 | 官方给出的解释如下:
26 |
27 | ```markdown
28 | How do I know whether a variable is allocated on the heap or the stack?
29 |
30 | From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
31 |
32 | The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
33 |
34 | In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
35 | ```
36 | 从上面可以了解到, 您不需要知道。Go中的每个变量都存在,只要有对它的引用即可。实现选择的存储位置与语言的语义无关。
37 |
38 | 存储位置确实会影响编写高效的程序。如果可能,Go编译器将为该函数的堆栈帧中的函数分配本地变量。
39 |
40 | 但是,如果编译器在函数返回后无法证明变量未被引用,则编译器必须在垃圾收集堆上分配变量以避免悬空指针错误。此外,如果局部变量非常大,将它存储在堆而不是堆栈上可能更有意义。
41 |
42 | 在当前的编译器中,如果变量具有其地址,则该变量是堆上分配的候选变量。但是,基础的逃逸分析可以将那些生存不超过函数返回值的变量识别出来,并且因此可以分配在栈上。
43 |
44 | Go的编译器会决定在哪(堆or栈)分配内存,保证程序的正确性。
45 |
46 | #### Go的堆栈分配
47 |
48 | * 每个goroutine维护着一个栈空间,默认最大为4KB.
49 | * 当goroutine的栈空间不足时,golang会调用 `runtime.morestack` (汇编实现:asm_xxx.s)来进行动态扩容.
50 | * 连续栈是当栈空间不足的时候申请一个2倍于当前大小的新栈,并把所有数据拷贝到新栈,接下来的所有调用执行都发生在新栈上.
51 | * 每个function维护着各自的栈帧(stack frame),当function退出时会释放栈帧.
52 |
53 | #### Go function内的栈操作
54 |
55 | 用一段简单的代码来说明Go函数调用及传参时的栈操作:
56 |
57 | ```go
58 | package main
59 |
60 | func g(p int) int {
61 | return p+1;
62 | }
63 |
64 | func main() {
65 | c := g(4) + 1
66 | _ = c
67 | }
68 | ```
69 |
70 | 执行 `go tool compile -S main.go` 生成汇编,并截取其中的一部分来说明一下程序调用时的栈操作.
71 |
72 | ``` go
73 | "".g t=1 size=17 args=0x10 locals=0x0
74 | // 初始化函数的栈地址
75 | // 0-16表示函数初始地址为0,数据大小为16字节(input: 8字节,output: 8字节)
76 | // SB是函数寄存器
77 | 0x0000 00000 (test_stack.go:3) TEXT "".g(SB), $0-16
78 | // 函数的gc收集提示。提示0和1是用于局部函数调用参数,需要进行回收
79 | 0x0000 00000 (test_stack.go:3) FUNCDATA $0, gclocals·aef1f7ba6e2630c93a51843d99f5a28a(SB)
80 | 0x0000 00000 (test_stack.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
81 | // FP(frame point)指向栈底
82 | // 将FP+8位置的数据(参数p)放入寄存器AX
83 | 0x0000 00000 (test_stack.go:4) MOVQ "".p+8(FP), AX
84 | 0x0005 00005 (test_stack.go:4) MOVQ (AX), AX
85 | // 寄存器值自增
86 | 0x0008 00008 (test_stack.go:4) INCQ AX
87 | // 从寄存器中取出值,放入FP+16位置(返回值)
88 | 0x000b 00011 (test_stack.go:4) MOVQ AX, "".~r1+16(FP)
89 | // 返回,返回后程序栈的空间会被回收
90 | 0x0010 00016 (test_stack.go:4) RET
91 | 0x0000 48 8b 44 24 08 48 8b 00 48 ff c0 48 89 44 24 10 H.D$.H..H..H.D$.
92 | 0x0010 c3 .
93 | "".main t=1 size=32 args=0x0 locals=0x10
94 | 0x0000 00000 (test_stack.go:7) TEXT "".main(SB), $16-0
95 | 0x0000 00000 (test_stack.go:7) SUBQ $16, SP
96 | 0x0004 00004 (test_stack.go:7) MOVQ BP, 8(SP)
97 | 0x0009 00009 (test_stack.go:7) LEAQ 8(SP), BP
98 | 0x000e 00014 (test_stack.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
99 | 0x000e 00014 (test_stack.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
100 | // SP(stack point)指向栈顶
101 | // 把4存入SP的位置
102 | 0x000e 00014 (test_stack.go:8) MOVQ $4, "".c(SP)
103 | // 这里会看到没有第9行`call g()`的调用出现,这是因为go汇编编译器会把一些短函数变成内嵌函数,减少函数调用
104 | 0x0016 00022 (test_stack.go:10) MOVQ 8(SP), BP
105 | 0x001b 00027 (test_stack.go:10) ADDQ $16, SP
106 | 0x001f 00031 (test_stack.go:10) RET
107 | ```
108 |
109 | 事实上,即便我定义了指针调用,以上的数据也都是在栈上拷贝的;那么Golang中的数据什么时候会被分配到堆上呢?
110 |
111 | #### Golang逃逸分析
112 |
113 | * 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法,用于分析在程序的哪些地方可以访问到指针。
114 | * Golang在编译时的逃逸分析可以减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
115 | * 如果你定义的对象的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行,提高效率。
116 |
117 | 还是上面的那段程序代码,我们可以执行 `go build -gcflags '-m -l' test_stack.go`来进行逃逸分析,输出结果如下:
118 |
119 | ```bash
120 | # command-line-arguments
121 | ./test_stack.go:3: g p does not escape
122 | ./test_stack.go:9: main &c does not escape
123 | ```
124 | 可以看到,对象c是没有逃逸的,还是分配在栈上。
125 |
126 | 即便在一开始定义的时候直接把c定义为指针:
127 |
128 | ```go
129 | package main
130 |
131 | func g(p *int) int {
132 | return *p + 1
133 | }
134 |
135 | func main() {
136 | c := new(int)
137 | (*c) = 4
138 | _ = g(c)
139 | }
140 | ```
141 |
142 | 逃逸分析的结果仍然不会改变:
143 |
144 | ```bash
145 | # command-line-arguments
146 | ./test_stack.go:3: g p does not escape
147 | ./test_stack.go:8: main new(int) does not escape
148 | ```
149 |
150 | 那么,在什么时候指针对象才会逃逸呢?那就是在按址传递的时候,按址传递的是指针对象,会发生逃逸。
151 |
152 | * 按值传递
153 |
154 | ```go
155 | package main
156 |
157 | func g(p int) int {
158 | ret := p + 1
159 | return ret
160 | }
161 |
162 | func main() {
163 | c := 4
164 | _ = g(c)
165 | }
166 | ```
167 |
168 | 返回值ret是按值传递的,执行的是栈拷贝,不存在逃逸.
169 |
170 | * 按址传递
171 |
172 | ```go
173 | package main
174 |
175 | func g(p *int) *int {
176 | ret := *p + 1
177 | return &ret
178 | }
179 |
180 | func main() {
181 | c := new(int)
182 | *c = 4
183 | _ = g(c)
184 | }
185 | ```
186 |
187 | 返回值`&ret`是按址传递,传递的是指针对象,发生了逃逸,将对象存放在堆上以便外部调用.
188 |
189 | ```bash
190 | # command-line-arguments
191 | ./test_stack.go:5:9: &ret escapes to heap
192 | ./test_stack.go:4:14: moved to heap: ret
193 | ./test_stack.go:3:17: g p does not escape
194 | ./test_stack.go:9:10: main new(int) does not escape
195 | ```
196 |
197 | golang只有在function内的对象可能被外部访问时,才会把该对象分配在堆上.
198 |
199 | * 在g()方法中,ret对象的引用被返回到了方法外,因此会发生逃逸;而p对象只在g()内被引用,不会发生逃逸.
200 | * 在main()方法中,c对象虽然被g()方法引用了,但是由于引用的对象c没有在g()方法中发生逃逸,因此对象c的生命周期还是在`main()`中的,不会发生逃逸.
201 |
202 | ```go
203 | package main
204 |
205 | type Result struct {
206 | Data *int
207 | }
208 |
209 | func g(p *int) *Result {
210 | var ret Result
211 | ret.Data = p
212 | return &ret
213 | }
214 |
215 | func main() {
216 | c := new(int)
217 | *c = 4
218 | _ = g(c)
219 | }
220 | ```
221 |
222 | 逃逸分析结果:
223 |
224 | ```bash
225 | # command-line-arguments
226 | ./test_stack.go:10:9: &ret escapes to heap
227 | ./test_stack.go:8:6: moved to heap: ret
228 | ./test_stack.go:7:17: leaking param: p to result ~r1 level=-1
229 | ./test_stack.go:14:10: new(int) escapes to heap
230 | ```
231 | * 可以看到,ret和2.2中一样,存在外部引用,发生了逃逸.
232 | * 由于 `ret.Data` 是一个指针对象,p赋值给`ret.Data`后,也伴随p发生了逃逸.
233 | * main()中的对象c,由于作为参数p传入g()后发生了逃逸,因此c也发生了逃逸.
234 | * 当然,如果定义 `ret.Data` 为int(instead of *int)的话,对象p也是不会逃逸的(执行了拷贝).
235 |
236 | #### 开发建议大对象按址传递,小对象按值传递
237 |
238 | * 按址传递更高效,按值传递更安全(from William Kennedy).
239 | * 90%的bug都来自于指针调用.
240 |
241 | #### 初始化一个结构体,使用引用的方式来传递指针
242 |
243 | ```go
244 | func r() *Result{
245 | var ret Result
246 | ret.Data = ...
247 | ...
248 | return &ret
249 | }
250 | ```
251 | 只有返回ret对象的引用时才会把对象分配在堆上,我们不必要在一开始的时候就显式地把ret定义为指针,因为这样会对阅读代码也会容易产生误导.
252 |
253 | ```go
254 | ret = &Result{}
255 | ...
256 | return ret
257 | ```
258 | #### 参考链接
259 |
260 | * [Golang汇编快速指南](http://blog.rootk.com/post/golang-asm.html)
261 | * [Golang汇编](https://lrita.github.io/2017/12/12/golang-asm/#how)
262 | * [Golang汇编命令解读](http://www.cnblogs.com/yjf512/p/6132868.html)
263 | * [go语言连续栈](https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.5.html)
264 | * [为何说Goroutine的栈空间可以无限大](http://blog.xiayf.cn/2014/01/17/goroutine-stack-infinite/)
265 | * [Goroutine stack](https://studygolang.com/articles/10597)
266 |
--------------------------------------------------------------------------------
/src/chapter09/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Go内存管理
2 |
3 |
4 |
5 |
6 |
7 | Go内存管理基于`TCMalloc`,使用连续虚拟地址,以页(8k)为单位、多级缓存进行管理;在分配内存时,需要对size进行对齐处理,根据best-fit找到合适的mspan,对未用完的内存还会拆分成其他大小的mspan继续使用。
8 |
9 | 在new一个object时(忽略逃逸分析),根据object的size做不同的分配策略:
10 |
11 | * 极小对象(size<16byte)直接在当前P的mcache上的tiny缓存上分配;
12 | * 小对象(16byte <= size <= 32k)在当前P的mcache上对应slot的空闲列表中分配,无空闲列表则会继续向mcentral申请(还是没有则向mheap申请);
13 | * 大对象(size>32k)直接通过mheap申请。
14 |
15 | #### 1. 内存分配知识
16 |
17 | * 计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址(PA)
18 | * 现代处理器使用的是一种为虚拟寻址(VA)的寻址形式,最少的寻址单位是字
19 | * 虚拟地址映射物理地址是通过读取页表(page table)进行地址翻译完成的:页表存放在物理存储器中
20 | * MMU(内核吧物理页作为内存管理的基本单位)以页(page)大小为单位来管理系统中的页表
21 | * 在虚拟存储器的习惯说法中,DRAM缓存不命中的成为:缺页
22 | * page的结构与物理页相关,而非与虚拟页相关
23 | * 系统中的每个物理页都要分配一个page结构体
24 |
25 | 在了解Go的内存分配器原理之前,我们先了解一下“动态存储分配器”。
26 |
27 | #### 2. 动态存储分配器
28 |
29 | 动态存储分配器维护着一个进程的虚拟存储区域,这个区域称为 “堆”,堆可以视为一组大小不同的 “块”(chunk: 连续的虚拟存储片,无论内存分配器和垃圾回收算法都依赖连续地址)的集合,并交由动态存储器维护。
30 |
31 | 动态分配器主要分为:
32 |
33 | * 显式:常见的malloc.
34 | * 隐式:垃圾回收.
35 |
36 | 在Go中,分配器将其管理(大块 --> 小块)的内存块分为两种:
37 |
38 | * span:由多个连续的页(page)组成的大块内存.
39 | * object:将span按特定大小切分多个小块,每个小块可存储一个对象.
40 |
41 | 按照其用途,span面向内部管理,object面向对象分配.
42 |
43 | 分配器按页数来区分不同大小的span。比如,以页数为单位将span存放到管理数组中,需要时就以页数为索引进行查找。当然,span大小并非固定不变。在获取闲置span时,如果没找到大小合适的,那就返回页数更多的,此时会引发裁剪操作,多余部分将构成新的span被放回管理数组。分配器还会尝试将地址相邻的空闲span合并,以构建更大的内存块,减少碎片,提供更灵活的分配策略。
44 |
45 | 我们在go的malloc.go代码中:
46 |
47 | ```go
48 | const (
49 | _PageShift = 13
50 | _PageSize = 1 << _PageShift
51 | _PageMask = _PageSize - 1
52 | )
53 | ```
54 | 可以看到,用于存储对象的object,按照8字节倍数分为n种。这种方式虽然会造成一些内存浪费,但分配器只须面对有限的几种规格的小块内存,优化了分配和复用管理策略。
55 |
56 | ```markdown
57 | 分配器会尝试将多个微小对象组合到一个object快内,以节约内存。
58 | ```
59 |
60 | 我们看到msize.go的部分:
61 |
62 | ```go
63 | var class_to_size [_NumSizeClasses]int32
64 | var class_to_allocnpages [_NumSizeClasses]int32
65 | var class_to_divmagic [_NumSizeClasses]divMagic
66 |
67 | var size_to_class8 [1024/8 + 1]int8
68 | var size_to_class128 [(_MaxSmallSize-1024)/128 + 1]int8
69 |
70 | func sizeToClass(size int32) int32 {
71 | if size > _MaxSmallSize {
72 | throw("invalid size")
73 | }
74 | if size > 1024-8 {
75 | return int32(size_to_class128[(size-1024+127)>>7])
76 | }
77 | return int32(size_to_class8[(size+7)>>3])
78 | }
79 | ```
80 |
81 | 分配器初始化时,会构建对照表存储大小和规格对应关系,包括用来切分的span页数。
82 |
83 | ```go
84 | // Tunable constants.
85 | _MaxSmallSize = 32 << 10
86 | ```
87 |
88 | 从上面的代码段,我们大概可以指定若对象大小超出特定阈值限制,会被当做大对象特别对待。
89 |
90 | #### 3. mmap函数
91 |
92 | Unix进程可以使用mmap函数来创建新的虚拟存储区域并将对象映射到这些区域中。
93 |
94 | mmap函数要求内核创建一个新的虚拟存储区域,最好是从起始地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到新的区域。
95 |
96 | #### 4. 数据频繁分配与回收
97 |
98 | 对于有效地进行数据频繁分配与回收,减少碎片,一般有两种手段:
99 |
100 | * 空闲链表: 提供直接可供使用,已分配的结构块,缺点是不能全局控制.
101 | * slab:linux提供的,可以把不同的对象划分为所谓高速缓存组.
102 |
103 | #### 5. Go的内存分配
104 |
105 | Go的内存分配器是采用google自家的tcmalloc,tcmalloc是一个带内存池的分配器,底层直接调用mmap函数,并使用bestfit进行动态分配。
106 |
107 | Go为每个系统线程分配了一个本地MCache,少量的地址分配就是从MCache分配的,并且定期进行垃圾回收,所以可见go的分配器包含了显式与隐式的调用。
108 |
109 | Go定义的小块内存,大小上是指32K或以下的对象,go底层会把这些小块内存按照指定规格(大约100种)进行切割,为了避免随意切割,申请任意字节内存时会向上取整到接近的块,将整块分配(从空闲链表)给到申请者。
110 |
111 | Go内存分配主要组件:
112 |
113 | * MCache:层次与MHeap类似,对于每个尺寸的类别都有一个空闲链表。每个M都有自己的局部Mcache(小对象从它取,无需加锁),这就是Go能够在多线程中高效内存管理的重要原因.
114 |
115 | * MCentral:在无空闲内存的时候,向Mheap申请一个span,而不是多个,申请的span包含多少个page由central的sizeclass来确定(跨进程复用).
116 |
117 | * MHeap:负责将MSpan组织和管理起来.
118 |
119 | (1)分配过程:从free中分配,如果发生切割则将剩余的部分放回到free中.
120 |
121 | (2)回收过程:回收一个Mspan时,首选查找它相邻的地址,再通过map映射得到对应的Mspan,如果Mspan的state是未使用,则可以将 两者进行合并。最后将这页或者合并后的页归还到free分配池或者large中。
122 |
123 | #### 6. Go的内存模型
124 |
125 | Go的内存模型可以视为两级的内存模型:
126 |
127 | 第一级:Mheap为主要组件:分配的单位是页,但管理的单位是MSpan,每次分配都是用bestFit的原则分配连续的页,回收是采用位图的方式。
128 |
129 | 第二级:MCache为主要组件:相当于一个内存池, 回收采用引用计数器.
130 |
131 | 分配场景:
132 |
133 | 为对象分配内存须区分是在栈上分配还是在堆上分配。通常情况下,编译器有责任尽可能使用寄存器和栈来存储对象,这有助于提升性能,减少垃圾回收器的压力。
134 |
135 | 应用示例:
136 |
137 | ```go
138 | package main
139 |
140 | func patent() *int {
141 | x := new(int)
142 | *x = 1234
143 | return x
144 | }
145 |
146 | func main() {
147 | println(*patent())
148 | }
149 | ```
150 |
151 | 我们禁止内联优化来编译上面的代码:
152 | ```go
153 | > go build -gcflags="-l" -o patent main.go
154 | ```
155 |
156 | 得到的结果是:
157 | ```go
158 | main.go:3 0x2040 65488b0c25a0080000 GS MOVQ GS:0x8a0, CX
159 | main.go:3 0x2049 483b6110 CMPQ 0x10(CX), SP
160 | main.go:3 0x204d 7639 JBE 0x2088
161 | main.go:3 0x204f 4883ec18 SUBQ $0x18, SP
162 | main.go:3 0x2053 48896c2410 MOVQ BP, 0x10(SP)
163 | main.go:3 0x2058 488d6c2410 LEAQ 0x10(SP), BP
164 | main.go:4 0x205d 488d05dc3b0500 LEAQ 0x53bdc(IP), AX
165 | main.go:4 0x2064 48890424 MOVQ AX, 0(SP)
166 | main.go:4 0x2068 e823a70000 CALL runtime.newobject(SB)
167 | main.go:4 0x206d 488b442408 MOVQ 0x8(SP), AX
168 | main.go:5 0x2072 48c700d2040000 MOVQ $0x4d2, 0(AX)
169 | main.go:6 0x2079 4889442420 MOVQ AX, 0x20(SP)
170 | main.go:6 0x207e 488b6c2410 MOVQ 0x10(SP), BP
171 | main.go:6 0x2083 4883c418 ADDQ $0x18, SP
172 | main.go:6 0x2087 c3 RET
173 | main.go:3 0x2088 e893720400 CALL runtime.morestack_noctxt(SB)
174 | main.go:3 0x208d ebb1 JMP main.patent(SB)
175 | :-1 0x208f cc INT $0x3
176 | ```
177 | 从结果的 CALL runtime.newobject(SB) ,证明我们的对象在堆上进行分配了。
178 |
179 | 但当使用默认参数,我们观察下结果:
180 |
181 | ```go
182 | > go build -o patent main.go
183 | ```
184 |
185 | 当我们跟上面一样分析test的分配情况时:
186 | ```go
187 | > go tool objdump -s "main\.patent" patent
188 | ```
189 |
190 | 命令执行后,并没有输出, 我们分析下main方法:
191 |
192 | ```go
193 | > go tool objdump -s "main\.main" patent
194 | ```
195 |
196 | 得到的结果如下:
197 | ```go
198 | main.go:9 0x2040 65488b0c25a0080000 GS MOVQ GS:0x8a0, CX
199 | main.go:9 0x2049 483b6110 CMPQ 0x10(CX), SP
200 | main.go:9 0x204d 763d JBE 0x208c
201 | main.go:9 0x204f 4883ec18 SUBQ $0x18, SP
202 | main.go:9 0x2053 48896c2410 MOVQ BP, 0x10(SP)
203 | main.go:9 0x2058 488d6c2410 LEAQ 0x10(SP), BP
204 | main.go:10 0x205d 48c7442408d2040000 MOVQ $0x4d2, 0x8(SP)
205 | main.go:10 0x2066 e875210200 CALL runtime.printlock(SB)
206 | main.go:10 0x206b 48c70424d2040000 MOVQ $0x4d2, 0(SP)
207 | main.go:10 0x2073 e8b8280200 CALL runtime.printint(SB)
208 | main.go:10 0x2078 e8e3230200 CALL runtime.printnl(SB)
209 | main.go:10 0x207d e8ee210200 CALL runtime.printunlock(SB)
210 | main.go:11 0x2082 488b6c2410 MOVQ 0x10(SP), BP
211 | main.go:11 0x2087 4883c418 ADDQ $0x18, SP
212 | main.go:11 0x208b c3 RET
213 | main.go:9 0x208c e83f720400 CALL runtime.morestack_noctxt(SB)
214 | main.go:9 0x2091 ebad JMP main.main(SB)
215 | ```
216 | 这表明内联优化后的代码没有调用newobject在堆上分配内存。
217 |
218 | 编译器这么做的目的是:没有内联时,需要在两个栈帧间传递对象,因此在堆上分配而不是返回一个失效栈帧的数据。而当内联后,实际上就成看main栈帧内的局部变量,无需到堆上操作。
219 |
220 | 内存分配流程:
221 |
222 | 1、将小对象的大小向上取整到一个对应的尺寸类别(大约100种),查找相应的MCache的空闲链表,如果链表不空,直接从上面分配一个对象,这个过程不加锁
223 |
224 | 2、如果MCache自由链表是空的,通过MCentral的自由链表取一些对象进行补充
225 |
226 | 3、如果MCentral的自由链表是空的,则往MHeap中取用一些页对MCentral进行补充,然后将这些内存截断成特定规格
227 |
228 | 4、如果MHeap空或者没有足够大的页的情况下,从操作系统分配一组新的页面,一般在1MB以上
229 |
230 | Go分配流程核心源码实现:
231 |
232 | ```go
233 | func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
234 | if gcphase == _GCmarktermination {
235 | throw("mallocgc called with gcphase == _GCmarktermination")
236 | }
237 |
238 | if size == 0 {
239 | return unsafe.Pointer(&zerobase)
240 | }
241 |
242 | if flags&flagNoScan == 0 && typ == nil {
243 | throw("malloc missing type")
244 | }
245 |
246 | if debug.sbrk != 0 {
247 | align := uintptr(16)
248 | if typ != nil {
249 | align = uintptr(typ.align)
250 | }
251 | return persistentalloc(size, align, &memstats.other_sys)
252 | }
253 |
254 | // assistG is the G to charge for this allocation, or nil if
255 | // GC is not currently active.
256 | var assistG *g
257 | if gcBlackenEnabled != 0 {
258 | // Charge the current user G for this allocation.
259 | assistG = getg()
260 | if assistG.m.curg != nil {
261 | assistG = assistG.m.curg
262 | }
263 | // Charge the allocation against the G. We'll account
264 | // for internal fragmentation at the end of mallocgc.
265 | assistG.gcAssistBytes -= int64(size)
266 |
267 | if assistG.gcAssistBytes < 0 {
268 | // This G is in debt. Assist the GC to correct
269 | // this before allocating. This must happen
270 | // before disabling preemption.
271 | gcAssistAlloc(assistG)
272 | }
273 | }
274 |
275 | // Set mp.mallocing to keep from being preempted by GC.
276 | mp := acquirem()
277 | if mp.mallocing != 0 {
278 | throw("malloc deadlock")
279 | }
280 | if mp.gsignal == getg() {
281 | throw("malloc during signal")
282 | }
283 | mp.mallocing = 1
284 |
285 | shouldhelpgc := false
286 | dataSize := size
287 | c := gomcache()
288 | var s *mspan
289 | var x unsafe.Pointer
290 | if size <= maxSmallSize {
291 | if flags&flagNoScan != 0 && size < maxTinySize {
292 | //小对象分配
293 | off := c.tinyoffset
294 | // Align tiny pointer for required (conservative) alignment.
295 | if size&7 == 0 {
296 | off = round(off, 8)
297 | } else if size&3 == 0 {
298 | off = round(off, 4)
299 | } else if size&1 == 0 {
300 | off = round(off, 2)
301 | }
302 | if off+size <= maxTinySize && c.tiny != 0 {
303 | // The object fits into existing tiny block.
304 | x = unsafe.Pointer(c.tiny + off)
305 | c.tinyoffset = off + size
306 | c.local_tinyallocs++
307 | mp.mallocing = 0
308 | releasem(mp)
309 | return x
310 | }
311 | // Allocate a new maxTinySize block.
312 | s = c.alloc[tinySizeClass]
313 | v := s.freelist
314 | if v.ptr() == nil {
315 | systemstack(func() {
316 | c.refill(tinySizeClass)
317 | })
318 | shouldhelpgc = true
319 | s = c.alloc[tinySizeClass]
320 | v = s.freelist
321 | }
322 | s.freelist = v.ptr().next
323 | s.ref++
324 | // prefetchnta offers best performance, see change list message.
325 | prefetchnta(uintptr(v.ptr().next))
326 | x = unsafe.Pointer(v)
327 | (*[2]uint64)(x)[0] = 0
328 | (*[2]uint64)(x)[1] = 0
329 | // See if we need to replace the existing tiny block with the new one
330 | // based on amount of remaining free space.
331 | if size < c.tinyoffset || c.tiny == 0 {
332 | c.tiny = uintptr(x)
333 | c.tinyoffset = size
334 | }
335 | size = maxTinySize
336 | } else {
337 | var sizeclass int8
338 | if size <= 1024-8 {
339 | sizeclass = size_to_class8[(size+7)>>3]
340 | } else {
341 | sizeclass = size_to_class128[(size-1024+127)>>7]
342 | }
343 | size = uintptr(class_to_size[sizeclass])
344 | s = c.alloc[sizeclass]
345 | v := s.freelist
346 | if v.ptr() == nil {
347 | systemstack(func() {
348 | c.refill(int32(sizeclass))
349 | })
350 | shouldhelpgc = true
351 | s = c.alloc[sizeclass]
352 | v = s.freelist
353 | }
354 | s.freelist = v.ptr().next
355 | s.ref++
356 | // prefetchnta offers best performance, see change list message.
357 | prefetchnta(uintptr(v.ptr().next))
358 | x = unsafe.Pointer(v)
359 | if flags&flagNoZero == 0 {
360 | v.ptr().next = 0
361 | if size > 2*sys.PtrSize && ((*[2]uintptr)(x))[1] != 0 {
362 | memclr(unsafe.Pointer(v), size)
363 | }
364 | }
365 | }
366 | } else {
367 | var s *mspan
368 | shouldhelpgc = true
369 | systemstack(func() {
370 | s = largeAlloc(size, flags)
371 | })
372 | x = unsafe.Pointer(uintptr(s.start << pageShift))
373 | size = s.elemsize
374 | }
375 |
376 | if flags&flagNoScan != 0 {
377 | // All objects are pre-marked as noscan. Nothing to do.
378 | } else {
379 | if typ == deferType {
380 | dataSize = unsafe.Sizeof(_defer{})
381 | }
382 | heapBitsSetType(uintptr(x), size, dataSize, typ)
383 | if dataSize > typ.size {
384 | // Array allocation. If there are any
385 | // pointers, GC has to scan to the last
386 | // element.
387 | if typ.ptrdata != 0 {
388 | c.local_scan += dataSize - typ.size + typ.ptrdata
389 | }
390 | } else {
391 | c.local_scan += typ.ptrdata
392 | }
393 | publicationBarrier()
394 | }
395 | if gcphase == _GCmarktermination || gcBlackenPromptly {
396 | systemstack(func() {
397 | gcmarknewobject_m(uintptr(x), size)
398 | })
399 | }
400 |
401 | if raceenabled {
402 | racemalloc(x, size)
403 | }
404 | if msanenabled {
405 | msanmalloc(x, size)
406 | }
407 |
408 | mp.mallocing = 0
409 | releasem(mp)
410 |
411 | if debug.allocfreetrace != 0 {
412 | tracealloc(x, size, typ)
413 | }
414 |
415 | if rate := MemProfileRate; rate > 0 {
416 | if size < uintptr(rate) && int32(size) < c.next_sample {
417 | c.next_sample -= int32(size)
418 | } else {
419 | mp := acquirem()
420 | profilealloc(mp, x, size)
421 | releasem(mp)
422 | }
423 | }
424 |
425 | if assistG != nil {
426 | // Account for internal fragmentation in the assist
427 | // debt now that we know it.
428 | assistG.gcAssistBytes -= int64(size - dataSize)
429 | }
430 |
431 | if shouldhelpgc && gcShouldStart(false) {
432 | gcStart(gcBackgroundMode, false)
433 | }
434 |
435 | return x
436 | }
437 | ```
438 |
439 | Go也有happens-before ,go happens-before常用的三原则是:
440 |
441 | * 对于不带缓冲区的channel,对其写happens-before对其读.
442 | * 对于带缓冲区的channel,对其读happens-before对其写.
443 | * 对于不带缓冲的channel的接收操作 happens-before 相应channel的发送操作完成.
444 |
--------------------------------------------------------------------------------
/src/chapter10/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang runtime的调度
2 |
3 | Golang作为一个为并发而产生的语言, 从Golang产生的那一刻就注定它具有高并发的特性,而 Go 语言中的并发(并行)编程是经由 goroutine 实现的,goroutine 是 Golang 最重要的特性之一,具有使用成本低、消耗资源低、能效高等特点,官方宣称原生 goroutine 并发成千上万不成问题,于是它也成为 Gopher们经常使用的特性。
4 |
5 | Goroutine,是Go语言基于并发编程的核心。那么 Goroutine 是什么?
6 |
7 | 通常 goroutine 会被当做 coroutine(协程)的 golang 实现,从比较粗浅的层面来看,这种认知也算是合理.
8 |
9 | 但实际上,goroutine 并非传统意义上的协程,现在主流的线程模型分三种:内核级线程模型、用户级线程模型和两级线程模型(也称混合型线程模型),传统的协程库属于用户级线程模型.
10 |
11 | 而 goroutine 和它的Go Scheduler在底层实现上其实是属于两级线程模型,通常 goroutine 会被当做 coroutine(协程)的 golang 实现,从比较粗浅的层面来看,这种认知也算是合理.
12 |
13 | 但是,goroutine 并非传统意义上的协程,目前主流的线程模型主要分为三种:
14 |
15 | 1. 内核级线程模型.
16 | 2. 用户级线程模型和两级线程模型(也称混合型线程模型).
17 | 3. 传统的协程库属于用户级线程模型.
18 |
19 | 因此,有时候为了方便理解可以简单把 goroutine 类比成协程,但心里一定要有个清晰的认知`goroutine`并不等同于协程。
20 |
21 | #### 线程
22 |
23 | 计算机在是早期的单进程操作系统,这样就面临2个问题:
24 |
25 | 1. 单一的执行流程,计算机只能一个任务一个任务处理。
26 |
27 | 2. 进程阻塞所带来的CPU时间浪费。
28 |
29 | 随着技术的发展,后面的操作系统就具有了最早的并发能力:多进程并发,当一个进程阻塞的时候,切换到另外等待执行的进程,这样就能尽量把CPU利用起来,CPU就不浪费了.
30 |
31 | 在多进程/多线程的操作系统中,就是为了解决在单线程系统中的阻塞的问题,因为一个进程阻塞cpu可以立刻切换到其他进程中去执行,而且调度cpu的算法可以保证在运行的进程都可以被分配到cpu的运行时间片。这样从宏观来看,似乎多个进程是在同时被运行。
32 |
33 | 但新的问题就又出现了,进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU虽然利用起来了,但如果进程过多,CPU有很大的一部分都被用来进行进程调度了。
34 |
35 |
36 |
37 |
38 |
39 | 但是要怎么才能提高CPU的利用率呢?对于Linux操作系统来讲,cpu对进程的态度和线程的态度是一样的。
40 |
41 | 很明显,CPU调度切换的是进程和线程。尽管线程看起来很美好,但实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突等。
42 |
43 | 随着时间的发展,工程师们发现,其实一个线程分为“内核态“线程和”用户态“线程。 即一个“用户态线程”必须要绑定一个“内核态线程”,但是CPU并不知道有“用户态线程”的存在,它只知道它运行的是一个“内核态线程”(系统的PCB进程控制块)。
44 |
45 |
46 |
47 |
48 |
49 | 这样,我们可以分类一下,内核线程依然叫“线程(thread)”,用户线程就叫“协程(co-routine)".
50 |
51 | 线程的实现模型主要有 3 种: 内核级线程模型,用户级线程模型和两级线程模型(也称混合型线程模型),它们之间最大的差异就在于用户线程与内核调度实体(KSE,Kernel Scheduling Entity)之间的对应关系上。而所谓的内核调度实体 KSE 就是指可以被操作系统内核调度器调度的对象实体。
52 |
53 | 简单来说 KSE 就是内核级线程,是操作系统内核的最小调度单元,也就是我们写代码的时候通俗理解上的线程了。
54 |
55 | #### 用户级线程模型
56 |
57 | 用户线程与内核线程 KSE 是多对一(N:1)的映射模型,多个用户线程的一般从属于单个进程并且多线程的调度是由用户自己的线程库来完成,线程的创建、销毁以及多线程之间的协调等操作都是由用户自己的线程库来负责而无须借助系统调用来实现。一个进程中所有创建的线程都只和同一个 KSE 在运行时动态绑定,也就是说,操作系统只知道用户进程而对其中的线程是无感知的,内核的所有调度都是基于用户进程。许多语言实现的 协程库 基本上都属于这种方式(比如 python 的 gevent)。
58 |
59 | 由于线程调度是在用户层面完成的,也就是相较于内核调度不需要让 CPU 在用户态和内核态之间切换,这种实现方式相比内核级线程可以做的很轻量级,对系统资源的消耗会小很多,因此可以创建的线程数量与上下文切换所花费的代价也会小得多。但该模型有个原罪:并不能做到真正意义上的并发,假设在某个用户进程上的某个用户线程因为一个阻塞调用(比如 I/O 阻塞)而被 CPU 给中断(抢占式调度)了,那么该进程内的所有线程都被阻塞(因为单个用户进程内的线程自调度是没有 CPU 时钟中断的,从而没有轮转调度),整个进程被挂起。即便是多 CPU 的机器,也无济于事,因为在用户级线程模型下,一个 CPU 关联运行的是整个用户进程,进程内的子线程绑定到 CPU 执行是由用户进程调度的,内部线程对 CPU 是不可见的,此时可以理解为 CPU 的调度单位是用户进程。
60 |
61 | 所以很多的协程库会把自己一些阻塞的操作重新封装为完全的非阻塞形式,然后在以前要阻塞的点上,主动让出自己,并通过某种方式通知或唤醒其他待执行的用户线程在该 KSE 上运行,从而避免了内核调度器由于 KSE 阻塞而做上下文切换,这样整个进程也不会被阻塞了。
62 |
63 |
64 |
65 |
66 |
67 | * 特点:
68 |
69 | N个协程绑定1个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。但也有很大的缺点,1个进程的所有协程都绑定在1个线程上.
70 |
71 | * 缺点
72 |
73 | 1. 某个程序用不了硬件的多核加速能力.
74 |
75 | 2. 一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,无并发能力.
76 |
77 | #### 内核级线程模型
78 |
79 | 用户线程与内核线程 KSE 是一对一(1:1)的映射模型,也就是每一个用户线程绑定一个实际的内核线程,而线程的调度则完全交付给操作系统内核去做,应用程序对线程的创建、终止以及同步都基于内核提供的系统调用来完成,大部分编程语言的线程库(比如 Java 的 `java.lang.Thread`、C++11 的 `std::thread` 等等)都是对操作系统的线程(内核级线程)的一层封装,创建出来的每个线程与一个独立的 KSE 静态绑定,因此其调度完全由操作系统内核调度器去做,也就是说,一个进程里创建出来的多个线程每一个都绑定一个 KSE。
80 |
81 | 这种模型的优势和劣势同样明显:优势是实现简单,直接借助操作系统内核的线程以及调度器,所以 CPU 可以快速切换调度线程,于是多个线程可以同时运行,因此相较于用户级线程模型它真正做到了并行处理;但它的劣势是,由于直接借助了操作系统内核来创建、销毁和以及多个线程之间的上下文切换和调度,因此资源成本大幅上涨,且对性能影响很大。
82 |
83 |
84 |
85 |
86 |
87 | * 特点:
88 |
89 | 1个协程绑定1个线程,这种最容易实现。协程的调度都由CPU完成了,不存在N:1缺点。
90 |
91 | * 缺点
92 |
93 | 协程的创建、删除和切换的代价都由CPU完成,有点略显昂贵了。
94 |
95 | #### 两级线程模型
96 |
97 | 两级线程模型是博采众长之后的产物,充分吸收前两种线程模型的优点且尽量规避它们的缺点。在此模型下,用户线程与内核 KSE 是多对多(N : M)的映射模型:首先,区别于用户级线程模型,两级线程模型中的一个进程可以与多个内核线程 KSE 关联,也就是说一个进程内的多个线程可以分别绑定一个自己的 KSE,这点和内核级线程模型相似;其次,又区别于内核级线程模型,它的进程里的线程并不与 KSE 唯一绑定,而是可以多个用户线程映射到同一个 KSE,当某个 KSE 因为其绑定的线程的阻塞操作被内核调度出 CPU 时,其关联的进程中其余用户线程可以重新与其他 KSE 绑定运行。
98 |
99 | 所以,两级线程模型既不是用户级线程模型那种完全靠自己调度的也不是内核级线程模型完全靠操作系统调度的,而是中间态(自身调度与系统调度协同工作),因为这种模型的高度复杂性,操作系统内核开发者一般不会使用,所以更多时候是作为第三方库的形式出现,而 Go 语言中的 runtime 调度器就是采用的这种实现方案,实现了 Goroutine 与 KSE 之间的动态关联,不过 Go 语言的实现更加高级和优雅.
100 |
101 | 该模型为何被称为两级?即用户调度器实现用户线程到 KSE 的调度,内核调度器实现 KSE 到 CPU 上的`调度`。
102 |
103 |
104 |
105 |
106 |
107 | * 特点
108 |
109 | G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会向其他的MP组合偷取一个可执行的G来执行, 即M个协程绑定1个线程,是N:1和1:1类型的结合,克服了以上2种模型的缺点,但实现起来最为复杂。
110 |
111 | 协程跟线程是有区别的,线程由CPU调度是抢占式的,协程由用户态调度是协作式的,一个协程让出CPU后,才执行下一个协程。
112 |
113 | #### Go的协程Goroutine
114 |
115 | Go为了提供更容易使用的并发方法,使用了goroutine和channel。goroutine来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被runtime调度,转移到其他可运行的线程上。最关键的是,开发人员是看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。
116 |
117 | Go中,协程被称为goroutine,它非常轻量,一个goroutine只占几KB,并且这几KB就足够goroutine运行完,这就能在有限的内存空间内支持大量goroutine,支持了更多的并发。虽然一个goroutine的栈只占几KB,但实际是可伸缩的,如果需要更多内容,runtime会自动为goroutine分配。
118 |
119 | Goroutine特点:
120 |
121 | * 占用内存更小(几Kb).
122 |
123 | * 调度更灵活(runtime调度).
124 |
125 | #### GPM模型
126 |
127 | 在Go中,线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上。
128 |
129 |
130 |
131 |
132 |
133 | 每一个 OS 线程都有一个固定大小的内存块(一般会是 2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。这个固定大小的栈同时很大又很小。因为 2MB 的栈对于一个小小的 goroutine 来说是很大的内存浪费,而对于一些复杂的任务(如深度嵌套的递归)来说又显得太小。因此,Go 语言做了它自己的`线程`。
134 |
135 | 在 Go 语言中,每一个 goroutine 是一个独立的执行单元,相较于每个 OS 线程固定分配 2M 内存的模式,goroutine 的栈采取了动态扩容方式, 初始时仅为 2KB,随着任务执行按需增长,最大可达 1GB(64 位机器最大是 1G,32 位机器最大是 256M),且完全由 golang 自己的调度器 Go Scheduler 来调度。
136 |
137 | 此外,GC 还会周期性地将不再使用的内存回收,收缩栈空间。 因此,Go 程序可以同时并发成千上万个 goroutine 是得益于它强劲的调度器和高效的内存模型。Go 的创造者大概对 goroutine 的定位就是屠龙刀,因为他们不仅让 goroutine 作为 golang 并发编程的最核心组件(开发者的程序都是基于 goroutine 运行的)而且 golang 中的许多标准库的实现也到处能见到 goroutine 的身影,比如 net/http 这个包,甚至语言本身的组件 runtime 运行时和 GC 垃圾回收器都是运行在 goroutine 上的,作者对 goroutine 的厚望可见一斑。
138 |
139 | 任何用户线程最终肯定都是要交由 OS 线程来执行的,goroutine(称为 G)也不例外,但是 G 并不直接绑定 OS 线程运行,而是由 Goroutine Scheduler 中的 P - Logical Processor (逻辑处理器)来作为两者的传递者,P 可以看作是一个抽象的资源或者一个上下文,一个 P 绑定一个 OS 线程.
140 |
141 | 在 golang 的实现里把 OS 线程抽象成一个数据结构:M,G 实际上是由 M 通过 P 来进行调度运行的,但是在 G 的层面来看,P 提供了 G 运行所需的一切资源和环境,因此在 G 看来 P 就是运行它的 “CPU”,由 G、P、M 这三种由 Go 抽象出来的实现,最终形成了 Go 调度器的基本结构:
142 |
143 | * G: 表示 Goroutine,每个 Goroutine 对应一个 G 结构体,G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重用。G 并非执行体,每个 G 需要绑定到 P 才能被调度执行。
144 |
145 | * P: Processor,表示逻辑处理器, 对 G 来说,P 相当于 CPU 核,G 只有绑定到 P(在 P 的 local runq 中)才能被调度。对 M 来说,P 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量),P 的数量由用户设置的 GOMAXPROCS 决定,但是不论 GOMAXPROCS 设置为多大,P 的数量最大为 256。
146 |
147 | * M: Machine,OS 线程抽象,负责调度任务, 代表着真正执行计算的资源,在绑定有效的 P 后,进入 schedule 循环;而 schedule 循环的机制大致是从 Global 队列、P 的 Local 队列以及 wait 队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M,如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础,M 的数量是不定的,由 Go Runtime 调整,为了防止创建过多 OS 线程导致系统调度不过来,目前默认最大限制为 10000 个。
148 |
149 | 在新的版本1.13.6中Go的GPM的模型的源码位于`src/runtime/runtime2.go`. 至于为什么M的的最大数量限制在10000,[在这里可以查看](https://github.com/golang/go/blob/master/src/runtime/proc.go#L540)
150 |
151 |
152 | 关于 P, 其实在 Go 1.0 发布的时候,它的调度器其实 G-M 模型,也就是没有 P 的,调度过程全由 G 和 M 完成,这个模型暴露出一些问题:
153 |
154 | 单一全局互斥锁(Sched.Lock)和集中状态存储的存在导致所有 goroutine 相关操作,比如:创建、重新调度等都要上锁;
155 |
156 | * goroutine 传递问题:M 经常在 M 之间传递可运行的 goroutine,这导致调度延迟增大以及额外的性能损耗;
157 |
158 | * 每个 M 做内存缓存,导致内存占用过高,数据局部性较差;
159 |
160 | * 由于 syscall 调用而形成的剧烈的 `worker thread` 阻塞和解除阻塞,导致额外的性能损耗。
161 |
162 | 这些问题实在太严重了,导致 Go1.0 虽然号称原生支持并发,却在并发性能上一直饱受诟病,于是Dmitry Vyukov在[Scalable Go Scheduler Design Doc](https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#heading=h.mmq8lm48qfcw)提出该模型在并发伸缩性方面的问题,并通过加入P(Processors)来改进该问题。
163 |
164 | 在重新设计和实现了 Go 调度器(在原有的 G-M 模型中引入了 P)并且实现了一个叫做 [work-stealing](https://en.wikipedia.org/wiki/Work_stealing) 的调度算法:
165 |
166 | * 每个 P 维护一个 G 的本地队列;
167 |
168 | * 当一个 G 被创建出来,或者变为可执行状态时,就把他放到 P 的可执行队列中;
169 |
170 | * 当一个 G 在 M 里执行结束后,P 会从队列中把该 G 取出;如果此时 P 的队列为空,即没有其他 G 可以执行, M 就随机选择另外一个 P,从其可执行的 G 队列中取走一半。
171 |
172 | 该算法避免了在 goroutine 调度时使用全局锁。
173 |
174 | #### GPM调度流程
175 |
176 | Go 调度器工作时会维护两种用来保存 G 的任务队列:一种是一个 Global 任务队列,一种是每个 P 维护的 Local 任务队列。
177 |
178 | 当通过go关键字创建一个新的 goroutine 的时候,它会优先被放入 P 的本地队列。为了运行 goroutine,M 需要持有(绑定)一个 P,接着 M 会启动一个 OS 线程,循环从 P 的本地队列里取出一个 goroutine 并执行。
179 |
180 | 当然上面提到的 `work-stealing`调度算法:当 M 执行完了当前 P 的 Local 队列里的所有 G 后,P 也不会就这么在那干等着啥都不干,它会先尝试从 Global 队列寻找 G 来执行,如果 Global 队列为空,它会随机挑选另外一个 P,从它的队列里中拿走一半的 G 到自己的队列中执行。
181 |
182 | ```go
183 |
184 | // go1.13.6 src/runtime/proc.go
185 |
186 | // 省略了GC检查等其它细节,只保留了主要流程
187 | // g: G结构体定义
188 | // sched: Global队列
189 | // 获取一个待执行的G
190 | // 尝试从其他P中steal,从全局队列中获取g,轮询网络。
191 | func findrunnable() (gp *g, inheritTime bool) {
192 | // 获取当前的G对象
193 | _g_ := getg()
194 |
195 | // The conditions here and in handoffp must agree: if
196 | // findrunnable would return a G to run, handoffp must start
197 | // an M.
198 |
199 | top:
200 | // 获取当前P对象
201 | _p_ := _g_.m.p.ptr()
202 | if sched.gcwaiting != 0 {
203 | gcstopm()
204 | goto top
205 | }
206 | if _p_.runSafePointFn != 0 {
207 | runSafePointFn()
208 | }
209 | if fingwait && fingwake {
210 | if gp := wakefing(); gp != nil {
211 | ready(gp, 0, true)
212 | }
213 | }
214 | if *cgo_yield != nil {
215 | asmcgocall(*cgo_yield, nil)
216 | }
217 |
218 | // 1. 尝试从P的Local队列中取得G 优先_p_.runnext 然后再从Local队列中取
219 | if gp, inheritTime := runqget(_p_); gp != nil {
220 | return gp, inheritTime
221 | }
222 |
223 | // 2. 尝试从Global队列中取得G
224 | if sched.runqsize != 0 {
225 | lock(&sched.lock)
226 | // globrunqget从Global队列中获取G 并转移一批G到_p_的Local队列
227 | gp := globrunqget(_p_, 0)
228 | unlock(&sched.lock)
229 | if gp != nil {
230 | return gp, false
231 | }
232 | }
233 |
234 | // Poll network.
235 | // This netpoll is only an optimization before we resort to stealing.
236 | // We can safely skip it if there are no waiters or a thread is blocked
237 | // in netpoll already. If there is any kind of logical race with that
238 | // blocked thread (e.g. it has already returned from netpoll, but does
239 | // not set lastpoll yet), this thread will do blocking netpoll below
240 | // anyway.
241 |
242 | // 3. 检查netpoll任务
243 | if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Load64(&sched.lastpoll) != 0 {
244 | if list := netpoll(false); !list.empty() { // non-blocking
245 | gp := list.pop()
246 | // netpoll返回的是G链表,将其它G放回Global队列
247 | injectglist(&list)
248 | casgstatus(gp, _Gwaiting, _Grunnable)
249 | if trace.enabled {
250 | traceGoUnpark(gp, 0)
251 | }
252 | return gp, false
253 | }
254 | }
255 |
256 | // 4. 尝试从其它P窃取任务
257 | procs := uint32(gomaxprocs)
258 | if atomic.Load(&sched.npidle) == procs-1 {
259 | // Either GOMAXPROCS=1 or everybody, except for us, is idle already.
260 | // New work can appear from returning syscall/cgocall, network or timers.
261 | // Neither of that submits to local run queues, so no point in stealing.
262 | goto stop
263 | }
264 | // If number of spinning M's >= number of busy P's, block.
265 | // This is necessary to prevent excessive CPU consumption
266 | // when GOMAXPROCS>>1 but the program parallelism is low.
267 | if !_g_.m.spinning && 2*atomic.Load(&sched.nmspinning) >= procs-atomic.Load(&sched.npidle) {
268 | goto stop
269 | }
270 | if !_g_.m.spinning {
271 | _g_.m.spinning = true
272 | atomic.Xadd(&sched.nmspinning, 1)
273 | }
274 | for i := 0; i < 4; i++ {
275 | // 随机P的遍历顺序
276 | for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
277 | if sched.gcwaiting != 0 {
278 | goto top
279 | }
280 | stealRunNextG := i > 2 // first look for ready queues with more than 1 g
281 | // runqsteal执行实际的steal工作,从目标P的Local队列转移一般的G过来
282 | // stealRunNextG指是否steal目标P的p.runnext G
283 | if gp := runqsteal(_p_, allp[enum.position()], stealRunNextG); gp != nil {
284 | return gp, false
285 | }
286 | }
287 | }
288 |
289 | stop:
290 |
291 | // 我们没事做如果我们处于GC标记阶段,可以安全地扫描和三色法标记对象为黑色并进行工作,请运行空闲时间标记,而不是放弃P
292 | // 当没有G可被执行时,M会与P解绑,然后进入休眠(idle)状态。
293 |
294 | if gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != 0 && gcMarkWorkAvailable(_p_) {
295 | _p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
296 | gp := _p_.gcBgMarkWorker.ptr()
297 | casgstatus(gp, _Gwaiting, _Grunnable)
298 | if trace.enabled {
299 | traceGoUnpark(gp, 0)
300 | }
301 | return gp, false
302 | }
303 |
304 | // wasm only:
305 | // If a callback returned and no other goroutine is awake,
306 | // then pause execution until a callback was triggered.
307 | if beforeIdle() {
308 | // At least one goroutine got woken.
309 | goto top
310 | }
311 |
312 | // Before we drop our P, make a snapshot of the allp slice,
313 | // which can change underfoot once we no longer block
314 | // safe-points. We don't need to snapshot the contents because
315 | // everything up to cap(allp) is immutable.
316 | allpSnapshot := allp
317 |
318 | // return P and block
319 | lock(&sched.lock)
320 | if sched.gcwaiting != 0 || _p_.runSafePointFn != 0 {
321 | unlock(&sched.lock)
322 | goto top
323 | }
324 | if sched.runqsize != 0 {
325 | gp := globrunqget(_p_, 0)
326 | unlock(&sched.lock)
327 | return gp, false
328 | }
329 | if releasep() != _p_ {
330 | throw("findrunnable: wrong p")
331 | }
332 | pidleput(_p_)
333 | unlock(&sched.lock)
334 |
335 | // Delicate dance: thread transitions from spinning to non-spinning state,
336 | // potentially concurrently with submission of new goroutines. We must
337 | // drop nmspinning first and then check all per-P queues again (with
338 | // #StoreLoad memory barrier in between). If we do it the other way around,
339 | // another thread can submit a goroutine after we've checked all run queues
340 | // but before we drop nmspinning; as the result nobody will unpark a thread
341 | // to run the goroutine.
342 | // If we discover new work below, we need to restore m.spinning as a signal
343 | // for resetspinning to unpark a new worker thread (because there can be more
344 | // than one starving goroutine). However, if after discovering new work
345 | // we also observe no idle Ps, it is OK to just park the current thread:
346 | // the system is fully loaded so no spinning threads are required.
347 | // Also see "Worker thread parking/unparking" comment at the top of the file.
348 | wasSpinning := _g_.m.spinning
349 | if _g_.m.spinning {
350 | _g_.m.spinning = false
351 | if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
352 | throw("findrunnable: negative nmspinning")
353 | }
354 | }
355 |
356 | // check all runqueues once again
357 | for _, _p_ := range allpSnapshot {
358 | if !runqempty(_p_) {
359 | lock(&sched.lock)
360 | _p_ = pidleget()
361 | unlock(&sched.lock)
362 | if _p_ != nil {
363 | acquirep(_p_)
364 | if wasSpinning {
365 | _g_.m.spinning = true
366 | atomic.Xadd(&sched.nmspinning, 1)
367 | }
368 | goto top
369 | }
370 | break
371 | }
372 | }
373 |
374 | // Check for idle-priority GC work again.
375 | if gcBlackenEnabled != 0 && gcMarkWorkAvailable(nil) {
376 | lock(&sched.lock)
377 | _p_ = pidleget()
378 | if _p_ != nil && _p_.gcBgMarkWorker == 0 {
379 | pidleput(_p_)
380 | _p_ = nil
381 | }
382 | unlock(&sched.lock)
383 | if _p_ != nil {
384 | acquirep(_p_)
385 | if wasSpinning {
386 | _g_.m.spinning = true
387 | atomic.Xadd(&sched.nmspinning, 1)
388 | }
389 | // Go back to idle GC check.
390 | goto stop
391 | }
392 | }
393 |
394 | // poll network
395 | if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
396 | if _g_.m.p != 0 {
397 | throw("findrunnable: netpoll with p")
398 | }
399 | if _g_.m.spinning {
400 | throw("findrunnable: netpoll with spinning")
401 | }
402 | list := netpoll(true) // block until new work is available
403 | atomic.Store64(&sched.lastpoll, uint64(nanotime()))
404 | if !list.empty() {
405 | lock(&sched.lock)
406 | _p_ = pidleget()
407 | unlock(&sched.lock)
408 | if _p_ != nil {
409 | acquirep(_p_)
410 | gp := list.pop()
411 | injectglist(&list)
412 | casgstatus(gp, _Gwaiting, _Grunnable)
413 | if trace.enabled {
414 | traceGoUnpark(gp, 0)
415 | }
416 | return gp, false
417 | }
418 | injectglist(&list)
419 | }
420 | }
421 | stopm()
422 | goto top
423 | }
424 | ```
425 | #### GPM模型调度
426 |
427 | 如果一切正常,调度器会以上述的那种方式顺畅地运行,但总是有特殊的情况存在, 下面分析 goroutine 在两种例外情况下的行为。
428 |
429 | Go runtime 会在下面的 goroutine 被阻塞的情况下运行另外一个 goroutine:
430 |
431 |
432 | 然而在通常情况下, Go runtime 会在下面的 goroutine 被阻塞的情况下运行另外一个 goroutine:
433 |
434 | * blocking syscall (for example opening a file)
435 | * network input
436 | * channel operations
437 | * primitives in the sync package
438 |
439 | 这里其实可以看做两个情况,即`用户态阻塞/唤醒` 和 `系统调用阻塞`.
440 |
441 | * 用户态阻塞/唤醒
442 |
443 | 当 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 状态.
444 |
445 | 当阻塞的 G 被另一端的 G2 唤醒时(比如 channel 的可读/写通知),G 被标记为 runnable,尝试加入 G2 所在 P 的 runnext,然后再是 P 的 Local 队列和 Global 队列。
446 |
447 | * syscall 系统调用阻塞
448 |
449 | 当 G 被阻塞在某个系统调用上时,此时 G 会阻塞在_Gsyscall状态,M 也处于 `block on syscall` 状态,此时的 M 可被抢占调度:执行该 G 的 M 会与 P 解绑,而 P 则尝试与其它 idle 的 M 绑定,继续执行其它 G。如果没有其它 idle 的 M,但 P 的 Local 队列中仍然有 G 需要执行,则创建一个新的 M.
450 |
451 | 当系统调用完成后,G 会重新尝试获取一个 idle 的 P 进入它的 Local 队列恢复执行,如果没有 idle 的 P,G 会被标记为 `runnable` 加入到 Global 队列。
452 |
453 | 系统调用能被调度的关键有两点:
454 |
455 | `runtime/syscall` 包中,将系统调用分为 `SysCall` 和 `RawSysCall`,`SysCall` 和 `RawSysCall`的区别是 `SysCall` 会在系统调用前后分别调用`entersyscall`和`exitsyscall`(位于`src/runtime/proc.go`),做一些现场保存和恢复操作,这样才能使P安全地与M解绑,并在其它M上继续执行其它G。
456 |
457 | 某些系统调用本身可以确定会长时间阻塞(比如锁),会调用 `entersyscallblock` 在发起系统调用前直接让P和M解绑。
458 |
459 | 这里的关键点是`sysmon`,它负责检查所有系统调用的执行时间,判断是否需要解绑。
460 |
461 | `sysmon`是一个由`runtime`启动的M,也叫监控线程,它无需P也可以运行,它每20us~10ms唤醒一次,主要执行:
462 |
463 | 1. 释放闲置超过5分钟的span物理内存;
464 | 2. 如果超过2分钟没有垃圾回收,强制执行;
465 | 3. 将长时间未处理的netpoll结果添加到任务队列;
466 | 4. 向长时间运行的G任务发出抢占调度;
467 | 5. 收回因syscall长时间阻塞的P;
468 |
469 | `sysmon` 它通过retake实现对syscall和长时间运行的G进行调度:
470 |
471 | ```go
472 | // src/runtime/proc.go:sysmon
473 |
474 | type sysmontick struct {
475 | schedtick uint32
476 | schedwhen int64
477 | syscalltick uint32
478 | syscallwhen int64
479 | }
480 |
481 | // forcePreemptNS is the time slice given to a G before it is
482 | // preempted.
483 | const forcePreemptNS = 10 * 1000 * 1000 // 10ms
484 |
485 | func retake(now int64) uint32 {
486 | n := 0
487 | // Prevent allp slice changes. This lock will be completely
488 | // uncontended unless we're already stopping the world.
489 | lock(&allpLock)
490 | // We can't use a range loop over allp because we may
491 | // temporarily drop the allpLock. Hence, we need to re-fetch
492 | // allp each time around the loop.
493 | for i := 0; i < len(allp); i++ {
494 | _p_ := allp[i]
495 | if _p_ == nil {
496 | // This can happen if procresize has grown
497 | // allp but not yet created new Ps.
498 | continue
499 | }
500 | pd := &_p_.sysmontick
501 | s := _p_.status
502 | sysretake := false
503 | if s == _Prunning || s == _Psyscall {
504 | // Preempt G if it's running for too long.
505 | t := int64(_p_.schedtick)
506 | if int64(pd.schedtick) != t {
507 | pd.schedtick = uint32(t)
508 | pd.schedwhen = now
509 | } else if pd.schedwhen+forcePreemptNS <= now {
510 | // 如果当前G执行时间超过10ms,则抢占(preemptone)
511 | // 执行抢占
512 |
513 | preemptone(_p_)
514 | // In case of syscall, preemptone() doesn't
515 | // work, because there is no M wired to P.
516 | sysretake = true
517 | }
518 | }
519 | if s == _Psyscall {
520 | // Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us).
521 | t := int64(_p_.syscalltick)
522 | if !sysretake && int64(pd.syscalltick) != t {
523 | pd.syscalltick = uint32(t)
524 | pd.syscallwhen = now
525 | continue
526 | }
527 | // 如果当前P Local队列没有其它G,当前有其它P处于Idle状态,并且syscall执行事件不超过10ms,则不用解绑当前P(handoffp)
528 | if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now {
529 | continue
530 | }
531 | // Drop allpLock so we can take sched.lock.
532 | unlock(&allpLock)
533 | // Need to decrement number of idle locked M's
534 | // (pretending that one more is running) before the CAS.
535 | // Otherwise the M from which we retake can exit the syscall,
536 | // increment nmidle and report deadlock.
537 |
538 | incidlelocked(-1)
539 | if atomic.Cas(&_p_.status, s, _Pidle) {
540 | if trace.enabled {
541 | traceGoSysBlock(_p_)
542 | traceProcStop(_p_)
543 | }
544 | n++
545 | _p_.syscalltick++
546 | handoffp(_p_)
547 | }
548 | incidlelocked(1)
549 | lock(&allpLock)
550 | }
551 | }
552 | unlock(&allpLock)
553 | return uint32(n)
554 | }
555 |
556 | ```
557 |
558 | #### 抢占式调度
559 |
560 | 当某个goroutine执行超过10ms,`sysmon`会向其发起抢占调度请求,由于Go调度不像OS调度那样有时间片的概念,因此实际抢占机制要弱很多: Go中的抢占实际上是为G设置抢占标记(g.stackguard0),当G调用某函数时(更确切说,在通过newstack分配函数栈时),被编译器安插的指令会检查这个标记,并且将当前G以`runtime.Goched`的方式暂停,并加入到全局队列。
561 |
562 | 源代码如下:
563 |
564 | ```go
565 | // Called from runtime·morestack when more stack is needed.
566 | // Allocate larger stack and relocate to new stack.
567 | // Stack growth is multiplicative, for constant amortized cost.
568 | //
569 | // g->atomicstatus will be Grunning or Gscanrunning upon entry.
570 | // If the GC is trying to stop this g then it will set preemptscan to true.
571 | //
572 | // ctxt is the value of the context register on morestack. newstack
573 | // will write it to g.sched.ctxt.
574 |
575 | func newstack() {
576 | thisg := getg()
577 | // TODO: double check all gp. shouldn't be getg().
578 | if thisg.m.morebuf.g.ptr().stackguard0 == stackFork {
579 | throw("stack growth after fork")
580 | }
581 | if thisg.m.morebuf.g.ptr() != thisg.m.curg {
582 | print("runtime: newstack called from g=", hex(thisg.m.morebuf.g), "\n"+"\tm=", thisg.m, " m->curg=", thisg.m.curg, " m->g0=", thisg.m.g0, " m->gsignal=", thisg.m.gsignal, "\n")
583 | morebuf := thisg.m.morebuf
584 | traceback(morebuf.pc, morebuf.sp, morebuf.lr, morebuf.g.ptr())
585 | throw("runtime: wrong goroutine in newstack")
586 | }
587 |
588 | gp := thisg.m.curg
589 |
590 | if thisg.m.curg.throwsplit {
591 | // Update syscallsp, syscallpc in case traceback uses them.
592 | morebuf := thisg.m.morebuf
593 | gp.syscallsp = morebuf.sp
594 | gp.syscallpc = morebuf.pc
595 | pcname, pcoff := "(unknown)", uintptr(0)
596 | f := findfunc(gp.sched.pc)
597 | if f.valid() {
598 | pcname = funcname(f)
599 | pcoff = gp.sched.pc - f.entry
600 | }
601 | print("runtime: newstack at ", pcname, "+", hex(pcoff),
602 | " sp=", hex(gp.sched.sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
603 | "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
604 | "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")
605 |
606 | thisg.m.traceback = 2 // Include runtime frames
607 | traceback(morebuf.pc, morebuf.sp, morebuf.lr, gp)
608 | throw("runtime: stack split at bad time")
609 | }
610 |
611 | morebuf := thisg.m.morebuf
612 | thisg.m.morebuf.pc = 0
613 | thisg.m.morebuf.lr = 0
614 | thisg.m.morebuf.sp = 0
615 | thisg.m.morebuf.g = 0
616 |
617 | // NOTE: stackguard0 may change underfoot, if another thread
618 | // is about to try to preempt gp. Read it just once and use that same
619 | // value now and below.
620 | preempt := atomic.Loaduintptr(&gp.stackguard0) == stackPreempt
621 |
622 | // Be conservative about where we preempt.
623 | // We are interested in preempting user Go code, not runtime code.
624 | // If we're holding locks, mallocing, or preemption is disabled, don't
625 | // preempt.
626 | // This check is very early in newstack so that even the status change
627 | // from Grunning to Gwaiting and back doesn't happen in this case.
628 | // That status change by itself can be viewed as a small preemption,
629 | // because the GC might change Gwaiting to Gscanwaiting, and then
630 | // this goroutine has to wait for the GC to finish before continuing.
631 | // If the GC is in some way dependent on this goroutine (for example,
632 | // it needs a lock held by the goroutine), that small preemption turns
633 | // into a real deadlock.
634 | if preempt {
635 | if thisg.m.locks != 0 || thisg.m.mallocing != 0 || thisg.m.preemptoff != "" || thisg.m.p.ptr().status != _Prunning {
636 | // Let the goroutine keep running for now.
637 | // gp->preempt is set, so it will be preempted next time.
638 | gp.stackguard0 = gp.stack.lo + _StackGuard
639 | gogo(&gp.sched) // never return
640 | }
641 | }
642 |
643 | if gp.stack.lo == 0 {
644 | throw("missing stack in newstack")
645 | }
646 | sp := gp.sched.sp
647 | if sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM {
648 | // The call to morestack cost a word.
649 | sp -= sys.PtrSize
650 | }
651 | if stackDebug >= 1 || sp < gp.stack.lo {
652 | print("runtime: newstack sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
653 | "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
654 | "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")
655 | }
656 | if sp < gp.stack.lo {
657 | print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->status=", hex(readgstatus(gp)), "\n ")
658 | print("runtime: split stack overflow: ", hex(sp), " < ", hex(gp.stack.lo), "\n")
659 | throw("runtime: split stack overflow")
660 | }
661 |
662 | if preempt {
663 | if gp == thisg.m.g0 {
664 | throw("runtime: preempt g0")
665 | }
666 | if thisg.m.p == 0 && thisg.m.locks == 0 {
667 | throw("runtime: g is running but p is not")
668 | }
669 | // Synchronize with scang.
670 | casgstatus(gp, _Grunning, _Gwaiting)
671 | if gp.preemptscan {
672 | for !castogscanstatus(gp, _Gwaiting, _Gscanwaiting) {
673 | // Likely to be racing with the GC as
674 | // it sees a _Gwaiting and does the
675 | // stack scan. If so, gcworkdone will
676 | // be set and gcphasework will simply
677 | // return.
678 | }
679 | if !gp.gcscandone {
680 | // gcw is safe because we're on the
681 | // system stack.
682 | gcw := &gp.m.p.ptr().gcw
683 | scanstack(gp, gcw)
684 | gp.gcscandone = true
685 | }
686 | gp.preemptscan = false
687 | gp.preempt = false
688 | casfrom_Gscanstatus(gp, _Gscanwaiting, _Gwaiting)
689 | // This clears gcscanvalid.
690 | casgstatus(gp, _Gwaiting, _Grunning)
691 | gp.stackguard0 = gp.stack.lo + _StackGuard
692 | gogo(&gp.sched) // never return
693 | }
694 |
695 | // Act like goroutine called runtime.Gosched.
696 | casgstatus(gp, _Gwaiting, _Grunning)
697 | gopreempt_m(gp) // never return
698 | }
699 |
700 | // Allocate a bigger segment and move the stack.
701 | // 扩容至现在的2倍
702 | oldsize := gp.stack.hi - gp.stack.lo
703 | newsize := oldsize * 2
704 | if newsize > maxstacksize {
705 | print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")
706 | throw("stack overflow")
707 | }
708 |
709 | // The goroutine must be executing in order to call newstack,
710 | // so it must be Grunning (or Gscanrunning).
711 | casgstatus(gp, _Grunning, _Gcopystack)
712 |
713 | // The concurrent GC will not scan the stack while we are doing the copy since
714 | // the gp is in a Gcopystack status.
715 | // 拷贝栈数据后切换到新栈
716 | copystack(gp, newsize, true)
717 | if stackDebug >= 1 {
718 | print("stack grow done\n")
719 | }
720 |
721 | // 恢复执行
722 | casgstatus(gp, _Gcopystack, _Grunning)
723 | gogo(&gp.sched)
724 | }
725 |
726 | // Copies gp's stack to a new stack of a different size.
727 | // Caller must have changed gp status to Gcopystack.
728 | //
729 | // If sync is true, this is a self-triggered stack growth and, in
730 | // particular, no other G may be writing to gp's stack (e.g., via a
731 | // channel operation). If sync is false, copystack protects against
732 | // concurrent channel operations.
733 | func copystack(gp *g, newsize uintptr, sync bool) {
734 | if gp.syscallsp != 0 {
735 | throw("stack growth not allowed in system call")
736 | }
737 | old := gp.stack
738 | if old.lo == 0 {
739 | throw("nil stackbase")
740 | }
741 | used := old.hi - gp.sched.sp
742 |
743 | // allocate new stack
744 | // 从缓存或堆分配新栈
745 | new := stackalloc(uint32(newsize))
746 | if stackPoisonCopy != 0 {
747 | fillstack(new, 0xfd)
748 | }
749 | if stackDebug >= 1 {
750 | print("copystack gp=", gp, " [", hex(old.lo), " ", hex(old.hi-used), " ", hex(old.hi), "]", " -> [", hex(new.lo), " ", hex(new.hi-used), " ", hex(new.hi), "]/", newsize, "\n")
751 | }
752 |
753 | // Compute adjustment.
754 | var adjinfo adjustinfo
755 | adjinfo.old = old
756 | adjinfo.delta = new.hi - old.hi
757 |
758 | // Adjust sudogs, synchronizing with channel ops if necessary.
759 | ncopy := used
760 | if sync {
761 | adjustsudogs(gp, &adjinfo)
762 | } else {
763 | // sudogs can point in to the stack. During concurrent
764 | // shrinking, these areas may be written to. Find the
765 | // highest such pointer so we can handle everything
766 | // there and below carefully. (This shouldn't be far
767 | // from the bottom of the stack, so there's little
768 | // cost in handling everything below it carefully.)
769 | adjinfo.sghi = findsghi(gp, old)
770 |
771 | // Synchronize with channel ops and copy the part of
772 | // the stack they may interact with.
773 | ncopy -= syncadjustsudogs(gp, used, &adjinfo)
774 | }
775 |
776 | // Copy the stack (or the rest of it) to the new location
777 | // 拷贝栈到新的位置
778 | memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy)
779 |
780 | // Adjust remaining structures that have pointers into stacks.
781 | // We have to do most of these before we traceback the new
782 | // stack because gentraceback uses them.
783 | adjustctxt(gp, &adjinfo)
784 | adjustdefers(gp, &adjinfo)
785 | adjustpanics(gp, &adjinfo)
786 | if adjinfo.sghi != 0 {
787 | adjinfo.sghi += adjinfo.delta
788 | }
789 |
790 | // Swap out old stack for new one
791 | // 切换到新栈
792 | gp.stack = new
793 | gp.stackguard0 = new.lo + _StackGuard // NOTE: might clobber a preempt request
794 | gp.sched.sp = new.hi - used
795 | gp.stktopsp += adjinfo.delta
796 |
797 | // Adjust pointers in the new stack.
798 | gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&adjinfo)), 0)
799 |
800 | // free old stack
801 | // 释放旧栈
802 | if stackPoisonCopy != 0 {
803 | fillstack(old, 0xfc)
804 | }
805 | stackfree(old)
806 | }
807 |
808 | ```
809 |
810 | go在1.3之前栈扩容采用的是分段栈(Segemented Stack),在栈空间不够的时候新申请一个栈空间用于被调用函数的执行, 执行后销毁新申请的栈空间并回到老的栈空间继续执行,当函数出现频繁调用(递归)时可能会引发hot split。
811 |
812 | 为了避免hot split, 1.3之后采用的是连续栈(Contiguous Stack),栈空间不足的时候申请一个2倍于当前大小的新栈,并把所有数据拷贝到新栈, 接下来的所有调用执行都发生在新栈上。
813 |
814 |
815 | 看完了扩容,我们来看看缩容。一些`long running`的goroutine可能由于某次函数调用中引发了栈的扩容, 被调用函数返回后很大部分空间都未被利用,为了解决这样的问题,需要能够对栈进行收缩,以节约内存提高利用率。
816 |
817 | 栈收缩不是在函数调用时发生的,是由垃圾回收器在垃圾回收时主动触发的。基本过程是计算当前使用的空间,小于栈空间的1/4的话, 执行栈的收缩,将栈收缩为现在的1/2,否则直接返回。
818 |
819 |
820 | ```go
821 | // runtime/stack.go
822 | // Maybe shrink the stack being used by gp.
823 | // Called at garbage collection time.
824 | // gp must be stopped, but the world need not be.
825 | func shrinkstack(gp *g) {
826 | gstatus := readgstatus(gp)
827 | if gp.stack.lo == 0 {
828 | throw("missing stack in shrinkstack")
829 | }
830 | if gstatus&_Gscan == 0 {
831 | throw("bad status in shrinkstack")
832 | }
833 |
834 | if debug.gcshrinkstackoff > 0 {
835 | return
836 | }
837 | f := findfunc(gp.startpc)
838 | if f.valid() && f.funcID == funcID_gcBgMarkWorker {
839 | // We're not allowed to shrink the gcBgMarkWorker
840 | // stack (see gcBgMarkWorker for explanation).
841 | return
842 | }
843 |
844 | // 收缩目标是一半大小
845 | oldsize := gp.stack.hi - gp.stack.lo
846 | newsize := oldsize / 2
847 | // Don't shrink the allocation below the minimum-sized stack
848 | // allocation.
849 | if newsize < _FixedStack {
850 | return
851 | }
852 | // Compute how much of the stack is currently in use and only
853 | // shrink the stack if gp is using less than a quarter of its
854 | // current stack. The currently used stack includes everything
855 | // down to the SP plus the stack guard space that ensures
856 | // there's room for nosplit functions.
857 | // 如果使用空间超过1/4, 则不收缩
858 | avail := gp.stack.hi - gp.stack.lo
859 | if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 {
860 | return
861 | }
862 |
863 | // We can't copy the stack if we're in a syscall.
864 | // The syscall might have pointers into the stack.
865 | if gp.syscallsp != 0 {
866 | return
867 | }
868 | if sys.GoosWindows != 0 && gp.m != nil && gp.m.libcallsp != 0 {
869 | return
870 | }
871 |
872 | if stackDebug > 0 {
873 | print("shrinking stack ", oldsize, "->", newsize, "\n")
874 | }
875 |
876 | // 用较小的栈替换当前的栈
877 | copystack(gp, newsize, false)
878 | }
879 |
880 | ```
881 |
882 | 这里只是对Go的调度器进行一个分析, 当然,Go 的调度中更复杂的抢占式调度、阻塞调度的更多细节,大家可以自行去找相关资料深入理解,这里只讲到 Go 调度器的基本调度过程,所以想了解更多细节的同学可以去看看 Go 调度器 G-P-M 模型的设计者 Dmitry Vyukov 写的该模型的设计文档[《Go Preemptive Scheduler Design》](https://docs.google.com/document/d/1ETuA2IOmnaQ4j81AtTGT40Y4_Jr6_IDASEKg0t0dBR8/edit#!) 以及直接去看源码,G-P-M 模型的定义放在 `src/runtime/runtime2.go` 里面,而调度过程则放在了 `src/runtime/proc.go` 里。
883 |
884 | 在Go的最新1.14源码中优化了调度器,后续我们继续分析.
885 |
886 | #### 资料参考
887 |
888 | * [Go调度模型](https://wudaijun.com/2018/01/go-scheduler/)
889 | * [The Go scheduler](https://morsmachine.dk/go-scheduler)
890 | * [work-stealing](https://en.wikipedia.org/wiki/Work_stealing)
891 |
892 |
--------------------------------------------------------------------------------
/src/chapter11/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang的逃逸分析
2 |
3 | 所谓逃逸分析(Escape analysis)是指由编译器决定内存分配的位置,不需要程序员指定。函数中申请一个新的对象如果分配在栈中,则函数执行结束可自动将内存回收;如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理.
4 |
5 | 每当函数中申请新的对象,编译器就会跟据该对象是否被函数外部引用来决定是否逃逸:
6 |
7 | 1. 如果函数外部没有引用,则优先放到栈中.
8 |
9 | 2. 如果函数外部存在引用,则必定放到堆中.
10 |
11 | 注意,对于函数外部没有引用的对象,也有可能放到堆中,比如内存过大超过栈的存储能力。
12 |
13 | 逃逸分析通常有四种情况:
14 |
15 | * 指针逃逸.
16 |
17 | * 栈空间不足逃逸.
18 |
19 | * 动态类型逃逸.
20 |
21 | * 闭包引用对象逃逸.
22 |
23 | #### 逃逸总结
24 |
25 | * 栈上分配内存比在堆中分配内存有更高的效率.
26 |
27 | * 栈上分配的内存不需要GC处理.
28 |
29 | * 堆上分配的内存使用完毕会交给GC处理.
30 |
31 | * 逃逸分析目的是决定内分配地址是栈还是堆.
32 |
33 | * 逃逸分析在编译阶段完成.
34 |
--------------------------------------------------------------------------------
/src/chapter12/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Redis为什么快
2 |
3 | Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器的内存的大小和网络的带宽,而且单线程的性能已经非常高了,就没有必要使用多线程了,所以 Redis 是单进程单线程的。 提示:如果我们运行的服务器是多核服务器,为了充分利用多核优势我们可以在单台服务器起多个 Redis 服务,或者架设 主从复制、哨兵模式、集群模式等多机方案。
4 |
5 | Redis 服务运行时只是处理客户端请求是单进程单线程的,但是服务运行时会有其他进程或线程处理其他的事,比如RDB的文件的生成就会在子进程中进行等。
6 |
7 | Redis为什么这么快?
8 |
9 | 1. 完全基于内存,绝大部分请求是基于内存的操作,而 Redis 的数据结构是类似于HashMap,而 HashMap 的操作时间复杂度是O(1)
10 | 2. Redis 数据结构设计简单,方便操作
11 | 3. 使用单线程,避免了进程或线程的上下文切换相关的消耗,不用考虑锁相关问题消耗。
12 | 4. 使用多路I/O复用模型,非阻塞IO
13 | 5. 使用底层模型不同,底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
14 |
15 | #### 什么是多路I/O复用
16 |
17 | 多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
18 |
19 | 这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
20 |
21 | #### Redis的数据过期策略
22 |
23 | Redis 中数据过期策略采用定期删除和惰性删除策略:
24 |
25 | * 定期删除策略:Redis 启用一个定时器定时监视所有的 key,判断key是否过期,过期的话就删除。这种策略可以保证过期的 key 最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所有的数据,非常消耗 CPU 资源,并且当 key 已过期,但是定时器还处于未唤起状态,这段时间内 key 仍然可以用。
26 |
27 | * 惰性删除策略:在获取 key 时,先判断 key 是否过期,如果过期则删除。这种方式存在一个缺点:如果这个 key 一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间。
28 |
29 | 这两种策略天然的互补,结合起来之后,定时删除策略就发生了一些改变,不在是每次扫描全部的 key 了,而是随机抽取一部分 key 进行检查,这样就降低了对 CPU 资源的损耗,惰性删除策略互补了为检查到的key,基本上满足了所有要求。但是有时候就是那么的巧,既没有被定时器抽取到,又没有被使用,这些数据又如何从内存中消失?没关系,还有内存淘汰机制,当内存不够用时,内存淘汰机制就会上场。
30 |
31 | 淘汰策略分为:
32 |
33 | 1. 当内存不足以容纳新写入数据时,新写入操作会报错(Redis 默认策略).
34 |
35 | 2. 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(LRU推荐使用)
36 |
37 | 3. 当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。
38 |
39 | 4. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
40 |
41 | 5. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。
42 |
43 | 6. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。
44 |
45 | #### 注意事项
46 |
47 | Redis是基于I/O多路复用的单线程模式,所以 Redis 在处理比较耗时的命令的时候性能会受影响。可以使用 Redis 多机部署方案来应对这样的问题.
48 |
--------------------------------------------------------------------------------
/src/chapter13/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang 性能优化
2 |
3 | Golang的 GC全称 `GarbageCollection`,即垃圾回收,是一种自动内存管理的机制。
4 |
5 | 当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收并供其他代码进行内存申请时候复用,或者将其归还给操作系统,这种针对内存级别资源的自动回收过程,即为垃圾回收。而负责垃圾回收的程序组件,即为垃圾回收器。
6 |
7 | 垃圾回收其实是一个完美的 “Simplicity is Complicated” 的例子。一方面,开发者受益于GC,不用关心、也不需要对内存进行手动的申请和释放操作,GC在程序运行时自动释放残留的内存。另一方面,GC 对开发者几乎不可见,仅在程序需要进行特殊优化时,通过提供可调控的 API,对 GC 的运行时机、运行开销进行把控的时候才得以现身。
8 |
9 | 通常,垃圾回收器的执行过程会被划分为两个半独立的组件:
10 |
11 | 赋值器(Mutator):这一名称本质上是在指代用户态的代码。因为对垃圾回收器而言,用户态的代码仅仅只是在修改对象之间的引用关系,也就是在对象图(对象之间引用关系的一个有向图)上进行操作。
12 |
13 | 回收器(Collector):负责执行垃圾回收的代码。
14 |
--------------------------------------------------------------------------------
/src/chapter14/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang的汇编过程
2 |
3 | 在程序编译的时候,汇编的目的是把汇编代码转化为机器指令,因为几乎每一条汇编指令都对应着一条机器指令,所以汇编的过程相对而言非常的简单。
4 |
5 | 汇编操作所生成的文件叫做目标文件(Object File),目标文件的结构与可执行文件是一致的,它们之间只存在着一些细微的差异。目标文件是无法被执行的,它还需要经过链接这一步操作,目标文件被链接之后才可以产生可执行文件。
6 |
7 | Golang原生支持用户级协程,交叉编译,跨平台部署运行, 但是go在编译成机器语言交付给CPU执行的过程中,汇编也只是一个中间状态,汇编指令相对于以上的高级语言而言则显得十分拗口。在大部分强类型的语言中,基本上代码在执行前会经历几个阶段:
8 |
9 | ```markdown
10 | 语法分析--->词法分析--->目标码生成
11 | ```
12 | Go的汇编是怎么样的?
13 |
14 | Go汇编器所用的指令,一部分与目标机器的指令一一对应,而另外一部分则不是。这是因为编译器套件不需要汇编器直接参与常规的编译过程。相反,编译器使用了一种半抽象的指令集,并且部分指令是在代码生成后才被选择的。
15 |
16 | 汇编器基于这种半抽象的形式工作,所以虽然你看到的是一条MOV指令,但是工具链针对这条指令实际生成可能完全不是一个移动指令,也许会是清除或者加载。也有可能精确的对应目标平台上同名的指令。
17 | 由于这种汇编并不对应某种真实的硬件架构,Go编译器会输出一种抽象可移植的汇编代码。
18 |
19 | 接着我们看一个应用示例:
20 |
21 | ```go
22 |
23 | package main
24 |
25 | //go:noinline
26 | func add(a, b int) (int, bool) {
27 | return a + b, true
28 | }
29 |
30 | func main() {
31 | add(5, 10)
32 | }
33 | ```
34 |
35 | 其中 `//go:noinline` 为编译器指令,不是注释,这里应该意为禁止内联,这部分在scan后形成ast树时也会scan到这个记录,在汇编的过程中会读取这个标记,从而控制一些汇编行为。
36 |
37 | 然后,我们将这段代码编译到汇编:
38 |
39 | ```bash
40 | > GOOS=linux GOARCH=amd64 go tool compile -S main.go
41 |
42 | "".add STEXT nosplit size=20 args=0x10 locals=0x0 funcid=0x0
43 | 0x0000 00000 (main.go:4) TEXT "".add(SB), NOSPLIT|ABIInternal, $0-16
44 | 0x0000 00000 (main.go:4) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
45 | 0x0000 00000 (main.go:4) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
46 | 0x0000 00000 (main.go:5) MOVL "".b+12(SP), AX
47 | 0x0004 00004 (main.go:5) MOVL "".a+8(SP), CX
48 | 0x0008 00008 (main.go:5) ADDL CX, AX
49 | 0x000a 00010 (main.go:5) MOVL AX, "".~r2+16(SP)
50 | 0x000e 00014 (main.go:5) MOVB $1, "".~r3+20(SP)
51 | 0x0013 00019 (main.go:5) RET
52 |
53 | "".main STEXT size=66 args=0x0 locals=0x18 funcid=0x0
54 | 0x0000 00000 (main.go:8) TEXT "".main(SB), ABIInternal, $24-0
55 | 0x0000 00000 (main.go:8) MOVQ (TLS), CX
56 | 0x0009 00009 (main.go:8) CMPQ SP, 16(CX)
57 | 0x000d 00013 (main.go:8) PCDATA $0, $-2
58 | 0x000d 00013 (main.go:8) JLS 58
59 | 0x000f 00015 (main.go:8) PCDATA $0, $-1
60 | 0x000f 00015 (main.go:8) SUBQ $24, SP
61 | 0x0013 00019 (main.go:8) MOVQ BP, 16(SP)
62 | 0x0018 00024 (main.go:8) LEAQ 16(SP), BP
63 | 0x001d 00029 (main.go:8) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
64 | 0x001d 00029 (main.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
65 | 0x001d 00029 (main.go:9) MOVQ $42949672965, AX
66 | 0x0027 00039 (main.go:9) MOVQ AX, (SP)
67 | 0x002b 00043 (main.go:9) PCDATA $1, $0
68 | 0x002b 00043 (main.go:9) CALL "".add(SB)
69 | 0x0030 00048 (main.go:10) MOVQ 16(SP), BP
70 | 0x0035 00053 (main.go:10) ADDQ $24, SP
71 | 0x0039 00057 (main.go:10) RET
72 | 0x003a 00058 (main.go:10) NOP
73 | 0x003a 00058 (main.go:8) PCDATA $1, $-1
74 | 0x003a 00058 (main.go:8) PCDATA $0, $-2
75 | 0x003a 00058 (main.go:8) CALL runtime.morestack_noctxt(SB)
76 | 0x003f 00063 (main.go:8) PCDATA $0, $-1
77 | 0x003f 00063 (main.go:8) NOP
78 | 0x0040 00064 (main.go:8) JMP 0
79 |
80 | ```
81 | 这里的add:
82 |
83 | ```go
84 | 0x0000 00000 (main.go:4) TEXT "".add(SB), NOSPLIT|ABIInternal, $0-16
85 |
86 | * 0x0000: 当前指令相对于当前函数的偏移量。
87 |
88 | * TEXT "".add: TEXT 指令声明了 "".add 是 .text 段(程序代码在运行期会放在内存的 .text 段中)的一部分,并表明跟在这个声明后的是函数的函数体。 在链接期,"" 这个空字符会被替换为当前的包名: 也就是说,"".add 在链接到二进制文件后会变成 `main.add`。
89 |
90 | * (SB): SB 是一个虚拟寄存器,保存了静态基地址(static-base) 指针,即我们程序地址空间的开始地址。 "".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲,它有一个直接的绝对地址: 是一个全局的函数符号。
91 |
92 | * NOSPLIT: 向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。 在我们 add 函数的这种情况下,编译器自己帮我们插入了这个标记: 它足够聪明地意识到,由于 add 没有任何局部变量且没有它自己的栈帧,所以一定不会超出当前的栈,因此每次调用函数时在这里执行栈检查就是完全浪费 CPU 循环了。
93 |
94 | * $0-16: $0 代表即将分配的栈帧大小;而 16 指定了调用方传入的参数大小。
95 |
96 | * 通常帧大小后一般都跟随着一个参数大小,用-分隔。(这不是一个减法操作,只是一种特殊的语法)
97 | 帧大小 $24-8 意味着这个函数有24个字节的帧以及8个字节的参数,位于调用者的帧上。如果NOSPLIT没有在TEXT中指定,则必须提供参数大小。
98 |
99 | 对于Go原型的汇编函数,go vet会检查参数大小是否正确。Go是一个具备gc机制的语言,因此在C,C++里担心的那些问题在Go这都不是问题!
100 |
101 | * Go 的调用规约要求每一个参数都通过栈来传递,这部分空间由 caller 在其栈帧(stack frame)上提供。调用其它函数之前,caller 就需要按照参数和返回变量的大小来对应地增长(返回后收缩)栈。
102 | ```
103 |
104 | ```go
105 | 0x0000 00000 (main.go:4) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
106 | 0x0000 00000 (main.go:4) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
107 | ```
108 |
109 | FUNCDATA以及PCDATA指令包含有被gc回收所使用的信息,这些指令是被编译器加入的。
110 |
111 | ```go
112 | 0x0000 00000 (main.go:5) MOVL "".b+12(SP), AX
113 | 0x0004 00004 (main.go:5) MOVL "".a+8(SP), CX
114 | ```
115 |
116 | Go的调用要求每一个参数都通过栈来传递,这部分空间由caller在其栈帧(stack frame)上提供。调用其它过程之前,caller就需要按照参数和返回变量的大小来对应地增长(返回后收缩)栈。Go编译器不会生成任何 `PUSH或POP` 族的指令: 栈的增长和收缩是通过在栈指针寄存器 SP 上分别执行减法和加法指令来实现的。
117 |
118 | ```go
119 | SP伪寄存器是虚拟的栈指针,用于引用帧局部变量以及为函数调用准备的参数,它指向局部栈帧的顶部。
120 | "".b+12(SP) 和 "".a+8(SP) 分别指向栈的低12字节和低8字节位置(栈是向低位地址方向增长的!)。
121 | ```
122 |
123 | ```go
124 | 0x0008 00008 (main.go:5) ADDL CX, AX
125 | 0x000a 00010 (main.go:5) MOVL AX, "".~r2+16(SP)
126 | 0x000e 00014 (main.go:5) MOVB $1, "".~r3+20(SP)
127 | ```
128 | 其中,第一个变量 a 的地址并不是 0(SP),而是在 8(SP),这是因为调用方通过使用 CALL 伪指令,把其返回地址保存在了 0(SP) 位置。参数是反序传入的,也就是说,第一个参数和栈顶距离最近。
129 |
130 | ADDL 进行实际的加法操作,L 这里代表 Long,4 字节的值(int32),其将保存在 AX 和 CX 寄存器中的值进行相加,然后再保存进 AX 寄存器中。 这个结果之后被移动到 `"".~r2+16(SP)` 地址处,这是之前调用方专门为返回值预留的栈空间。这一次 `"".~r2` 同样没什么语义上的含义。
131 |
132 | 为了弄清楚Go 是如何处理多返回值,我们可以同时返回了一个 bool 常量 true。 返回这个 bool 值的方法和之前返回数值的方法是一样的,只是相对于 SP 寄存器的偏移量发生了变化。
133 |
134 | 最后:
135 |
136 | ```go
137 | 0x0013 00019 (main.go:5) RET
138 | ```
139 |
140 | 最后的 RET 伪指令告诉 Go汇编器插入一些指令,这些指令是对应的目标平台中的调用规约所要求的,从子过程中返回时所需要的指令。 一般情况下这样的指令会使在 0(SP) 寄存器中保存的函数返回地址被 pop 出栈,并跳回到该地址。
141 |
142 | 接着我们看下main:
143 |
144 | ```go
145 | "".main STEXT size=66 args=0x0 locals=0x18 funcid=0x0
146 | 0x0000 00000 (main.go:8) TEXT "".main(SB), ABIInternal, $24-0
147 | 0x0000 00000 (main.go:8) MOVQ (TLS), CX
148 | 0x0009 00009 (main.go:8) CMPQ SP, 16(CX)
149 | 0x000d 00013 (main.go:8) PCDATA $0, $-2
150 | 0x000d 00013 (main.go:8) JLS 58
151 | 0x000f 00015 (main.go:8) PCDATA $0, $-1
152 | 0x000f 00015 (main.go:8) SUBQ $24, SP
153 | 0x0013 00019 (main.go:8) MOVQ BP, 16(SP)
154 | 0x0018 00024 (main.go:8) LEAQ 16(SP), BP
155 | 0x001d 00029 (main.go:8) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
156 | 0x001d 00029 (main.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
157 | 0x001d 00029 (main.go:9) MOVQ $42949672965, AX
158 | 0x0027 00039 (main.go:9) MOVQ AX, (SP)
159 | 0x002b 00043 (main.go:9) PCDATA $1, $0
160 | 0x002b 00043 (main.go:9) CALL "".add(SB)
161 | 0x0030 00048 (main.go:10) MOVQ 16(SP), BP
162 | 0x0035 00053 (main.go:10) ADDQ $24, SP
163 | 0x0039 00057 (main.go:10) RET
164 | 0x003a 00058 (main.go:10) NOP
165 | 0x003a 00058 (main.go:8) PCDATA $1, $-1
166 | 0x003a 00058 (main.go:8) PCDATA $0, $-2
167 | 0x003a 00058 (main.go:8) CALL runtime.morestack_noctxt(SB)
168 | 0x003f 00063 (main.go:8) PCDATA $0, $-1
169 | 0x003f 00063 (main.go:8) NOP
170 | 0x0040 00064 (main.go:8) JMP 0
171 | ```
172 |
173 |
174 | ```markdown
175 | "".main (被链接之后名字会变成 main.main) 是一个全局的函数符号,存储在 .text 段中,该函数的地址是相对于整个地址空间起始位置的一个固定的偏移量。
176 | 它分配了 24 字节的栈帧,且不接收参数,不返回值。
177 | * main 作为调用者,通过对虚拟栈指针(stack-pointer)寄存器做减法,将其栈帧大小增加了24个字节(回忆一下栈是向低地址方向增长,所以这里的 SUBQ 指令是将栈帧的大小调整得更大了)。 这 24个字节中:
178 |
179 | * 8 个字节(16(SP)-24(SP)) 用来存储当前帧指针 BP (这是一个实际存在的寄存器)的值,以支持栈的展开和方便调试
180 | * 1+3 个字节(12(SP)-16(SP)) 是预留出的给第二个返回值 (bool) 的空间,除了类型本身的 1 个字节,在 amd64 平台上还额外需要 3 个字节来做对齐
181 | * 4 个字节(8(SP)-12(SP)) 预留给第一个返回值 (int32)
182 | * 4 个字节(4(SP)-8(SP)) 是预留给传给被调用函数的参数 b (int32)
183 | * 4 个字节(0(SP)-4(SP)) 预留给传入参数 a (int32)
184 | ```
185 |
186 | 最后,跟着栈的增长,`LEAQ` 指令计算出帧指针的新地址,并将其存储到 BP 寄存器中。
187 |
188 | ```go
189 | 0x001d 00029 (main.go:9) MOVQ $42949672965, AX
190 | 0x0027 00039 (main.go:9) MOVQ AX, (SP)
191 | ```
192 |
193 | 调用方将被调用方需要的参数作为一个`Quad word`(8 字节值,对应$42949672965)推到了刚刚增长的栈的栈顶。
194 |
195 | 尽管指令里出现的 42949672965 这个值看起来像是随机的垃圾值,实际上这个值对应的就是 10 和 32 这两个 4 字节值,它们两被连接成了一个 8 字节值。
196 |
197 | ```bash
198 | > echo 'obase=2;42949672965' | bc
199 | 101000000000000000000000000000000101
200 | ```
201 |
202 | 我们使用相对于 `static-base` 指针的偏移量,来对 add 函数进行 CALL 调用: 这种调用实际上相当于直接跳到一个指定的地址。
203 |
204 | 注意 CALL 指令还会将函数的返回地址(8 字节值)也推到栈顶;所以每次我们在 add 函数中引用 SP 寄存器的时候还需要额外偏移 8 个字节! 例如,`"".a` 现在不是 0(SP) 了,而是在 8(SP) 位置。
205 |
206 | ```go
207 | 0x0030 00048 (main.go:10) MOVQ 16(SP), BP
208 | 0x0035 00053 (main.go:10) ADDQ $24, SP
209 | 0x0039 00057 (main.go:10) RET
210 | ```
211 |
212 | 这里的3个指令对应:
213 |
214 | * 将帧指针(frame-pointer)下降一个栈帧(stack-frame)的大小(就是“向下”一级).
215 | * 将栈收缩 24 个字节,回收之前分配的栈空间.
216 | * 请求Go汇编器插入子过程返回相关的指令.
217 |
--------------------------------------------------------------------------------
/src/chapter15/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang 中的defer性能提升
2 |
3 | 在Golang 1.14中新加入了开放编码(Open-coded)`defer`类型,编译器在ssa过程中会把被延迟的方法直接插入到函数的尾部,这样就避免了运行时的deferproc及`deferprocStack`操作。
4 |
5 | 避免了在没有运行时判断下的`deferreturn`调用。如有运行时判断的逻辑,则 `deferreturn` 也进一步优化,开放编码下的 `deferreturn` 不会进行`jmpdefer`的尾递归调用,而直接在一个循环里遍历执行。
6 |
7 | 在1.14中defer的实现原理,共有三种defer模式类型,编译后一个函数里只会一种defer模式。
8 |
9 | #### 堆上分配
10 |
11 | 在 Golang 1.13 之前的版本中,所有 defer 都是在堆上分配 (deferProc),该机制在编译时会进行两个步骤:
12 |
13 | 1. 在 defer 语句的位置插入 `runtime.deferproc`,当被执行时,延迟调用会被保存为一个 `_defer` 记录,并将被延迟调用的入口地址及其参数复制保存,存入 Goroutine 的调用链表中。
14 |
15 | 2. 在函数返回之前的位置插入 `runtime.deferreturn`,当被执行时,会将延迟调用从 Goroutine 链表中取出并执行,多个延迟调用则以 `jmpdefer` 尾递归调用方式连续执行。
16 |
17 | 这种机制的主要性能问题存在于每个 defer 语句产生记录时的内存分配,以及记录参数和完成调用时参数移动的系统调用开销。
18 |
19 | #### 栈上分配
20 |
21 | 在Golang 1.13 版本中新加入 `deferprocStack` 实现了在栈上分配的形式来取代 `deferproc`,相比后者,栈上分配在函数返回后 `_defer` 便得到释放,省去了内存分配时产生的性能开销,只需适当维护 `_defer` 的链表即可。
22 |
23 | 编译器可以去选择使用`deferproc` 还是 `deferprocStack`,通常情况下都会使用`deferprocStack`,性能会提升约 30%。不过在 defer 语句出现在了循环语句里,或者无法执行更高阶的编译器优化时,亦或者同一个函数中使用了过多的 defer 时,依然会使用 `deferproc`。
24 |
25 | 栈上分配 (deferprocStack),基本跟堆上差不多,只是分配方式改为在栈上分配,压入的函数调用栈存有`_defer`记录,另外编译器在ssa过程中会预留defer空间。
26 |
27 | SSA 代表 `static single-assignment`,是一种IR(中间表示代码),要保证每个变量只被赋值一次。这个能帮助简化编译器的优化算法。简单来说,使用ssa可以使二进制文件大小减少了30%,性能提升5%-35%等.
28 |
29 | ```go
30 | // buildssa builds an SSA function for fn.
31 | // worker indicates which of the backend workers is doing the processing.
32 | func buildssa(fn *Node, worker int) *ssa.Func {
33 | name := fn.funcname()
34 | printssa := name == ssaDump
35 | var astBuf *bytes.Buffer
36 | if printssa {
37 | astBuf = &bytes.Buffer{}
38 | fdumplist(astBuf, "buildssa-enter", fn.Func.Enter)
39 | fdumplist(astBuf, "buildssa-body", fn.Nbody)
40 | fdumplist(astBuf, "buildssa-exit", fn.Func.Exit)
41 | if ssaDumpStdout {
42 | fmt.Println("generating SSA for", name)
43 | fmt.Print(astBuf.String())
44 | }
45 | }
46 |
47 | var s state
48 | s.pushLine(fn.Pos)
49 | defer s.popLine()
50 |
51 | s.hasdefer = fn.Func.HasDefer()
52 | if fn.Func.Pragma&CgoUnsafeArgs != 0 {
53 | s.cgoUnsafeArgs = true
54 | }
55 |
56 | fe := ssafn{
57 | curfn: fn,
58 | log: printssa && ssaDumpStdout,
59 | }
60 | s.curfn = fn
61 |
62 | s.f = ssa.NewFunc(&fe)
63 | s.config = ssaConfig
64 | s.f.Type = fn.Type
65 | s.f.Config = ssaConfig
66 | s.f.Cache = &ssaCaches[worker]
67 | s.f.Cache.Reset()
68 | s.f.DebugTest = s.f.DebugHashMatch("GOSSAHASH", name)
69 | s.f.Name = name
70 | s.f.PrintOrHtmlSSA = printssa
71 | if fn.Func.Pragma&Nosplit != 0 {
72 | s.f.NoSplit = true
73 | }
74 | s.panics = map[funcLine]*ssa.Block{}
75 | s.softFloat = s.config.SoftFloat
76 |
77 | if printssa {
78 | s.f.HTMLWriter = ssa.NewHTMLWriter(ssaDumpFile, s.f.Frontend(), name, ssaDumpCFG)
79 | // TODO: generate and print a mapping from nodes to values and blocks
80 | dumpSourcesColumn(s.f.HTMLWriter, fn)
81 | s.f.HTMLWriter.WriteAST("AST", astBuf)
82 | }
83 |
84 | // Allocate starting block
85 | s.f.Entry = s.f.NewBlock(ssa.BlockPlain)
86 |
87 | // Allocate starting values
88 | s.labels = map[string]*ssaLabel{}
89 | s.labeledNodes = map[*Node]*ssaLabel{}
90 | s.fwdVars = map[*Node]*ssa.Value{}
91 | s.startmem = s.entryNewValue0(ssa.OpInitMem, types.TypeMem)
92 |
93 | s.hasOpenDefers = Debug['N'] == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed()
94 | switch {
95 | case s.hasOpenDefers && (Ctxt.Flag_shared || Ctxt.Flag_dynlink) && thearch.LinkArch.Name == "386":
96 | // Don't support open-coded defers for 386 ONLY when using shared
97 | // libraries, because there is extra code (added by rewriteToUseGot())
98 | // preceding the deferreturn/ret code that is generated by gencallret()
99 | // that we don't track correctly.
100 | s.hasOpenDefers = false
101 | }
102 | if s.hasOpenDefers && s.curfn.Func.Exit.Len() > 0 {
103 | // Skip doing open defers if there is any extra exit code (likely
104 | // copying heap-allocated return values or race detection), since
105 | // we will not generate that code in the case of the extra
106 | // deferreturn/ret segment.
107 | s.hasOpenDefers = false
108 | }
109 | if s.hasOpenDefers &&
110 | s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 {
111 | // Since we are generating defer calls at every exit for
112 | // open-coded defers, skip doing open-coded defers if there are
113 | // too many returns (especially if there are multiple defers).
114 | // Open-coded defers are most important for improving performance
115 | // for smaller functions (which don't have many returns).
116 | s.hasOpenDefers = false
117 | }
118 |
119 | s.sp = s.entryNewValue0(ssa.OpSP, types.Types[TUINTPTR]) // TODO: use generic pointer type (unsafe.Pointer?) instead
120 | s.sb = s.entryNewValue0(ssa.OpSB, types.Types[TUINTPTR])
121 |
122 | s.startBlock(s.f.Entry)
123 | s.vars[&memVar] = s.startmem
124 | if s.hasOpenDefers {
125 | // Create the deferBits variable and stack slot. deferBits is a
126 | // bitmask showing which of the open-coded defers in this function
127 | // have been activated.
128 | deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8])
129 | s.deferBitsTemp = deferBitsTemp
130 | // For this value, AuxInt is initialized to zero by default
131 | startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8])
132 | s.vars[&deferBitsVar] = startDeferBits
133 | s.deferBitsAddr = s.addr(deferBitsTemp, false)
134 | s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits)
135 | // Make sure that the deferBits stack slot is kept alive (for use
136 | // by panics) and stores to deferBits are not eliminated, even if
137 | // all checking code on deferBits in the function exit can be
138 | // eliminated, because the defer statements were all
139 | // unconditional.
140 | s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false)
141 | }
142 |
143 | // Generate addresses of local declarations
144 | s.decladdrs = map[*Node]*ssa.Value{}
145 | for _, n := range fn.Func.Dcl {
146 | switch n.Class() {
147 | case PPARAM, PPARAMOUT:
148 | s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type), n, s.sp, s.startmem)
149 | if n.Class() == PPARAMOUT && s.canSSA(n) {
150 | // Save ssa-able PPARAMOUT variables so we can
151 | // store them back to the stack at the end of
152 | // the function.
153 | s.returns = append(s.returns, n)
154 | }
155 | case PAUTO:
156 | // processed at each use, to prevent Addr coming
157 | // before the decl.
158 | case PAUTOHEAP:
159 | // moved to heap - already handled by frontend
160 | case PFUNC:
161 | // local function - already handled by frontend
162 | default:
163 | s.Fatalf("local variable with class %v unimplemented", n.Class())
164 | }
165 | }
166 |
167 | // Populate SSAable arguments.
168 | for _, n := range fn.Func.Dcl {
169 | if n.Class() == PPARAM && s.canSSA(n) {
170 | v := s.newValue0A(ssa.OpArg, n.Type, n)
171 | s.vars[n] = v
172 | s.addNamedValue(n, v) // This helps with debugging information, not needed for compilation itself.
173 | }
174 | }
175 |
176 | // Convert the AST-based IR to the SSA-based IR
177 | s.stmtList(fn.Func.Enter)
178 | s.stmtList(fn.Nbody)
179 |
180 | // fallthrough to exit
181 | if s.curBlock != nil {
182 | s.pushLine(fn.Func.Endlineno)
183 | s.exit()
184 | s.popLine()
185 | }
186 |
187 | for _, b := range s.f.Blocks {
188 | if b.Pos != src.NoXPos {
189 | s.updateUnsetPredPos(b)
190 | }
191 | }
192 |
193 | s.insertPhis()
194 |
195 | // Main call to ssa package to compile function
196 | ssa.Compile(s.f)
197 |
198 | if s.hasOpenDefers {
199 | s.emitOpenDeferInfo()
200 | }
201 |
202 | return s.f
203 | }
204 | ```
205 | 如果在构建ssa时如发现`gcflags`有N禁止优化的参数 或者 `return数量 * defer数量`超过了15不适用`open-coded`模式。
206 |
207 | 此外逃逸分析会判断循序的层数,如果有轮询,那么强制使用栈分配模式。
208 |
209 | ```go
210 | // augmentParamHole augments parameter holes as necessary for use in
211 | // go/defer statements.
212 | func (e *Escape) augmentParamHole(k EscHole, call, where *Node) EscHole {
213 | k = k.note(call, "call parameter")
214 | if where == nil {
215 | return k
216 | }
217 |
218 | // Top level defers arguments don't escape to heap, but they
219 | // do need to last until end of function. Tee with a
220 | // non-transient location to avoid arguments from being
221 | // transiently allocated.
222 | if where.Op == ODEFER && e.loopDepth == 1 {
223 | // force stack allocation of defer record, unless open-coded
224 | // defers are used (see ssa.go)
225 | where.Esc = EscNever
226 | return e.later(k)
227 | }
228 | return e.heapHole().note(where, "call parameter")
229 | }
230 | ```
231 |
232 | #### 开放编码
233 |
234 | Golang 1.14 版本继续加入了开发编码(open coded),该机制会将延迟调用直接插入函数返回之前,省去了运行时的 `deferproc` 或 `deferprocStack` 操作,在运行时的 `deferreturn` 也不会进行尾递归调用,而是直接在一个循环中遍历所有延迟函数执行。
235 |
236 | 这种机制使得 defer 的开销几乎可以忽略,唯一的运行时成本就是存储参与延迟调用的相关信息,不过使用这个机制还需要三个条件:
237 |
238 | 1. 没有禁用编译器优化,即没有设置 `-gcflags "-N"`.
239 | 2. 函数内 defer 的数量不超过 8 个,且返回语句与延迟语句个数的乘积不超过 15.
240 | 3. defer 不是在循环语句中。
241 |
242 | 此外该机制还引入了一种元素 —— 延迟比特(defer bit),用于运行时记录每个 defer 是否被执行(尤其是在条件判断分支中的 defer),从而便于判断最后的延迟调用该执行哪些函数。
243 |
244 | 延迟比特的原理:
245 |
246 | 同一个函数内每出现一个 defer 都会为其分配 1个比特,如果被执行到则设为 1,否则设为 0,当到达函数返回之前需要判断延迟调用时,则用掩码判断每个位置的比特,若为 1 则调用延迟函数,否则跳过。
247 |
248 | 为了轻量,官方将延迟比特限制为 1 个字节,即 8 个比特,这就是为什么不能超过 8 个 defer 的原因,若超过依然会选择堆栈分配,但显然大部分情况不会超过 8 个。
249 |
250 | ```go
251 | // The constant is known to runtime.
252 | const tmpstringbufsize = 32
253 | const zeroValSize = 1024 // must match value of runtime/map.go:maxZero
254 |
255 | func walk(fn *Node) {
256 | Curfn = fn
257 |
258 | if Debug['W'] != 0 {
259 | s := fmt.Sprintf("\nbefore walk %v", Curfn.Func.Nname.Sym)
260 | dumplist(s, Curfn.Nbody)
261 | }
262 |
263 | lno := lineno
264 |
265 | // Final typecheck for any unused variables.
266 | for i, ln := range fn.Func.Dcl {
267 | if ln.Op == ONAME && (ln.Class() == PAUTO || ln.Class() == PAUTOHEAP) {
268 | ln = typecheck(ln, ctxExpr|ctxAssign)
269 | fn.Func.Dcl[i] = ln
270 | }
271 | }
272 |
273 | // Propagate the used flag for typeswitch variables up to the NONAME in its definition.
274 | for _, ln := range fn.Func.Dcl {
275 | if ln.Op == ONAME && (ln.Class() == PAUTO || ln.Class() == PAUTOHEAP) && ln.Name.Defn != nil && ln.Name.Defn.Op == OTYPESW && ln.Name.Used() {
276 | ln.Name.Defn.Left.Name.SetUsed(true)
277 | }
278 | }
279 |
280 | for _, ln := range fn.Func.Dcl {
281 | if ln.Op != ONAME || (ln.Class() != PAUTO && ln.Class() != PAUTOHEAP) || ln.Sym.Name[0] == '&' || ln.Name.Used() {
282 | continue
283 | }
284 | if defn := ln.Name.Defn; defn != nil && defn.Op == OTYPESW {
285 | if defn.Left.Name.Used() {
286 | continue
287 | }
288 | yyerrorl(defn.Left.Pos, "%v declared but not used", ln.Sym)
289 | defn.Left.Name.SetUsed(true) // suppress repeats
290 | } else {
291 | yyerrorl(ln.Pos, "%v declared but not used", ln.Sym)
292 | }
293 | }
294 |
295 | lineno = lno
296 | if nerrors != 0 {
297 | return
298 | }
299 | walkstmtlist(Curfn.Nbody.Slice())
300 | if Debug['W'] != 0 {
301 | s := fmt.Sprintf("after walk %v", Curfn.Func.Nname.Sym)
302 | dumplist(s, Curfn.Nbody)
303 | }
304 |
305 | zeroResults()
306 | heapmoves()
307 | if Debug['W'] != 0 && Curfn.Func.Enter.Len() > 0 {
308 | s := fmt.Sprintf("enter %v", Curfn.Func.Nname.Sym)
309 | dumplist(s, Curfn.Func.Enter)
310 | }
311 | }
312 | ```
313 |
314 | 在使用`open code`的模式的时候,默认`open coded`最多支持8个defer,超过则取消。
315 |
316 | ```go
317 | const maxOpenDefers = 8
318 |
319 | func walkstmt(n *Node) *Node {
320 | ...
321 | switch n.Op {
322 | case ODEFER:
323 | Curfn.Func.SetHasDefer(true)
324 | Curfn.Func.numDefers++
325 | if Curfn.Func.numDefers > maxOpenDefers {
326 | Curfn.Func.SetOpenCodedDeferDisallowed(true)
327 | }
328 |
329 | if n.Esc != EscNever {
330 | Curfn.Func.SetOpenCodedDeferDisallowed(true)
331 | }
332 | ...
333 | }
334 | ```
335 |
336 | 因此 `open coded`的使用条件是,最多8个defer,而且 `return * defer < 15`,无循环,`gcflags`无 “N” 并且取消优化。
337 |
--------------------------------------------------------------------------------
/src/chapter16/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Golang中的TCP开发
2 |
3 | 在了解Golang的TCP开发之前, 我们先来了解下网际协议是什么?
4 |
5 | #### 网际协议
6 |
7 | 网际协议(Internet Protocol,缩写IP),又称互联网协议,是用于分组交换数据网络的协议。
8 |
9 | IP是在 `TCP/IP` 协议族中网络层的主要协议,任务仅仅是根据源主机和目的主机的地址来传送数据。为此目的,IP定义了寻址方法和数据报的封装结构。
10 |
11 | 第一个架构的主要版本为IPv4,目前仍然是广泛使用的互联网协议,尽管世界各地正在积极部署IPv6。
12 |
13 | * IP封装
14 |
15 | 数据在IP互联网中传送时会封装为数据包。
16 |
17 | 网际协议的独特之处在于:在报文交换网络中主机在传输数据之前,无须与先前未曾通信过的目的主机预先创建好特定的"通路"。互联网协议提供了"不可靠的"数据包传输机制(也称"尽力而为"或"尽最大努力交付");也就是说,它不保证数据能准确的传输。
18 |
19 | 数据包在到达的时候可能已经损坏,顺序错乱(与其它一起传送的报文相比),产生冗余包,或者全部丢失。 如果应用需要保证可靠性,一般需要采取其他的方法,例如利用IP的上层协议控制。
20 |
21 | * IP提供的服务
22 |
23 | 由于封装带来的抽象机制,IP能够在各种各样的网络上工作,例如以太网、异步传输模式、FDDI、Wi-Fi、令牌环等等。每个链路层的实现可能有自己的方法(也有可能是完全没有它自己的方法),把IP地址解析成相应的数据链路地址。IPv4使用地址解析协议(ARP),而IPv6采用邻居发现协议(NDP)。
24 |
25 | * 可靠性
26 |
27 | 互联网协议的设计原则,假定网络基础设施本身就是不可靠的单一网络元素或传输介质,并且它使用的是动态的节点和连接。不存在中央监测和性能衡量机制来跟踪和维护网络的状态。为了减少网络的复杂性,大部分网络只能故意地分布在每个数据传输的终端节点。传输路径中的路由器只是简单地将资料包发送到下一个匹配目的地址的路由前缀的本地网关。
28 |
29 | 由于这种设计的结果,互联网协议只提供尽力传送,其服务也不受信任。在网络专业语言中是无连接的协议,相对于所谓的面向连接的模式。
30 |
31 | 在缺乏可靠性的条件下允许下列任何故障发生:
32 |
33 | * 数据损坏.
34 | * 丢失数据.
35 | * 重复到来.
36 | * 数据包传递乱序;意思是,报文A即使在报文B之前发送,B也可能在A之前先抵达.
37 |
38 | 互联网协议提供的唯一帮助是,IPv4规定透过在路由器节点计算校验和来确保IP数据报头是正确的。这个带来的副作用是当场丢弃报头错误的资料包。在这种情况下不需要发送通知给任一终端节点,但是互联网控制消息协议(ICMP)中存在做到这一点的机制。
39 |
40 | 但是,IPv6为了快速传输已经放弃了计算校验和的使用。对这些可靠性问题的更正是上层协议的责任。例如,上层协议为了确保按顺序传送可能要缓存数据,直到数据可以传递给应用程序。
41 |
42 | 除了可靠性问题,互联网及其组成部分的动态性和多样性不能确保任何路径是有能力地或合适地完成所要求的数据传输,即使路径是有效并且可靠的。技术限制有在给定的链路上允许的数据包的大小。
43 | 应用程序必须确保它使用适当的传输特性。这种责任还在于一些在应用层协议和IP之间的上层协议。
44 |
45 | 存在审查的本地连接尺寸最大传输单位(MTU),以及整个预计到目标路径时使用IPv6。IPv4的网络层有自动分片成更小的单位进行传输原始数据报的能力。在这种情况下,IP确实能够为乱序的分片进行顺序排序。
46 |
47 | * IP寻址和路由
48 |
49 | 网际协议最为复杂的方面可能就是寻址和路由。寻址就是如何将IP地址分配给各个终端节点,以及如何划分和组合子网。所有网络端点都需要路由,尤其是网际之间的路由器。路由器通常用内部网关协议(Interior Gateway Protocols,IGPs)和外部网关协议(External Gateway Protocols,EGPs)决定怎样发送IP数据包。
50 |
51 | #### OSI五层模型
52 |
53 |
54 |
55 |
56 |
57 | 在互联网中,按照不同的模型划分会有不用的分层,越往上的层越靠近用户,越往下的层越靠近硬件。
58 |
59 | 接下来我们一层一层的自底向上介绍一下每一层的作用.
60 |
61 | * 物理层
62 |
63 | 通常情况下我们的计算机要与外界互联网通信,需要先把计算机连接网络,因此可以用双绞线、光纤、无线电波等方式。
64 |
65 | 这就叫做"实物理层",它就是把电脑连接起来的物理手段。它主要规定了网络的一些电气特性,作用是负责传送0和1的电信号。
66 |
67 | 物理层解决如何在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的主要任务描述为:确定与传输媒体的接口的一些特性。
68 |
69 | * 数据链路层
70 |
71 | 单纯的0和1没有任何意义,因此呢,使用者会为其赋予一些特定的含义,规定解读电信号的方式。
72 |
73 | 早期的时候,每家公司都有自己的电信号分组方式。随着不断发展,"以太网"(Ethernet)的协议,占据了主导地位,然后成为了标准。以太网规定,一组电信号构成一个数据包,叫做"帧"(Frame)。每一帧分成两个部分:标头(Head)和数据(Data)。
74 |
75 | 其中"标头"包含数据包的一些说明项,比如发送者、接受者、数据类型等等;"数据"则是数据包的具体内容。"标头"的长度,固定为18字节。"数据"的长度,最短为46字节,最长为1500字节。因此,整个"帧"最短为64字节,最长为1518字节。如果数据很长,就必须分割成多个帧进行发送。
76 |
77 | 但是,发送者和接受者是如何标识呢?以太网规定,连入网络的所有设备都必须具有"网卡"接口。数据包必须是从一块网卡,传送到另一块网卡。网卡的地址,就是数据包的发送地址和接收地址,这叫做MAC地址。
78 |
79 | 每块网卡出厂的时候,都有一个全世界独一无二的MAC地址,长度是48个二进制位,通常用12个十六进制数表示。前6个十六进制数是厂商编号,后6个是该厂商的网卡流水号。有了MAC地址,就可以定位网卡和数据包的路径了。
80 |
81 | 我们会通过ARP协议来获取接受方的MAC地址,有了MAC地址之后,如何把数据准确的发送给接收方呢?
82 |
83 | 其实这里以太网采用了一种很"原始"的方式,它不是把数据包准确送到接收方,而是向本网络内所有计算机都发送,让每台计算机读取这个包的"标头",找到接收方的MAC地址,然后与自身的MAC地址相比较,如果两者相同,就接受这个包,做进一步处理,否则就丢弃这个包。这种发送方式就叫做"广播"(broadcasting)。
84 |
85 | * 网络层
86 |
87 | 按照以太网协议的规则我们可以依靠MAC地址来向外发送数据。理论上依靠MAC地址,你电脑的网卡就可以找到身在世界另一个角落的某台电脑的网卡了,但是这种做法有一个很大缺陷就是以太网采用广播方式发送数据包,所有需要成员人手一"包",不仅效率低,而且发送的数据只能局限在发送者所在的子网络。
88 |
89 | 也就是意味着,如果两台计算机不在同一个子网络,广播是传不过去的。这种设计是合理且必要的,因为如果互联网上每一台计算机都会收到互联网上收发的所有数据包,那是不现实的。
90 |
91 | 因此,必须找到一种方法区分哪些MAC地址属于同一个子网络,哪些不是。如果是同一个子网络,就采用广播方式发送,否则就采用"路由"方式发送。这就导致了"网络层"的诞生。
92 |
93 | 它的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做"网络地址",简称"网址"。
94 |
95 | "网络层"出现以后,每台计算机有了两种地址,一种是MAC地址,另一种是网络地址。两种地址之间没有任何联系,MAC地址是绑定在网卡上的,网络地址则是网络管理员分配的。
96 |
97 | 网络地址帮助我们确定计算机所在的子网络,MAC地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理MAC地址。
98 |
99 | 规定网络地址的协议,叫做IP协议。它所定义的地址,就被称为IP地址。目前,广泛采用的是IP协议第四版,简称`IPv4`。
100 |
101 | IPv4这个版本规定,网络地址由32个二进制位组成,我们通常习惯用分成四段的十进制数表示IP地址,从`0.0.0.0`一直到`255.255.255.255`。
102 |
103 | 根据IP协议发送的数据,就叫做IP数据包。IP数据包也分为"标头"和"数据"两个部分:"标头"部分主要包括版本、长度、IP地址等信息,"数据"部分则是IP数据包的具体内容。
104 |
105 | IP数据包的"标头"部分的长度为20到60字节,整个数据包的总长度最大为65535字节。
106 |
107 | * 传输层
108 |
109 | 有了MAC地址和IP地址,我们已经可以在互联网上任意两台主机上建立通信。但问题是同一台主机上会有许多程序都需要用网络收发数据,比如邮箱和浏览器这两个程序都需要连接互联网并收发数据,我们如何区分某个数据包到底是归哪个程序的呢?
110 |
111 | 这时候,我们还需要一个参数,表示这个数据包到底供哪个程序(进程)使用。这个参数就叫做"端口"(port),它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据。
112 |
113 | "端口"是0到65535之间的一个整数,正好16个二进制位。0到1023的端口被系统占用,用户只能选用大于1023的端口。有了IP和端口我们就能实现唯一确定互联网上一个程序,进而实现网络间的程序通信。
114 |
115 | 我们必须在数据包中加入端口信息,这就需要新的协议。最简单的实现叫做UDP协议,它的格式几乎就是在数据前面,加上端口号。
116 |
117 | UDP数据包,也是由"标头"和"数据"两部分组成:"标头"部分主要定义了发出端口和接收端口,"数据"部分就是具体的内容。UDP数据包非常简单,"标头"部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。
118 |
119 | UDP协议的优点是比较简单,容易实现,但是缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。为了解决这个问题,提高网络可靠性,TCP协议就诞生了。
120 |
121 | TCP协议能够确保数据不会遗失,它的缺点是过程复杂、实现困难、消耗较多的资源。TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
122 |
123 | * 应用层
124 |
125 | 应用程序收到"传输层"的数据,接下来就要对数据进行解包。由于互联网是开放架构,数据来源各种各样,必须事先规定好通信的数据格式,否则接收方根本无法获得真正发送的数据内容。
126 |
127 | "应用层"的作用就是规定应用程序使用的数据格式,例如我们TCP协议之上常见的Email、HTTP、FTP等协议,这些协议就组成了互联网协议的应用层。
128 |
129 | 发送方的HTTP数据经过互联网的传输过程中会依次添加各层协议的标头信息,接收方收到数据包之后再依次根据协议解包得到数据。
130 |
131 |
132 |
133 |
134 |
135 | 因此应用层主要是提供用户接口,特指能够发起网络流量的程序,比如客户端程序: QQ,MSN,浏览器等。服务器程序:web服务器,邮件服务器,流媒体服务器等等。数据单位为报文。
136 |
137 | #### socket编程
138 |
139 | 我们通常会听到socker编程,但是socket编程具体指什么,可能不是所有人都很清楚,下面我们了解下什么是socket编程。
140 |
141 | Socket是`BSD UNIX`的进程通信机制,通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为`TCP/IP`网络的API,它定义了许多函数或例程,开发人员可以用它们来开发TCP/IP网络上的应用程序。
142 |
143 | 电脑上运行的应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
144 |
145 | Socket是应用层与`TCP/IP`协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。
146 |
147 |
148 |
149 |
150 |
151 | 原始的TCP socket开发中,偏向于底层,基本利用系统调用和操作系统交互。
152 |
153 | 通常会包括:
154 |
155 | * 同步
156 | * 异步
157 | * 阻塞
158 | * 非阻塞
159 |
160 | 在实际业务使用中,从高性能的角度考虑,经典的使用方式为:Reactor模式的IO多路复用。
161 |
162 | 整个实现模型还是比较复杂,涉及到几个关键、复杂的模块:
163 |
164 | * 多线程.
165 | * 工作线程池.
166 | * 任务队列.
167 | * 复用IO.
168 |
169 | 定时器模型:
170 |
171 | 因此,出现了许多高性能IO多路复用框架:`libevent/libev/libuv`等。目的是降低开发者的开发复杂度。
172 |
173 | Go设计的目标之一就是面向大规模后端服务程序,网络通信又是至关重要的一部分。go中暴露给语言使用者的`tcp socket api`是建立在OS原生tcp socket接口之上,其中配合了go runtime的调度需要,所以和OS原生接口存在差别。
174 |
175 | 相对于传统的IO多路复用框架,go语言直接将"复杂性"隐藏在Runtime中。
176 |
177 | #### TCP协议
178 |
179 | TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。
180 |
181 | #### 二进制协议
182 |
183 | 基于文本类型的协议(比如 JSON)和二进制协议都是字节通信,他们不同点在于他们使用哪种类型的字节和如何组织这些字节。
184 |
185 | 文本协议只适用于 ASCII 或 Unicode 编码可打印的字符通信。例如 "26" 使用 "2" 和 "6" 的 utf 编码的字符串表示,这种方式方便我们读,但对于计算机效率较低。
186 |
187 | 在二进制协议中,同样数字 "26" 可使用一个字节 `0x1A` 十六进制表示,减少了一半的存储空间且原始的字节格式能够被计算机直接识别而不需解析。当一个数字足够大的时候,性能优势就会明显体现。
188 |
189 | #### 计算机字节序和网络字节序
190 |
191 | 这里我们需要了解下CPU和内存一些知识。
192 |
193 | * CPU与内存
194 |
195 | 内存的最小存储单位为一个字节,我们通过一个十六进制的数据表示每个字节的编号(比如4G内存的十六进制表示从`0x0000 0000`到`0xffff ffff`)。其中内存地址的编号上限由地址总线`address bus`的位数相关,CPU通过地址总线来表示向内存说明想要存储数据的地址。
196 |
197 | 32位CPU,CPU包含32个针脚来传递地址信息,每个针脚能传递的信息为1bit(即0 或者1),对应的地址空间大小为`2^32 = 4G`。
198 |
199 | 64位CPU拥有更大的寻址能力,最大支持到16GB内存,而对比32位的CPU只支持4G内存。
200 |
201 | 同其他存储介质相比,内存的存储单元采用了随机读取存储器RAM, `Random Access Memory`,存储器读取数据花费的时间和数据所在的位置无关(而磁盘和磁带只能顺序访问数据,有损于速度和效率)。
202 | 这个特性使得系统可以把控进程的运行时间,这也是内存称为主存储器的关键因素。内存的缺点是不能持久化数据,计算机一旦断电内存中的数据就会消失。
203 |
204 | * 计算机字节序
205 |
206 | 字节序,也就是字节的顺序,指的是多字节的数据类型(int, float 等)在内存中的存放顺序。
207 |
208 | 在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如:如果C/C++中的一个int型变量 a 的起始地址是`&a = 0x100`,那么 a 的四个字节将被存储在存储器的`0x100`, `0x101`, `0x102`, `0x103`位置。
209 |
210 | 根据整数 a 在连续的 4 byte 内存中的存储顺序,字节序被分为大端序(Big Endian) 与 小端序(Little Endian)两类。 然后就牵涉出两大CPU派系:
211 |
212 | * `Motorola 6800`,`PowerPC 970`,SPARC(除V9外)等处理器采用 `Big Endian`方式存储数据;
213 |
214 | * x86系列,VAX,PDP-11等处理器采用`Little Endian`方式存储数据。
215 |
216 |
217 |
218 |
219 |
220 | * `Big Endian` 大端是指低地址端存放高位字节。
221 | * `Little Endian` 小端是指低地址端存放低位字节。
222 |
223 | 在计算机内部,小端序被广泛应用于现代性`CPU`内部存储数据;而在文件存储和网络传输一般采用 `Big Endian`大端。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输.
224 |
225 | 使用小端序时不移动字节就能改变 number 占内存的大小而不需内存地址起始位。比如我想把四字节的 int32 类型的整型转变为八字节的 int64 整型,只需在小端序末端加零即可。
226 |
227 | ```bash
228 | 55 33 22 11
229 | 55 33 22 11 00 00 00 00
230 | ```
231 |
232 | 上述扩展或缩小整型变量操作在编译器层面非常有用,但在网络协议层则不是这样了。
233 |
234 | 在网络协议层操作二进制数字时约定使用大端序,大端序是网络字节传输采用的方式。因为大端序最高有效字节排在首位(低地址端存放高位字节),能够按照字典排序,所以我们能够比较二进制编码后数字的每个字节。
235 |
236 | ```go
237 | fmt.Println(bytes.Equal([]byte("Golang Best"), []byte("Golang Best"))) // true
238 | ```
239 |
240 | 固定长度编码 `Fixed-length encoding`
241 |
242 | Go中有多种类型的整型, int8, int16, int32 和 int64 ,分别使用 1, 2, 4, 8 个字节表示,我们称之为固定长度类型 (fixed-length types)。 处理字节流和内存中的字节切片方式不一样,编码 (Encoding) 和解码 (decoding) 二进制数据过程也不一样。
243 |
244 | 可变长度编码理想情况下值小的数字占用的空间比值大的数字少,有多种实现方案,Go Binary 实现方式和 `protocol buffer encoding` 一致,具体原理如下:
245 |
246 | 每个字节的首位存放一个标识位,用以表明是否还有跟多字节要读取及剩下的七位是否真正存储数据。
247 |
248 | 标识位分别为 `0` 和 `1`:
249 |
250 | * 1 表示还要继续读取该字节后面的字节.
251 | * 0 表示停止读取该字节后面的字节.
252 |
253 | 一旦所有读取完所有的字节,每个字节串联的结果就是最后的值。
254 |
255 | 例如,数字53用二进制表示为 110101 ,需要六位存储,除了标识位还剩余七位,所以在标识位后补 0 凑够七位,最终结果为 00110101。标识位 0 表明所在字节后面没有字节可读了,标识位后面的 0110101 保存了值。
256 |
257 | 再来一个大点的数字举例,1732 二进制使用`11011000100`表示,实际上只需使用 11 位的空间存储,除了标识位每个字节只能保存 7 位,所以数字 1732 需要两个字节存储。第一个字节使用 1 表示所在字节后面还有字节,第二个字节使用 0 表示所在字节后面没有字节,最终结果为:`10001101 01000100`.
258 |
259 | 二进制协议 (Binary protocol) 高效地在底层处理数据通信,字节序决定字节输出的顺序、通过可变长度编码压缩数据存储空间。
260 |
261 | 各自的优势:
262 |
263 | * `Big Endian` 大端符号位的判定固定为第一个字节,容易判断正负。
264 | * `Little Endian` 小端长度为1,2,4字节的数,排列方式都是一样的,数据类型转换非常方便。
265 |
266 | #### TCP黏包
267 |
268 | TCP数据在发送和接收时会形成粘包,也就是没有按照预期的大小得到数据,数据包不完整。这个问题的产生并不是因为设计、代码。
269 |
270 | 封包在发送时,为了提高发送效率,无论是开发者使用的网络库,还是操作系统底层都会对封包进行拼包,将小包凑成大包,在TCP层可以节约包头的大小损耗,I/O层的调用损耗也可以有所降低。
271 |
272 | 在接收TCP封包时,接收缓冲区的大小与发送过来的TCP传输单元大小不等,这时候会造成两种情况:
273 |
274 | * 接收的数据大于等于接收缓冲区大小时,此时需要将数据复制到用户缓忡,接着读取后面的封包。
275 | * 接收的数据小于接收缓冲区大小时,此时需要继续等待后续的 TCP 封包。
276 |
277 | 那么为什么会出现粘包?
278 |
279 | 出现粘包的,主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
280 |
281 | "粘包"可发生在发送端也可发生在接收端:
282 |
283 | 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
284 |
285 | 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
286 |
287 | 解决办法:
288 |
289 | 出现"粘包"的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
290 |
291 | 封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入"包尾"内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
292 |
293 | 我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。
294 |
295 | ```go
296 | // socker
297 | package proto
298 |
299 | import (
300 | "bufio"
301 | "bytes"
302 | "encoding/binary"
303 | )
304 |
305 | // Encode 将消息编码
306 | func Encode(message string) ([]byte, error) {
307 | // 读取消息的长度,转换成int32类型(占4个字节)
308 | var length = int32(len(message))
309 | var pkg = new(bytes.Buffer)
310 | // 写入消息头
311 | err := binary.Write(pkg, binary.LittleEndian, length)
312 | if err != nil {
313 | return nil, err
314 | }
315 | // 写入消息实体
316 | err = binary.Write(pkg, binary.LittleEndian, []byte(message))
317 | if err != nil {
318 | return nil, err
319 | }
320 | return pkg.Bytes(), nil
321 | }
322 |
323 | // Decode 解码消息
324 | func Decode(reader *bufio.Reader) (string, error) {
325 | // 读取消息的长度
326 | lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
327 | lengthBuff := bytes.NewBuffer(lengthByte)
328 | var length int32
329 | err := binary.Read(lengthBuff, binary.LittleEndian, &length)
330 | if err != nil {
331 | return "", err
332 | }
333 | // Buffered返回缓冲中现有的可读取的字节数。
334 | if int32(reader.Buffered()) < length+4 {
335 | return "", err
336 | }
337 |
338 | // 读取真正的消息数据
339 | pack := make([]byte, int(4+length))
340 | _, err = reader.Read(pack)
341 | if err != nil {
342 | return "", err
343 | }
344 | return string(pack[4:]), nil
345 | }
346 | ```
347 |
348 | 接下来在服务端和客户端分别使用上面定义的Decode和Encode函数处理数据。
349 |
350 | ```go
351 | // server端
352 | func process(conn net.Conn) {
353 | defer conn.Close()
354 | reader := bufio.NewReader(conn)
355 | for {
356 | msg, err := proto.Decode(reader)
357 | if err == io.EOF {
358 | return
359 | }
360 | if err != nil {
361 | fmt.Println("decode msg failed, err:", err)
362 | return
363 | }
364 | fmt.Println("收到client发来的数据:", msg)
365 | }
366 | }
367 |
368 | func main() {
369 | listen, err := net.Listen("tcp", "127.0.0.1:10890")
370 | if err != nil {
371 | fmt.Println("listen failed, err:", err)
372 | return
373 | }
374 | defer listen.Close()
375 | for {
376 | conn, err := listen.Accept()
377 | if err != nil {
378 | fmt.Println("accept failed, err:", err)
379 | continue
380 | }
381 | go process(conn)
382 | }
383 | }
384 | ```
385 |
386 | ```go
387 | // client 端
388 | func main() {
389 | conn, err := net.Dial("tcp", "127.0.0.1:30000")
390 | if err != nil {
391 | fmt.Println("dial failed, err", err)
392 | return
393 | }
394 | defer conn.Close()
395 | for i := 0; i < 20; i++ {
396 | msg := `Hello, Hello. How are you?`
397 | data, err := proto.Encode(msg)
398 | if err != nil {
399 | fmt.Println("encode msg failed, err:", err)
400 | return
401 | }
402 | conn.Write(data)
403 | }
404 | }
405 | ```
406 |
--------------------------------------------------------------------------------
/src/chapter17/golang.01.md:
--------------------------------------------------------------------------------
1 | #### CPU上下文切换
2 |
3 | 由于Linux 是一个多任务操作系统,而且,它支持远大于 CPU 数量的任务同时运行,但是 这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们,造成多任务同时运行的错觉。
4 | 而在每个任务运行前,CPU 都需要知道任务从哪里加载和从哪里开始运行,通常都会需要系统事先帮它设置好 CPU 寄存器和程序计数器(Program Counter, PC).
5 |
6 | 在CPU中,寄存器是CPU内置的容量小、但速度极快的内存。然而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU上下文.
7 |
8 | CPU 上下文切换 ,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。 而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行.
9 |
10 | #### 什么是任务
11 |
12 | CPU 上下文切换无非就是更新了 CPU 寄存器的值嘛,但这些寄存器,本身就是为了快速运行任务而设计的,为什么会影响系统的 CPU 性能呢?
13 |
14 | 在解析这个问题前,我们需要先了解下操作系统管理的这些“任务”到底是什么呢?
15 |
16 | 进程和线程正是最常见的任务。但是除此之外,硬件通过触发信号,会导致中断处理程序的调用,也是一种常见的任务。
17 |
18 | 所以,根据任务的不同,CPU上下文切换可分为几种场景:
19 |
20 | * 系统调用上下文切换.
21 | * 进程上下文切换.
22 | * 线程上下文切换.
23 | * 中断上下文切换.
24 |
25 | #### 系统调用引起的上下文切换
26 |
27 | Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中, CPU 特权等级的 Ring 0 和 Ring 3。
28 |
29 |
30 |
31 |
32 |
33 | * 内核空间(Ring 0)具有最高权限,可以直接访问所有资源.
34 | * 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源.
35 |
36 | 由系统调用完成从用户态到内核态的转变。这个过程也会发生CPU上下文切换。
37 |
38 | CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。
39 |
40 | 而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。 所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。
41 |
42 | 不过,需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟我们通常所说的进程上下文切换是不一样的(系统调用在同一个进程里运行)。 所以系统调用过程通常称为 特权模式切换 ,而不是上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。
43 |
44 | #### 进程上下文切换
45 |
46 | 进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了 虚拟内存、栈、全局变量等用户空间的资源 ,还包括了 内核堆栈、寄存器等内核空间的状态. 因此,进程的上下文切换就比系统调用时多了一步: 在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
47 |
48 | 在每次进程进行上下文切换时,通常都需要几十纳秒到数微妙的 CPU 时间。这个时间还是相当不错的,尤其是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。这也是导致平均负载升高的一个重要因素。
49 |
50 | 进程上下文切换的时机:
51 |
52 | * 进程所分配的时间片耗尽,就会被系统挂起.
53 | * 进程在系统资源(比如内存)不足时,需等待资源满足才能运行.
54 | * 进程调用如sleep等方法主动挂起.
55 | * 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起.
56 | * 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序.
57 |
58 | #### 线程上下文切换
59 |
60 | 线程与进程的一大区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。内核的任务调度对象是线程。而进程只是给线程提供了虚拟内存、全局变量等资源。 线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
61 |
62 | 线程上下文切换可以分为两种:
63 |
64 | * 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样.
65 | * 前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据.
66 | * 虽然同为上下文切换,但同进程内的线程切换,要比多进程间的切换消耗更少的资源,这也正是多线程代替多进程的一个优势.
67 |
68 | #### 中断上下文切换
69 |
70 | 为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行 ,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
71 |
72 | 跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
73 |
74 | 对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。
75 |
76 | 因此,CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下,不需要我们特别关注。 但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降。
77 |
--------------------------------------------------------------------------------
/src/chapter18/golang.01.md:
--------------------------------------------------------------------------------
1 | #### 汇编
2 |
3 | 在计算机中,真正能够被计算机理解的是低级语言,它专门用来控制硬件。汇编语言就是低级语言,直接描述控制 CPU 的运行。
4 |
5 | #### 汇编语言是什么
6 |
7 | 在计算机中,CPU 只负责计算。但是你输入一条指令(instruction),它就运行一次,然后停下来,等待下一条指令。
8 |
9 | 这些指令都是二进制的,称为操作码(opcode), 比如加法指令就是`00000011`。编译器的作用,就是将高级语言写好的程序,翻译成一条条操作码。
10 |
11 | 对于人来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,就诞生了汇编语言。
12 |
13 | 汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。
14 |
15 | 最开始的时候,编写程序就是手写二进制指令,然后通过各种开关输入计算机,比如要做加法了,就按一下加法开关。随着后来,发明了纸带打孔机,通过在纸带上打孔,将二进制指令自动输入计算机。
16 |
17 | 然而,为了解决二进制指令的可读性问题,工程师将那些指令写成了八进制。二进制转八进制是轻而易举的,但是八进制的可读性也不行。很自然地,最后还是用文字表达,加法指令写成 ADD。内存地址也不再直接引用,而是用标签表示。
18 |
19 | 这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler。它处理的文本,自然就叫做 aseembly code。标准化以后,称为 assembly language,缩写为 asm,中文译为汇编语言。
20 |
21 | #### 寄存器
22 |
23 | 在汇编中需要了解到的是寄存器和和内存模型。
24 |
25 | 在计算机中,CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。
26 |
27 | 但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。
28 |
29 |
30 |
31 |
32 |
33 | 寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。
34 |
35 | 寄存器的种类:
36 |
37 | 在早期的时候,x86 CPU 只有8个寄存器,而且每个都有不同的用途。但是现在的寄存器已经有100多个了,而且都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来。
38 |
39 | ```markdown
40 | * EAX 的全称是Extended Accumulator X,即累加寄存器。A代表Accumulator.
41 | * EBX 的全称是Extended Base X,即基地址"(base)寄存器, 在内存寻址时存放基地址.
42 | * ECX 的全称是Extended Counting X,即计数寄存器。C代表counting.
43 | * EDX 的全称是Extended Data X,即总是被用来放整数除法产生的余数.
44 | * EDI 的全称是Extended Destination Indexing,即目的索引寄存器。D代表destnation,I代表Indexing.
45 | * ESI 的全称是Extended Source Indexing,即源索引寄存器。S代表Source,I代表Indexing.
46 | * EBP 的全称是Extended (Stack) Base Pointer ,即栈基指针寄存器。B代表Base.
47 | * ESP 的全称是Extended Stack Pointer,即栈指针寄存器。S代表Stack.
48 | ```
49 | 这里的这8个寄存器之中,前面七个都是通用的。ESP 寄存器有特定用途,保存当前 Stack 的地址.
50 |
51 |
52 |
53 |
54 |
55 | 通常我们常常看到 32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。
56 |
57 | #### 内存模型Heap
58 |
59 | 寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从`0x1000`到`0x8000`,起始地址是较小的那个地址,结束地址是较大的那个地址。
60 |
61 |
62 |
63 |
64 |
65 | 在程序中,这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。
66 |
67 | #### 内存模型Stack
68 |
69 | 在计算机中,除了堆(Heap)以外,其他的内存占用叫做Stack(栈)。通常来说,Stack是由于函数运行而临时占用的内存区域。
70 |
71 |
72 |
73 |
74 |
75 | 所有的帧都存放在 Stack,由于帧是一层层叠加的,所以Stack叫做栈。生成新的帧,叫做"入栈",英文是 push;栈的回收叫做"出栈",英文是 pop。
76 |
77 | Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出"的数据结构。每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。
78 |
79 | Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。
80 |
--------------------------------------------------------------------------------
/src/chapter19/golang.01.md:
--------------------------------------------------------------------------------
1 | #### Serverless
2 |
3 | Serverless 是最近几年业界很火的技术名词,你可以在国内外各种技术大会上看到它的身影,主流云服务商也不断地推出 Serverless 相关的云产品和新功能(比如 AWS Lambda、阿里云函数计算、腾讯云云函数),各种关于 Serverless 的商业和开源产品也层出不穷(比如 Serverless Framework、OpenFaaS、kubeless)。
4 |
5 | 在这个背景下,开始使用 Serverless 的产品,用它来解决实际问题,比如用 Serverless 技术实现自动化运维、开发小程序、开发服务端应用。
6 |
7 | 但是我们是否思考过 Serverless为什么这么火呢,换句话说,Serverless 架构兴起的必然因素是什么?
8 |
9 | 在说到Serverless之前,就不得不说云计算,因为云计算的发展史就是 Serverless 的兴起史。
10 |
11 | 纵观云计算的历史,我们可以将其分为物理机时代、虚拟机时代、容器时代、Serverless 时代,所以接下来,就让我们深入云计算的发展史,去寻找 Serverless 架构兴起的必然因素。
12 |
13 | #### 物理机时代
14 |
15 |
16 |
17 |
18 |
19 | 但是对于云计算的概念其实可以追溯到 60 多年前,最早的说法是图中的“分时操作系统”,也就是通过时间片轮转的方式把一个操作系统给多个用户使用。同时还在发展的是虚拟化技术,也就是把一台物理机隔离为多台虚拟机,这样就能把一个硬件给多个用户使用。
20 |
21 | 1997年,Ramnath Chellapa 教授把云计算定义成“一种新的计算范式,其中计算的边界将由经济原理决定,而不仅仅是技术限制”,通俗来讲就是,云计算不只是虚拟化技术,还是云服务商提供计算资源,使用者购买计算资源。
22 |
23 | 总的来说,在2000年之前, 互联网刚刚兴起,而云计算还处于理论阶段,也就是物理机时代。如果这个时候你想在创业,开发一个电商网站,上线就需要经过以下步骤:
24 |
25 | * 购买一台服务器(物理机)
26 |
27 | * 找个机房并给服务器通上电、连上网线.
28 |
29 | * 在服务器上安装操作系统.
30 |
31 | * 在服务器上安装数据库和网站环境.
32 |
33 | * 部署网站.
34 |
35 | * 测试.
36 |
37 | 这时你的网站架构是单机版的单体架构,数据库、应用、Nginx 等服务全都在一台你自己管理的服务器上。
38 |
39 | 网站上线之后,你将会遇到各种各样的问题,一旦停电,就会导致网络中断,服务器也会停机,那网站就没办法访问了,因此呢,用户不能再不断去买。于是为了避免断电、断网,大概率会选择把服务器托管到电信机房,那里停电的概率低很多,但是每个月得多付一些租金。
40 |
41 | 但是没想到半年后,问题又出现了,服务器 CPU 烧毁了!这不是简单换一台服务器就能解决的事情,原来服务器上的数据如何迁移?新的环境如何与原来保持一致?怎么保证网站持续可用,各种问题接连而至......
42 |
43 | 总的来说,物理机时代,网站上线和稳定运行面临的最大问题就是服务器等硬件问题,你既要购买服务器,还要承担服务器的场地、电力、网络等开销,并且还需负责服务器的维护。好在随后几年随着虚拟化技术逐渐成熟,云计算逐渐进入虚拟机时代,这也给我们带来了希望。
44 |
45 | #### 虚拟机时代
46 |
47 |
48 |
49 |
50 |
51 | 虚拟机发展历程中的重要节点。其中一个重要里程碑之一就是 2001 年 VMWare 带来的针对 x86 服务器的虚拟化产品,使虚拟化技术逐渐普及。对云厂商来说,通过虚拟化技术,它可以把一台物理机分割成多台虚拟机提供给多用户使用,充分利用硬件资源,而且创建速度和弹性也远超物理机。对于开发者来说,就不用再买硬件了,直接在云平台买虚拟机,成本更低了。
52 |
53 | 2001 年之后,虚拟化技术日渐成熟, 因此也出现了很多基于虚拟化的云厂商和产品(开篇我也提到了)。最初云厂商都是卖硬件,AWS 的 EC2、阿里云 ECS、Azure Virtual Machines,这种云计算形态也被叫作 IaaS(基础设施即服务). 后面随着业务形态发展,云厂商发现可以抽象出一些通用的平台,比如中间件、数据库等,于是就把这些功能做成服务,也放在云上去卖,这就是 PaaS(平台即服务)。
54 |
55 | 那有了 IaaS 之后,你就可以把电商网站迁移到虚拟机上了, 再也不用担心断电断网和硬件故障。不过,当你的电商网站越做越强,用户越来越多,数据库每天都有几千万条数据写入,数据库性能很快就会达到瓶颈,就会出现用户因付款太慢放弃付款的情况;除此之外,每天也有上百万图片存到磁盘,磁盘也快要耗尽了。如果网站出现崩溃,就直接导致用户流失,甚至资损。
56 |
57 | 为了降低服务器负载,我们把数据库迁移到了云厂商提供的云数据库上,把图片存储迁移到对象存储:
58 |
59 | 云数据库有专门的服务器,并且还提供了备份容灾,比自己在服务器上安装数据库更稳性能更强。对象存储能无限扩容,不用担心磁盘不够了。
60 |
61 | 这样一来,服务器就只负责处理用户的请求,把计算和存储分离开来,既降低了系统负载,也提升了数据安全性。并且单机应用升级为了集群应用,通过负载均衡,会把用户流量均匀分配到每台服务器上。
62 |
63 | 不过在服务器扩容的过程中,你还是会遇到一些麻烦。 比如购买服务器时,会发现之前服务器型号没有了,只有新的型号,并且每次新扩容一台服务器,都需要在上面初始化软件环境和配置,还需要保证所有服务器运行环境一致,这是个非常复杂还容易出错的工作。
64 |
65 | 总的来说,虚拟机可以让你不用关心底层硬件,但是如果能让我们不用关心运行环境就更好了。于是,容器技术诞生了。
66 |
67 | #### 容器时代
68 |
69 |
70 |
71 |
72 |
73 | 2013 年 Docker 的发布,代表着容器技术替代了虚拟化技术,云计算进入容器时代。 容器就是把代码和运行环境打包在一起,这样代码就可以在任何地方运行。有了容器技术,你在服务器上部署的就不再是应用了,而是容器。当容器多了的时候,如何管理就成了一个问题,于是出现了容器编排技术,比如 2014 年 Google 开源的 Kubernetes。
74 |
75 | 基于容器,你部署网站的方式也有了改变:
76 |
77 | * 搭建Kubernetes 集群;
78 |
79 | * 构建容器镜像;
80 |
81 | * 部署镜像。
82 |
83 | 我们部署架构也演进得更现代化了:
84 |
85 | 不仅使用了容器,还使用了 Kubernetes 来做管理容器集群。基于 Kubernetes 和云厂商提供的弹性能力,你可以实现网站的自动弹性伸缩。这样在流量洪峰到来时,就可以自动弹出更多的资源;当流量低谷时,自动释放多余的资源。
86 |
87 | 想到这儿,我们肯定多多少少有些兴奋,但时间一久,问题也随之出现。因为你需要去规划节点和 Pod 的 CPU、内存、磁盘等资源,需要编写复杂的 YAML 去部署 Pod、服务,需要经常排查 Pod 出现的异常,需要学习专业的运维知识,渐渐地,你好像变成了 Kubernetes 运维工程师, 如果能完全不关心运维,只专注于产品的开发就好了,这样能节省很多时间,以更快的速度完成产品迭代上线。
88 |
89 | 而且你也没想到,由于提前准备不够充分,双十一来的时候,零点的订单量远超预期,网站又崩了!集群虽然感知到了需要弹出更多的资源,但由于服务器弹出需要一定时间,没来得及应对这种瞬时流量,要是能够支持秒级弹性就好了。于是,Serverless 时代来临了。
90 |
91 | #### Serverless 时代
92 |
93 |
94 |
95 |
96 |
97 |
98 | Serverless是指构建和运行不需要服务器管理的一种概念。
99 |
100 | 对于 Serverless,目前我们得到的还是一个比较抽象的概念,这是因为这项技术尚处于发展阶段。现阶段关于 Serverless 的实现主要是基于 FaaS(函数即服务) 和 BaaS (后端即服务)的方案。
101 |
102 | * FaaS 提供了运行函数代码的能力,并且具有自动弹性伸缩。基于 FaaS,我们应用的组成就不再是集众多功能于一身的集合体,而是一个个独立的函数。每个函数实现各自的业务逻辑,由这些函数组成复杂的应用。
103 |
104 | * BaaS 是将后端能力封装成了服务,并以接口的形式提供服务。比如数据库、文件存储等。通过 BaaS 平台的接口,我们运行在 FaaS 中的函数就能调用各种后端服务,进而以更低开发成本实现复杂的业务逻辑。
105 |
106 | 说了这么多到底什么是serverless呢?
107 |
108 | 广义的Serverless:
109 |
110 | 通常来讲,广义的 Serverless 是指:构建和运行软件时不需要关心服务器的一种架构思想。 虽然 Serverless 翻译过来是 “无服务器”,但这并不代表着应用运行不需要服务器,而是开发者不需要关心服务器。而基于 Serverless 思想实现的软件架构就是 Serverless 架构。
111 |
112 | 那与 Serverless 对应的概念就是 Serverful。我们直接在虚拟机上部署网站的架构,就是 Serverful 的架构,在这种架构下,如果要保证网站持续稳定运行,就需要解决很多问题。
113 |
114 | * 备份容灾: 要实现服务器、数据库的备份容灾机制,使一台服务器出故障不影响整个系统。
115 |
116 | * 弹性伸缩: 系统能根据业务流量大 小等指标,响应式地调整服务规模,实现自动弹性伸缩。
117 |
118 | * 日志监控: 需要记录详细的日志,方便排查问题和观察系统运行情况,并且实现实时的系统监控和业务监控。
119 |
120 | 解决这些复杂的问题需要投入大量的人力、物力,小公司几乎无法自己去解决。而对开发者来说,Serverful 的架构开发成本也非常高,原本几行代码就可以搞定一个简单的业务逻辑,但你却得添加庞大的框架,比如 RPC(Remote Procedure Call,远程调用)、缓存等。
121 |
122 | Serverless 就是为了解决这些问题诞生的。 它可以把底层的硬件、存储等基础资源隐藏起来,由平台统一调度、运维。并将常用的基础技术抽象、封装(比如数据库、消息队列等)以服务的方式提供给开发者。开发者只专注于开发业务逻辑,所有业务无关的基础设施,都交给 Serverless 平台。
123 |
124 | 因此呢, Serverless 和 Serverful 的架构有这样几个区别。
125 |
126 | * 资源分配:在 Serverless 架构中,你不用关心应用运行的资源(比如服务配置、磁盘大小)只提供一份代码就行。
127 |
128 | * 计费方式:在 Serverless 架构中,计费方式按实际使用量计费(比如函数调用次数、运行时长),不按传统的执行代码所需的资源计费(比如固定 CPU)。计费粒度也精确到了毫秒级,而不是传统的小时级别。
129 |
130 | * 弹性伸缩:Serverless 架构的弹性伸缩更自动化、更精确,可以快速根据业务并发扩容更多的实例,甚至允许缩容到零实例状态来实现零费用,对用户来说是完全无感知的。而传统架构对服务器(虚拟机)进行扩容,虚拟机的启动速度也比较慢,需要几分钟甚至更久。
131 |
132 | 所以,一个应用如果是 Serverless 架构的,必须要实现自动弹性伸缩和按量付费,这也是 Serverless 的核心特点。
133 |
134 | * 狭义的 Serverless:
135 |
136 | 广义的 Serverless 更多是指一种技术理念,狭义的 Serverless 则是现阶段主流的技术实现。之所以说是狭义的,是因为 Serverless 架构正在持续发展中,未来可能有更好的技术方案。
137 |
138 | 在我看来,狭义的Serverless 是 FaaS 和 BaaS 的组合,为什么呢?
139 |
140 | 既然 Serverless架构可以让你不关心服务器,那怎么用 Serverless 架构去实现这个功能呢?答案就是 FaaS。
141 |
142 | FaaS(Function as a Service)本质上是一个函数运行平台,大多 FaaS 产品都支持 Node.js、Python、Java等编程语言,你可以选择你喜欢的编程语言编写函数并运行。函数运行时,你对底层的服务器是无感知的,FaaS 产品会负责资源的调度和运维,这是它的特点之一,不用运维。
143 |
144 | 另外,FaaS 中的函数也不是持续运行的,而是通过事件进行触发,比如 HTTP 事件、消息事件等,产生事件的源头叫触发器,FaaS 平台会集成这些触发器,我们直接用就行,这是 FaaS 的第二个特点,事件驱动。
145 |
146 | FaaS 的第三个特点是按量付费。 FaaS 产品的收费方式,都是按照函数执行次数和执行时消耗的 CPU、内存等资源进行计费的。除此之外,FaaS 在运行函数的时候,会根据并发量自动生成多个函数实例,并且并发理论是没有上限的,这是它的第四个特点,弹性伸缩。
147 |
148 | 基于 FaaS 和 BaaS 的架构,是一种计算和存储分离的架构。 计算由 FaaS 负责,存储由 BaaS 负责,计算和存储也被分开部署和收费。这使应用的存储不再是应用本身的一部分,而是演变成了独立的云服务,降低了数据丢失的风险。而应用本身也变成了无状态的应用,更容易进行调度和扩缩容。
149 |
150 | 基于 FaaS 和 BaaS ,你的应用就实现了自动弹性伸缩、按量付费、不用关心服务器,这正是 Serverless 架构的必要因素。所以说狭义的 Serverless 是 FaaS 和 BaaS 的组合。
151 |
152 | Kubernetes 本身也不是 Serverless,只是在概念方面有些类似。 Kubernetes 是一种容器编排技术。在 Kubernetes 中应用运行的基本单位是 Pod(容器组),Pod 是应用及运行环境的集合,所以你也不用关心服务器了。基于 Kubernetes,你能很方便地进行 Pod 的管理,并且实现应用的弹性伸缩。
153 |
154 | 但从运维的角度来看,主流的 Kubernetes 服务提供商,如 EKS (Amazon Elastic Kubernetes Service) 和 ACK(阿里云容器服务),提供的都是 Kubernetes 集群托管和运维服务,开发者可以方便地管理 Kubernetes 集群中硬件、存储、Pod 等资源,但上层应用的运维和调度还是需要开发者自己进行。
155 |
156 | 从成本的角度来看,Kubernetes 也无法做到按代码执行次数和实际消耗资源计费,还是和传统的 Serverful 一样,按照资源数量计费。
157 |
158 | 所以,Kubernetes 是介于 Serverful 和 Serverless 中间的产物。
159 |
160 | 而云原生指的是原生为云设计的架构模式,就是应用一开始设计开发就按照在云上运行的方式进行,充分利用云的优势。Serverless 几乎封装了所有的底层资源调度和运维工作,让你更容易使用云计算基础设施,极大简化了基于云服务的编程。
161 |
162 | 因此 Serverless 是云原生的一种实现,云原生的另一种实现是 Kubernetes。
163 |
164 |
165 | Serverless 的优缺点:
166 |
167 | 没有一项技术是十全十美的,Serverless 也一样。了解它的优缺点,可以让你今后更好地进行技术选型,决定是否用 Serverless 进行应用开发。根据 Serverless 的定义,Serverless 的优点主要有:不用运维、弹性伸缩、节省成本、开发简单、降低风险、易于扩展。 但它也存在缺点。
168 |
169 | * 依赖第三方服务
170 |
171 | 要用 Serverless,就要用云厂商提供的 Serverless 产品,比如 FaaS、BaaS,这样业务就和第三方云厂商绑定了。并且一旦你选择了一个云厂商,要想从一个云移到另一个台,成本很高(因为现在 Serverless 还没有一个统一的标准,云厂商各做各的,Serverless 产品也都不一样)。所以,依赖第三方服务是优点也是缺点。当然,我觉得这是大势所趋,让专业的人做专业的事,可以极大提高生产力。
172 |
173 | * 底层硬件的多样性
174 |
175 | 目前 Serverless 的技术实现是 FaaS 和 BaaS。你的应用代码在 FaaS 上运行,但其底层的硬件资源多样,也不确定,云厂商可以灵活地选择服务器来运行你的代码,这就让运行函数的物理环境变得不同,甚至有的函数会运行在不同代的 CPU 上。 如果代码不依赖底层 CPU,那影响可能是不同 CPU 性能有差异;如果代码必须运行在某种类型的 CPU 或 GPU 上,那就需要云厂商提供这种能力了。这其实也暴露了云厂商的目的,就是最大化平衡资源利用效率与成本。当然,如果你不是特别关注底层硬件,影响也不大。
176 |
177 | * 应用性能瓶颈
178 |
179 | 基于 Serverless 架构的应用,函数运行前需要现初始化函数运行环境,这个过程需要消耗一定时间。因为函数不是持续“在线”的,而是需要运行的时候才启动(不像传统应用,服务是一直启动的)。
180 |
181 | 从资源利用率来讲,这种模式可以节省资源,但从应用性能上来讲,这就会降低应用性能,并且还要靠云厂商实现性能优化(让延时只有几毫秒或者几十毫秒,毕竟一个接口最大的耗时是在网络上,可能长达几百毫秒)。但如果你的应用对性能非常敏感,就需要考虑一下怎么去优化应用性能了。
182 |
183 | * 函数通信效率低
184 |
185 | 传统的 MVC(Model-View-Controller) 架构模式中,View 层方法调用 Model 层方法,都是在内存中进行的。而在 Serverless 应用中,函数与函数之间就完全独立了。如果两个函数的数据有依赖,需要进行通信、交换数据,就要进行函数与函数之间的调用(调用方式是 HTTP 调用)。相比之前的内存调用,数据交互效率显然低了很多。而这个问题的本质,是 FaaS 还没有比较好的数据通信协议或方案。
186 |
187 | 开发调试复杂
188 |
189 | Serverless 架构正处于飞速发展的阶段,其开发、调试、部署工具链并不完善(基本是每个云厂商各玩各的)。 另外,应用依赖的第三方云服务也很难进行调试。要想在本地开发调试 Serverless 应用,还是比较复杂。
190 |
191 | 但是,虽然 Serverelss 存在缺点,但是相信伴随着技术的不断成熟,这些问题在未来都能得到很好的解决,Serverless的应用面也会越来越广阔!
192 |
--------------------------------------------------------------------------------
/src/chapter20/golang.01.md:
--------------------------------------------------------------------------------
1 | #### QUIC网络传输协议
2 |
3 | QUIC 全称 quick udp internet connection,“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 Google 提出的基于 UDP 进行可靠传输的协议。QUIC 在应用层实现了丢包恢复、拥塞控制、滑窗机制等保证数据传输的可靠性,同时对传输的数据具备前向安全的加密能力。HTTP3 则是 IETF(互联网工程任务组)基于 QUIC 协议基础进行设计的新一代 HTTP 协议。
4 |
5 | QUIC/HTTP3 分层模型及与 HTTP2 对比:
6 |
7 |
8 |
9 |
10 |
11 | #### QUIC 核心优势是什么?
12 |
13 | 1.0-RTT 建立连接
14 |
15 | QUIC 基于的 UDP 协议本身无需握手,并且它早于 `TLS 1.3` 协议,就实现了自己的 0-RTT 加密握手。下图分别代表了 1-RTT 握手(首次建连),成功的 0-RTT 握手,以及失败回退的握手。
16 |
17 |
18 |
19 |
20 |
21 | 2.无队头阻塞的多路复用
22 |
23 | 相比于 HTTP/2 的多路复用,QUIC 不会受到队头阻塞的影响,各个流更独立,多路复用的效果也更好。
24 |
25 |
26 |
27 |
28 |
29 | 3.连接迁移
30 |
31 | 跟TCP用四元组标识一个唯一连接不同,QUIC 使用一个 64 位的 ConnectionID 来标识连接,基于这个特点,QUIC 的使用连接迁移机制,在四元组发生变化时(比如客户端从 WIFI 切换到蜂窝网络),尝试“保留”先前的连接,从而维持数据传输不中断。
32 |
33 |
34 |
35 |
36 |
37 |
38 | 4.全应用态协议栈
39 |
40 | QUIC核心逻辑都在用户态,能灵活的修改连接参数、替换拥塞算法、更改传输行为。而TCP核心实现在内核态,改造需要修改内核并且进行系统重启,成本极高。
41 |
42 | #### QUIC 网络协议栈的选型
43 |
44 | 虽然 QUIC 各个特性看上去很美好,但需要客户端或者服务端的网络协议栈都支持 QUIC 协议。截止目前,除 `iOS15` 在指定接口 `NSURLSession` 及限制条件前提下,支持了 HTTP3,其他系统及主流网络库均不支持 QUIC。
45 |
46 |
47 |
48 |
49 |
50 | 如何让业务快速将QUIC协议用起来,用这些先性加速网络性能? QUIC 协议栈的实现成本非常高,主要体现在两方面:
51 |
52 | * 实现复杂度很高,如上面介绍,QUIC/HTTP3 横跨传输层、安全、应用层,相当于要把`TCP+TLS+HTTP` 重新实现一次。
53 |
54 | * QUIC 一直保持着高速发展,分为 gQUIC(Google QUIC)、iQUIC(IETF-QUIC)两大类,衍生的 QUIC 子版本有几十个。
55 |
56 | 为了快速把 QUIC 协议落地,给业务提升网络性能,我们选择了开源的 Chromium `cronet` 网络协议栈作为基础。Chomium,作为占领全球浏览器绝对地位的 Chrome 的开源代码,有 Google 强大的研发团队支撑,其网络协议栈是一个相对独立的组件,被称为 Cronet。
57 |
58 | * 协议栈完整性:完善的 QUIC 协议栈,还包括 `HTTP2`,`WEBSOCKET`, `FTP`,`SOCKS` 协议;
59 | * QUIC 版本支持:支持 gQUIC 和 iQUIC,并且还在不断保持更新;
60 | * 跨平台性:非常好,基于 chrome 的跨平台能力,对于各类操作系统、终端都有适配。
61 |
62 | #### QUIC 协议栈的工程实践
63 |
64 | Cronet 能直接用起来吗?结合我们的实践与业务同学的反馈,直接使用的问题和接入困难度是比较大的。
65 |
66 | 问题一:代码体积过大,逻辑层级多,不利于集成和安装包体积控制(移动端)
67 |
68 | Cronet 核心及关联的第三方库代码有大概 85w 行,涉及 2800 多个类。但其实 Cronet 里大部分代码都与 QUIC 没有关系,由于其作为浏览器的网络协议栈,集成了大量浏览器行为逻辑,而这些能力对于网络协议栈是不需要的。
69 |
70 | 其次,QUIC 协议只是 Cronet 里众多通信协议之一,除 QUIC 外的其他协议,通用的平台或软件(例如 Nginx)本身就已经有实现,没有必要重复建设,这些协议的存在除了增加协议栈内部逻辑复杂度,还增大了整个库的体积,例如在安卓平台上,cronet 动态库的体积接近 3MB,这对于一些体积敏感的应用是一个巨大的挑战。
71 |
72 | 针对体积问题,我们进行了代码精简和 lib 体积缩减的探究。
73 |
74 | 第一步,分析归纳:
75 |
76 | * 通过对 cronet 代码的分析和理解,冗余的代码被我们分成了三种:
77 |
78 | * 无用的内部逻辑,例如 HTTP 模块里包含了很多浏览器才会用到的代码和功能;
79 |
80 | * 无需用到的的协议,例如 FTP、Websocket等;
81 |
82 | * 与quic无关的功能模块,例如tcp连接池等。
83 |
84 | 第二步,代码裁剪:
85 |
86 | 针对分析归纳中的三种问题,我们做了针对性的裁剪。首先是精简了关键类,例如协议管控的类中,核心流程步骤被从 21 步压缩到了 5 步,函数数量从 146 个减少到 24 个,将浏览器相关的冗余逻辑去除。
87 |
88 | 接着对用不到的协议类型、模块组件做了剔除。裁剪后的效果如下:
89 |
90 |
91 |
92 |
93 |
94 | #### QUIC协议栈的易用性
95 |
96 | 虽然在工程方向,解决了体积大小和编译集成的问题。实际接入使用时,Cronet 的易用性依然不够好。
97 |
98 |
99 |
100 |
101 |
102 | 在通道直连方面,我们将底层 udp socket 粒度的接口进行封装后直接对外可见,用户可通过 socket 粒度的接口直接发起 IP 直连的 QUIC 请求,同时也保留了 DNS 建连能力,在保持原生能力的同时,拓展了用户的使用场景。
103 |
104 | 在网络参数配置和性能数据打点能力上,我们深入协议栈细节,逐个分析了多个核心模块,将关键的参数和性能数据抽象出来。并且在控制面上将配置参数、性能打点整合对外呈现。
105 |
106 |
107 |
108 |
109 |
110 | #### 私有协议和明文传输
111 |
112 | 在 Cronet 中,要想使用 QUIC协议,应用层传输的报文必须是 HTTP,也就是所谓的 HTTP3 协议。但 HTTP 报文对于游戏、音视频等业务是个巨大的阻碍,它们当前都是通过 TCP 或者 UDP 传输自定义的协议的,如果为了接入 QUIC 而把应用层数据从私有协议强行改为 HTTP3,无疑是本末倒置。另外,由于是自定义协议,这些报文一般不需要 QUIC 进行加密,但加密是 QUIC 协议的标配,这会消耗额外的性能。
113 |
114 | 为此,在仔细研究了 Quic 核心代码后,研发对私有协议、明文传输的支持,来满足业务传输自定义协议的需求。首先是在 QuicStream 中,允许 stream 直接收发数据报文,HTTP 流程只是其中一个选择。
115 |
116 |
117 |
118 |
119 |
120 | 为了实现明文传输,如果直接去改加解密流程,对代码的入侵较大,如果考虑不周容易引入未知风险。为了尽量较少代码入侵以及维护原生实现的安全运行,我们将 QuicFramer 中的加解密套件选择处进行了 hook,引入了 FakeEncrypt或者FakeDecrypt 替换真实的加解密套件,以极小的入侵代价低成本的实现了明文传输。
121 |
122 |
123 |
124 |
125 |
126 | 在做完明文传输方案后,我们意识到由于这是一个非常底层的修改,对于客户端和服务端来需要高度一致的,要么双端都选择加密,要么双端都选择明文。如果双端不统一,则握手就会失败。为了使兼容性更好,减少运维成本和失败风险,我们在握手协商过程中,加入了明文传输的协商。
127 |
128 | 如下图流程,当前的握手过程,使用了AEAD这个 tag 标识了待协商的加密算法。
129 |
130 |
131 |
132 |
133 |
134 | 改进后,AEAD 可以携带明文的加密算法,客户端如果也认可,则在下一次 CHLO 中选择该算法,则之后两边都进行明文传输。
135 |
136 | #### 弱网优化之实时传输
137 |
138 | 实时传输是 QUIC 的一个拓展功能,目前在 IETF 草稿阶段。实时传输适用于对数据可靠性要求不高,但非常注重数据实时性的业务。例如音视频传输、互动游戏等。实时传输在 QUIC 中的定位,以及与可靠传输的区别如下:
139 |
140 | * 相同点:
141 |
142 | 1. 在 QUIC 连接建立、创建 QUIC 数据包、数据加解密这些基础功能,不可靠数据与可靠数据都是共用的。
143 |
144 | 2. 不可靠传输也有拥塞控制、ACK机制,与可靠传输一致。
145 |
146 | * 不同点:
147 |
148 | 1. 不可靠数据不受滑动窗口限制,滑窗窗口满只限制可靠数据传输。
149 |
150 | 2. 发生丢包重传时,只重传可靠数据帧,不可靠数据帧不进行重传。
151 |
152 | 不可靠数据没有 quic stream概念,只是frame 粒度。这其中,一个关键点在于数据是否重传,IETF 草稿的定义对这块比较开放,可以完全不重传,也可以选择性重传。
153 |
154 | 为此, QUIC 在实现实时传输时,做了灵活的改造,对于实时传输的数据,提供多种重传策略供使用者选择,可以完全不重传,也可以选择性重传某个重要的数据(比如关键帧),我们也在尝试做动态重传控制,依托我们的弱网判断模型,动态调整重传策略。
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/images/1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/1.jpeg
--------------------------------------------------------------------------------
/src/images/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/1.jpg
--------------------------------------------------------------------------------
/src/images/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/10.jpg
--------------------------------------------------------------------------------
/src/images/100.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/100.jpg
--------------------------------------------------------------------------------
/src/images/101.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/101.jpg
--------------------------------------------------------------------------------
/src/images/102.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/102.jpg
--------------------------------------------------------------------------------
/src/images/103.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/103.jpg
--------------------------------------------------------------------------------
/src/images/104.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/104.jpg
--------------------------------------------------------------------------------
/src/images/105.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/105.jpg
--------------------------------------------------------------------------------
/src/images/106.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/106.jpg
--------------------------------------------------------------------------------
/src/images/107.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/107.jpg
--------------------------------------------------------------------------------
/src/images/108.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/108.jpg
--------------------------------------------------------------------------------
/src/images/109.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/109.jpg
--------------------------------------------------------------------------------
/src/images/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/11.jpg
--------------------------------------------------------------------------------
/src/images/110.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/110.jpg
--------------------------------------------------------------------------------
/src/images/111.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/111.jpg
--------------------------------------------------------------------------------
/src/images/112.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/112.jpg
--------------------------------------------------------------------------------
/src/images/114.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/114.jpg
--------------------------------------------------------------------------------
/src/images/115.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/115.jpg
--------------------------------------------------------------------------------
/src/images/116.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/116.jpg
--------------------------------------------------------------------------------
/src/images/117.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/117.jpg
--------------------------------------------------------------------------------
/src/images/118.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/118.jpg
--------------------------------------------------------------------------------
/src/images/119.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/119.jpg
--------------------------------------------------------------------------------
/src/images/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/12.jpg
--------------------------------------------------------------------------------
/src/images/120.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/120.jpg
--------------------------------------------------------------------------------
/src/images/121.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/121.jpg
--------------------------------------------------------------------------------
/src/images/122.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/122.jpg
--------------------------------------------------------------------------------
/src/images/123.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/123.jpg
--------------------------------------------------------------------------------
/src/images/124.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/124.jpg
--------------------------------------------------------------------------------
/src/images/125.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/125.jpg
--------------------------------------------------------------------------------
/src/images/126.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/126.jpg
--------------------------------------------------------------------------------
/src/images/127.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/127.jpg
--------------------------------------------------------------------------------
/src/images/128.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/128.jpg
--------------------------------------------------------------------------------
/src/images/129.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/129.jpg
--------------------------------------------------------------------------------
/src/images/13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/13.jpg
--------------------------------------------------------------------------------
/src/images/130.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/130.jpg
--------------------------------------------------------------------------------
/src/images/131.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/131.jpg
--------------------------------------------------------------------------------
/src/images/132.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/132.jpg
--------------------------------------------------------------------------------
/src/images/133.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/133.jpg
--------------------------------------------------------------------------------
/src/images/134.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/134.jpg
--------------------------------------------------------------------------------
/src/images/135.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/135.jpg
--------------------------------------------------------------------------------
/src/images/136.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/136.jpg
--------------------------------------------------------------------------------
/src/images/137.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/137.jpg
--------------------------------------------------------------------------------
/src/images/138.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/138.jpg
--------------------------------------------------------------------------------
/src/images/139.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/139.jpg
--------------------------------------------------------------------------------
/src/images/14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/14.jpg
--------------------------------------------------------------------------------
/src/images/140.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/140.jpg
--------------------------------------------------------------------------------
/src/images/141.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/141.jpg
--------------------------------------------------------------------------------
/src/images/142.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/142.jpg
--------------------------------------------------------------------------------
/src/images/143.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/143.jpg
--------------------------------------------------------------------------------
/src/images/144.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/144.jpg
--------------------------------------------------------------------------------
/src/images/145.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/145.jpg
--------------------------------------------------------------------------------
/src/images/146.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/146.jpg
--------------------------------------------------------------------------------
/src/images/147.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/147.jpg
--------------------------------------------------------------------------------
/src/images/148.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/148.jpg
--------------------------------------------------------------------------------
/src/images/149.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/149.jpg
--------------------------------------------------------------------------------
/src/images/15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/15.jpg
--------------------------------------------------------------------------------
/src/images/150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/150.jpg
--------------------------------------------------------------------------------
/src/images/151.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/151.jpg
--------------------------------------------------------------------------------
/src/images/152.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/152.jpg
--------------------------------------------------------------------------------
/src/images/153.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/153.jpg
--------------------------------------------------------------------------------
/src/images/154.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/154.jpg
--------------------------------------------------------------------------------
/src/images/155.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/155.jpg
--------------------------------------------------------------------------------
/src/images/156.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/156.jpg
--------------------------------------------------------------------------------
/src/images/157.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/157.jpg
--------------------------------------------------------------------------------
/src/images/158.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/158.jpg
--------------------------------------------------------------------------------
/src/images/159.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/159.jpg
--------------------------------------------------------------------------------
/src/images/16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/16.jpg
--------------------------------------------------------------------------------
/src/images/160.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/160.jpg
--------------------------------------------------------------------------------
/src/images/161.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/161.jpg
--------------------------------------------------------------------------------
/src/images/162.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/162.jpg
--------------------------------------------------------------------------------
/src/images/163.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/163.jpg
--------------------------------------------------------------------------------
/src/images/164.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/164.jpg
--------------------------------------------------------------------------------
/src/images/165.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/165.jpg
--------------------------------------------------------------------------------
/src/images/166.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/166.jpg
--------------------------------------------------------------------------------
/src/images/167.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/167.jpg
--------------------------------------------------------------------------------
/src/images/168.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/168.jpg
--------------------------------------------------------------------------------
/src/images/169.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/169.jpg
--------------------------------------------------------------------------------
/src/images/17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/17.jpg
--------------------------------------------------------------------------------
/src/images/170.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/170.jpg
--------------------------------------------------------------------------------
/src/images/171.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/171.jpg
--------------------------------------------------------------------------------
/src/images/172.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/172.jpg
--------------------------------------------------------------------------------
/src/images/173.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/173.jpg
--------------------------------------------------------------------------------
/src/images/174.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/174.jpg
--------------------------------------------------------------------------------
/src/images/175.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/175.jpg
--------------------------------------------------------------------------------
/src/images/176.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/176.jpg
--------------------------------------------------------------------------------
/src/images/177.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/177.jpg
--------------------------------------------------------------------------------
/src/images/178.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/178.jpg
--------------------------------------------------------------------------------
/src/images/179.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/179.jpg
--------------------------------------------------------------------------------
/src/images/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/18.jpg
--------------------------------------------------------------------------------
/src/images/180.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/180.jpg
--------------------------------------------------------------------------------
/src/images/181.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/181.jpg
--------------------------------------------------------------------------------
/src/images/182.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/182.jpg
--------------------------------------------------------------------------------
/src/images/183.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/183.jpg
--------------------------------------------------------------------------------
/src/images/184.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/184.jpg
--------------------------------------------------------------------------------
/src/images/185.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/185.jpg
--------------------------------------------------------------------------------
/src/images/186.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/186.jpg
--------------------------------------------------------------------------------
/src/images/187.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/187.jpg
--------------------------------------------------------------------------------
/src/images/188.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/188.jpg
--------------------------------------------------------------------------------
/src/images/189.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/189.jpg
--------------------------------------------------------------------------------
/src/images/19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/19.jpg
--------------------------------------------------------------------------------
/src/images/190.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/190.jpg
--------------------------------------------------------------------------------
/src/images/191.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/191.jpg
--------------------------------------------------------------------------------
/src/images/192.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/192.jpg
--------------------------------------------------------------------------------
/src/images/193.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/193.jpg
--------------------------------------------------------------------------------
/src/images/194.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/194.jpg
--------------------------------------------------------------------------------
/src/images/195.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/195.jpg
--------------------------------------------------------------------------------
/src/images/196.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/196.jpg
--------------------------------------------------------------------------------
/src/images/197.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/197.jpg
--------------------------------------------------------------------------------
/src/images/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/2.jpg
--------------------------------------------------------------------------------
/src/images/20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/20.jpg
--------------------------------------------------------------------------------
/src/images/21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/21.jpg
--------------------------------------------------------------------------------
/src/images/22.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/22.jpg
--------------------------------------------------------------------------------
/src/images/23.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/23.jpg
--------------------------------------------------------------------------------
/src/images/24.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/24.jpg
--------------------------------------------------------------------------------
/src/images/25.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/25.jpg
--------------------------------------------------------------------------------
/src/images/26.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/26.jpg
--------------------------------------------------------------------------------
/src/images/27.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/27.jpg
--------------------------------------------------------------------------------
/src/images/28.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/28.jpg
--------------------------------------------------------------------------------
/src/images/29.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/29.jpg
--------------------------------------------------------------------------------
/src/images/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/3.jpg
--------------------------------------------------------------------------------
/src/images/30.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/30.jpg
--------------------------------------------------------------------------------
/src/images/31.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/31.jpg
--------------------------------------------------------------------------------
/src/images/32.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/32.jpg
--------------------------------------------------------------------------------
/src/images/33.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/33.jpg
--------------------------------------------------------------------------------
/src/images/34.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/34.jpg
--------------------------------------------------------------------------------
/src/images/35.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/35.jpg
--------------------------------------------------------------------------------
/src/images/36.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/36.jpg
--------------------------------------------------------------------------------
/src/images/37.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/37.jpg
--------------------------------------------------------------------------------
/src/images/38.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/38.jpg
--------------------------------------------------------------------------------
/src/images/39.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/39.jpg
--------------------------------------------------------------------------------
/src/images/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/4.jpg
--------------------------------------------------------------------------------
/src/images/40.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/40.jpg
--------------------------------------------------------------------------------
/src/images/41.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/41.jpg
--------------------------------------------------------------------------------
/src/images/42.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/42.jpg
--------------------------------------------------------------------------------
/src/images/43.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/43.jpg
--------------------------------------------------------------------------------
/src/images/44.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/44.jpg
--------------------------------------------------------------------------------
/src/images/45.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/45.jpg
--------------------------------------------------------------------------------
/src/images/46.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/46.jpg
--------------------------------------------------------------------------------
/src/images/47.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/47.jpg
--------------------------------------------------------------------------------
/src/images/48.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/48.jpg
--------------------------------------------------------------------------------
/src/images/49.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/49.jpg
--------------------------------------------------------------------------------
/src/images/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/5.jpg
--------------------------------------------------------------------------------
/src/images/50.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/50.jpg
--------------------------------------------------------------------------------
/src/images/51.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/51.jpg
--------------------------------------------------------------------------------
/src/images/52.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/52.jpg
--------------------------------------------------------------------------------
/src/images/53.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/53.jpg
--------------------------------------------------------------------------------
/src/images/54.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/54.jpg
--------------------------------------------------------------------------------
/src/images/55.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/55.jpg
--------------------------------------------------------------------------------
/src/images/56.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/56.jpg
--------------------------------------------------------------------------------
/src/images/57.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/57.jpg
--------------------------------------------------------------------------------
/src/images/58.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/58.jpg
--------------------------------------------------------------------------------
/src/images/59.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/59.jpg
--------------------------------------------------------------------------------
/src/images/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/6.jpg
--------------------------------------------------------------------------------
/src/images/60.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/60.jpg
--------------------------------------------------------------------------------
/src/images/61.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/61.jpg
--------------------------------------------------------------------------------
/src/images/62.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/62.jpg
--------------------------------------------------------------------------------
/src/images/63.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/63.jpg
--------------------------------------------------------------------------------
/src/images/64.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/64.jpg
--------------------------------------------------------------------------------
/src/images/65.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/65.jpg
--------------------------------------------------------------------------------
/src/images/66.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/66.jpg
--------------------------------------------------------------------------------
/src/images/67.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/67.jpg
--------------------------------------------------------------------------------
/src/images/68.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/68.jpg
--------------------------------------------------------------------------------
/src/images/69.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/69.jpg
--------------------------------------------------------------------------------
/src/images/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/7.jpg
--------------------------------------------------------------------------------
/src/images/70.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/70.jpg
--------------------------------------------------------------------------------
/src/images/71.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/71.jpg
--------------------------------------------------------------------------------
/src/images/72.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/72.jpg
--------------------------------------------------------------------------------
/src/images/73.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/73.jpg
--------------------------------------------------------------------------------
/src/images/74.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/74.jpg
--------------------------------------------------------------------------------
/src/images/75.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/75.jpg
--------------------------------------------------------------------------------
/src/images/76.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/76.jpg
--------------------------------------------------------------------------------
/src/images/77.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/77.jpg
--------------------------------------------------------------------------------
/src/images/78.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/78.jpg
--------------------------------------------------------------------------------
/src/images/79.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/79.jpg
--------------------------------------------------------------------------------
/src/images/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/8.jpg
--------------------------------------------------------------------------------
/src/images/80.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/80.jpg
--------------------------------------------------------------------------------
/src/images/81.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/81.jpg
--------------------------------------------------------------------------------
/src/images/82.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/82.jpg
--------------------------------------------------------------------------------
/src/images/83.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/83.jpg
--------------------------------------------------------------------------------
/src/images/84.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/84.jpg
--------------------------------------------------------------------------------
/src/images/85.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/85.jpg
--------------------------------------------------------------------------------
/src/images/86.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/86.jpg
--------------------------------------------------------------------------------
/src/images/87.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/87.jpg
--------------------------------------------------------------------------------
/src/images/88.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/88.jpg
--------------------------------------------------------------------------------
/src/images/89.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/89.jpg
--------------------------------------------------------------------------------
/src/images/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/9.jpg
--------------------------------------------------------------------------------
/src/images/90.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/90.jpg
--------------------------------------------------------------------------------
/src/images/91.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/91.jpg
--------------------------------------------------------------------------------
/src/images/92.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/92.jpg
--------------------------------------------------------------------------------
/src/images/93.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/93.jpg
--------------------------------------------------------------------------------
/src/images/94.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/94.jpg
--------------------------------------------------------------------------------
/src/images/95.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/95.jpg
--------------------------------------------------------------------------------
/src/images/96.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/96.jpg
--------------------------------------------------------------------------------
/src/images/97.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/97.jpg
--------------------------------------------------------------------------------
/src/images/98.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/98.jpg
--------------------------------------------------------------------------------
/src/images/99.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeKe-Li/data-structures-questions/904210f6f195736eded51931e5730cbb7d8a976d/src/images/99.jpg
--------------------------------------------------------------------------------