-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.xml
316 lines (271 loc) · 37.4 KB
/
index.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>绝知</title>
<link>https://www.yici.xin/</link>
<description>Recent content on 绝知</description>
<image>
<url>https://www.yici.xin/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
<link>https://www.yici.xin/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
</image>
<generator>Hugo -- gohugo.io</generator>
<language>zh</language>
<lastBuildDate>Tue, 05 Mar 2024 23:32:45 +0800</lastBuildDate><atom:link href="https://www.yici.xin/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>光栅化直线绘制算法</title>
<link>https://www.yici.xin/post/tech/%E5%85%89%E6%A0%85%E5%8C%96%E7%9B%B4%E7%BA%BF%E7%BB%98%E5%88%B6%E7%AE%97%E6%B3%95/</link>
<pubDate>Thu, 22 Feb 2024 00:28:31 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/%E5%85%89%E6%A0%85%E5%8C%96%E7%9B%B4%E7%BA%BF%E7%BB%98%E5%88%B6%E7%AE%97%E6%B3%95/</guid>
<description>光栅扫描显示器,如LCD显示器、LED屏幕,是由大量的小像素组成的矩阵,是一种画点的设备。由于屏幕是由离散的像素组成的,而直线是连续的,所以在绘制过程中需要某种形式的近似,以确定哪些像素最接近理想直线路径。直线光栅化算法的目标,就是根据给定的起点终点,计算出最靠近理想直线的像素点集。
直线绘制算法在计算机图形学中是非常基础的,它们是图形渲染的核心技术之一,所以算法应追求最极致的性能和效率,通常都会追求以下两点:
为了硬件友好性,通常都会避免使用浮点数和除法,尽量使用硬件加法完成所有的计算。
算法一般都会使用递推的形式,目的是每次计算新的像素点时,可以复用上一次计算的结果。
通常,算法会根据$|∆x|$和$|∆y|$的大小比较,选择一个递进方向,称为主方向,在主方向上,每次做$\pm1$操作,另一方向可能不变,也可能$\pm1$,这取决于误差项。
举个简单的算法例子,起点是(0,0),终点是(9,3),即理想直线是$y= \dfrac 1 3 x $ ,此时$|∆x|$&gt;$|∆y|$,所以x轴方向是主方向,当我们绘制完起点像素(0,0)后,下一个像素点可以确定是$(1,?)$,具体是$p_0(1,0)$还是$p_1(1,1)$,这取决于哪个像素点与理想直线$y= \dfrac 1 3 x $的距离更近,我们可以计算出理想直线此时的点是$(1,\dfrac 1 3)$, 此时$p_0(1,0)$会更近,所以选择$p_0$像素点,即另一方向上不变,这个简单的算法没有满足上面提到的两点追求,我们使用了浮点数计算,且当前一次的计算对下一次计算没有任何帮助,看看常见的那些光栅化直线绘制算法是怎么做的
DDA算法 基本原理 DDA(Digital Differential analyzer),即数值微分法,是用数值方法求解微分方程的一种算法。
直线用斜截式表示为 $$ y = kx+b $$ 其中的斜率k为$k=\dfrac {∆x} {∆y}= \dfrac {y_1-y_0} {x_1-x_0}$
第一个八分象限内的DDA算法 通用的DDA算法 不足之处 Bresenham算法 介绍两种理解方式,其原理是一样的,但推导方式略有不同
基本原理——形式一 基本原理——形式二 中点算法 </description>
</item>
<item>
<title>Golang Sql源码解析</title>
<link>https://www.yici.xin/post/tech/golang-sql%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</link>
<pubDate>Mon, 05 Feb 2024 09:43:27 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang-sql%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</guid>
<description>环境信息:go1.21.5 amd64
抽象定义 database/sql库是go标准库的一部分,它本身不提供数据库驱动,而是定义了一套轻量级的接口来与SQL或类SQL的数据库进行交互,由如下核心接口组成:
<!DOCTYPE HTML> Connector Connector是连接器的抽象,用于获取连接
// /src/database/sql/driver/driver.go:121 // 一个Connector代表一个固定配置的驱动,能够创建任意数量的、等价的连接给多个协程使用 // // Connector可以传递给sql.OpenDB函数,让每种数据库实现各自的*sql.DB // // 如果Connector实现了io.Closer,会在sql.DB的Close方法最后调用Close() type Connector interface { // Connect 返回一个数据库连接 // sql库有空闲连接池机制,用于连接重用 // // context仅用于拨号目的(见 net.DialContext),不应存储或用于其他目的。 // 一个默认的超时时间是必要的,因为连接池可能会异步调用Connect方法来处理查询 // // 返回的连接同一时刻只能给一个goroutine使用 Connect(context.Context) (Conn, error) // Driver方法返回Connector的底层Driver,主要是为了与sql.DB上的Driver方法保持兼容。 Driver() Driver } Driver 驱动 // Driver是数据库驱动程序必须实现的接口。 // 数据库驱动程序可以实现DriverContext来访问上下文,并且只需对连接池中的连接解析一次名称,而不是每个连接都解析一次。 // /src/database/sql/driver/driver.go:84 type Driver interface { // Open 返回一个新的数据库连接 // 该名称是一个特定驱动程序格式的字符串。如username:passpord@(ip:port)/database Open(name string) (Conn, error) } Conn 连接 Conn是连接的抽象</description>
</item>
<item>
<title>Rust动态数组Vector</title>
<link>https://www.yici.xin/post/tech/rust%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84vector/</link>
<pubDate>Tue, 16 May 2023 21:26:57 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/rust%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84vector/</guid>
<description>在Rust中,动态数组被称为&quot;Vec&quot;或&quot;Vector&quot;。Vector是一种在堆上分配内存的集合类型,允许你存储多个值。它的大小是动态的,可以根据需要进行增减。
创建Vector 创建rust动态数组有好几种方式
New 预先设置容量 vec!宏 let v: Vec&lt;i32&gt; = Vec::new(); let v: Vec&lt;i32&gt; = Vec::with_capacity(capacity) let v = vec![1, 2, 3]; 读取元素 Vec读取指定位置的元素有两种方式
使用下标 使用get 下标索引 get fn main() { let v = vec![1, 2, 3, 4, 5]; let third: &amp;i32 = &amp;v[2]; println!(&#34;第三个元素是 {}&#34;, third); } fn main() { let v = vec![1, 2, 3, 4, 5]; match v.get(2) { Some(third) =&gt; println!(&#34;第三个元素是 {third}&#34;), None =&gt; println!(&#34;去你的第三个元素,根本没有!&#34;), } } 和其它语言一样,集合类型的索引下标都是从 0 开始,&amp;v[2] 表示借用 v 中的第三个元素,最终会获得该元素的引用。而 v.</description>
</item>
<item>
<title>Http2.0和3.0</title>
<link>https://www.yici.xin/post/tech/http2.0%E5%92%8C3.0/</link>
<pubDate>Fri, 24 Mar 2023 22:40:52 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/http2.0%E5%92%8C3.0/</guid>
<description>HTTP2.0 HTTP/2 是 HTTP/1.1 的替代者,它的主要目的是改进网络性能,提升用户体验。与 HTTP/1.1 相比,HTTP/2 具有以下几个主要优点:
多路复用 服务端推送 二进制分帧 流量控制 多路复用 多路复用:在 HTTP/1.1 中,每次请求需要建立一个新的连接或者等待前一个请求完成。这样会造成大量的连接建立和关闭,降低了效率。HTTP/2 允许在同一个连接上并行传输多个请求和响应,从而大大提高了效率。
这解决了一部分的HTTP1.0队头阻塞问题,可以假设这么一个情况,一个较大的js文件和一个较小的css文件都需要传输,但是如果js文件先于css文件,那么css文件就要等到js文件传输完之后才能进行传输,即使它比较小,其实可以更早的传输并使用。使用多路复用后,相当于发一部分js的包,发一部分css的包,这样较小的css包可以更早的传输完毕。
这在http1.0中是无法实现的,主要问题是 HTTP/1.1 是一个纯文本协议,它只在有效荷载(payload)的前面附加头(headers)。它不会进一步区分单个(大块)资源与其他资源。这是 HTTP/1.1 协议设计方式的一个基础限制。如果只有一个 HTTP/1.1 连接,那么在切换到发送新资源之前,必须完整地传输资源响应。如果前面的资源创建缓慢,可能会引起队头阻塞问题。这就是为什么浏览器开始为 HTTP/1.1 上的每个页面打开多个TCP链接(通常是6个),你也可以通过多域名配置同时打开更多的TCP链接,也就是说,在不超过6个请求并发的情况下,就不会出现队头阻塞的情况。当然,大量的TCP链接肯定会导致更大的开销,如HTTPS的TLS握手操作。
HTTP/2 的目标非常明确:能够回到单个 TCP 连接,解决队头阻塞问题。或者说:我们希望能够正确地复用资源块(resource chunks)。这在 HTTP/1.1 中是不可能的,因为没有办法分辨一个块属于哪个资源,或者它在哪里结束,另一个块从哪里开始。HTTP/2 非常优雅地解决了这一问题,它在资源块之前添加了帧(frames)。
HTTP/2 在每个块前面放置一个的数据帧(DATA frame)。这些数据帧主要包含两个关键的元数据。首先:下面的块属于哪个资源。每个资源的“字节流(bytestream)”都被分配了一个唯一的数字,即流id(stream id)。第二:块的大小是多少。因此,通过“framing”单个消息,HTTP/2 比 HTTP/1.1 更加灵活。它允许在单个 TCP 连接上通过交错排列块来多路传输多个资源。它还解决了第一个资源缓慢时的队头阻塞问题。
资源加权
在多路复用的基础上,我们还可以控制单个链接上对不同资源的带宽分布,如
公平多路复用(例如两个渐进的 JPEGs):12121212 加权多路复用(2是1的两倍):22122122121 反向顺序调度(例如2是密钥服务器推送的资源):22221111 部分调度(流1被中止且未完整发送):112222 服务端推送 HTTP/2 允许服务端主动将内容推送给客户端,这样可以提前加载网页所需的资源,从而提高网页加载的速度。这样的服务端推送可以避免客户端需要再次发送请求以获取某些资源,从而减少网络延迟。
二进制分帧 HTTP/2 使用二进制编码,并通过分帧传输。这样可以避免字符编码问题,并提高了数据传输的效率。HTTP/2 中的帧有多种类型,每种类型的帧都有自己的特定用途,例如用于数据传输、流量控制和错误处理等。
流量控制 HTTP/2 允许实现流量控制,从而避免网络阻塞。在 HTTP/1.1 中,如果一个请求占用了大量的带宽,就可能导致其他请求的响应时间变长,影响用户体验。HTTP/2 的流量控制功能允许服务端和客户端协商请求和响应的速率,从而避免阻塞。
HTTP2.0的不足 队头阻塞问题 上文提到了HTTP2.0增加了多路复用机制解决了队头阻塞问题,但是还有一个问题,事实证明,HTTP/2 只解决了 HTTP 级别的队头阻塞,可以称之为“应用层”队头阻塞。然而,在典型的网络模型中,还需要考虑下面的其他层。也就是HTTP2.0所使用的传输层协议TCP的队头阻塞问题,因为TCP的重传机制,例如TCP按顺序传输包1、2、3,当1和3到达时,2包因为某些原因丢失,此时TCP并不会提交包3给上层,而是存在缓冲区中等到2包到达后在一起提交给上层,这就导致了3包被2包所阻塞,即使3包和2包是独立关系的。总之,TCP 不知道 HTTP/2 的独立流(streams)这一事实,意味着 TCP 层队头阻塞(由于丢失或延迟的数据包)也最终导致 HTTP 队头阻塞!</description>
</item>
<item>
<title>转换bytes和string的最佳实践</title>
<link>https://www.yici.xin/post/tech/%E8%BD%AC%E6%8D%A2bytes%E5%92%8Cstring%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</link>
<pubDate>Wed, 01 Feb 2023 19:34:04 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/%E8%BD%AC%E6%8D%A2bytes%E5%92%8Cstring%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</guid>
<description>本文golang源码为1.18版本
标准转换 使用标准转换是最常见的选择
package main func main() { s := &#34;Hello, world!&#34; // string转byte数组 b := []byte(s) // byte数组转string s2 := string(b) } 该转换语句会被go编译器翻译为runtime层的方法调用,其中[]byte到string的转换对应/src/runtime/string.go:81处的slicebytetostring函数;而string到[]byte的转换对应的则是/src/runtime/string.go:172处的stringtoslicebyte函数。
先来看看比较简单的stringtoslicebyte函数,tmpBuf类型是个大小为32的byte数组。
// The constant is known to the compiler. // There is no fundamental theory behind this number. 🤣 const tmpStringBufSize = 32 type tmpBuf [tmpStringBufSize]byte func stringtoslicebyte(buf *tmpBuf, s string) []byte { var b []byte if buf != nil &amp;&amp; len(s) &lt;= len(buf) { *buf = tmpBuf{} b = buf[:len(s)] } else { // 如果没有缓冲区或缓冲区大小不足,需要申请内存 b = rawbyteslice(len(s)) } // 复制数据 copy(b, s) return b } func rawbyteslice(size int) (b []byte) { // 容量计算,考虑内存对齐,寻找大小最匹配的内存块 cap := roundupsize(uintptr(size)) // 使用mallocgc申请对应大小的内存 p := mallocgc(cap, nil, false) // 如果要申请的size和最终计算得到的cap大小不一致,cap只会比size更大,清理掉多余的内存 if cap !</description>
</item>
<item>
<title>Rust——泛型和trait</title>
<link>https://www.yici.xin/post/tech/rust%E6%B3%9B%E5%9E%8B%E5%92%8Ctrait/</link>
<pubDate>Wed, 18 Jan 2023 14:16:44 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/rust%E6%B3%9B%E5%9E%8B%E5%92%8Ctrait/</guid>
<description>泛型 函数签名中使用泛型 尝试编写一个寻找数组中最大值的函数:
fn largest_i32(list: &amp;[i32]) -&gt; &amp;i32 { let mut largest = &amp;list[0]; for item in list.iter() { if item &gt; largest { largest = item; } } largest } 该函数仅支持i32类型,如果我们需要一个char类型的largest_char函数,应该会是这样:
fn largest_char(list: &amp;[char]) -&gt; &amp;char { let mut largest = &amp;list[0]; for item in list.iter() { if item &gt; largest { largest = item; } } largest } 可以发现,我们在编写一些逻辑重复,仅类型不同的代码,为了能够支持更多的类型而不需要手写它们的largest函数,可以将这部分代码中的类型使用泛型替换。
所以泛型版本应该是这么写的:
fn largest_t&lt;T&gt;(list: &amp;[T]) -&gt; &amp;T { let mut largest = &amp;list[0]; for item in list.</description>
</item>
<item>
<title>Rust错误处理</title>
<link>https://www.yici.xin/post/tech/rust%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</link>
<pubDate>Mon, 16 Jan 2023 10:02:41 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/rust%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</guid>
<description>rust中的错误分为可恢复错误(recoverable)和 不可恢复错误(unrecoverable)。可恢复错误使用Result&lt;T,E&gt;枚举,不可恢复错误使用panic!宏。
不可恢复错误——panic!宏 当出现 panic 时,程序默认会开始 展开(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 Cargo.toml 的 [profile] 部分增加 panic = 'abort',可以由展开切换为终止。例如,如果你想要在release模式中 panic 时直接终止:
[profile.release] panic = &#39;abort&#39; panic!宏的使用 fn main() { panic!(&#34;crash and burn&#34;); } 该代码的输出是这样的: 我们仅能看到panic!时提供的内容&quot;crash and burn&quot;,和panic!的调用位置,要想看到panic!宏的完整调用栈,可以使用RUST_BACKTRACE=1 cargo run命令来执行程序。 实际的输出可能因不同的操作系统和 Rust 版本而有所不同 可恢复错误——Result&lt;T,E&gt;枚举 enum Result&lt;T, E&gt; { Ok(T), Err(E), } T 和 E 是泛型类型参数。通常处理可恢复错误时,函数将会返回一个Result,调用者可使用match进行匹配。
use std::fs::File; fn main() { let f = File::open(&#34;hello.txt&#34;); let f = match f { Ok(file) =&gt; file, // 如果成功,返回值的file赋值给f Err(error) =&gt; { panic!</description>
</item>
<item>
<title>Golang——plan9汇编</title>
<link>https://www.yici.xin/post/tech/golangplan9%E6%B1%87%E7%BC%96/</link>
<pubDate>Fri, 30 Dec 2022 10:51:52 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golangplan9%E6%B1%87%E7%BC%96/</guid>
<description>基本指令 栈相关 在 plan9 汇编中,虽然存在push、pop指令,但是在生成的代码中一般是看不到的,通常栈的这两种操作会以操作SP寄存器的位置来替换。
SUBQ $0x18, SP // 对 SP 做减法,为函数分配函数栈帧 ... // 省略无用代码 ADDQ $0x18, SP // 对 SP 做加法,清除函数栈帧 栈的方向是从内存高位置向低位置的,所以对 SP 寄存器做减法,就是给栈分配内存,加法即回收内存。
x86 汇编中 push 和 pop 指令可以接一个寄存器,如 pop ax,表示将栈顶元素放入 ax 寄存器并且栈顶寄存器自动回退,而 plan9 只是做了栈顶寄存器操作,对于赋值操作需要使用 mov 指令 😭
数据搬运 plan9 汇编中,常数以$num形式表示,可以是负数,一般为 10 进制,当然也可以用$0x123的十六进制形式。
MOVB $1, DI // 1 byte MOVW $0x10, BX // 2 bytes MOVD $1, DX // 4 bytes MOVQ $-10, AX // 8 bytes plan9 汇编中使用的通常是带有后缀的 MOV 指令,不同后缀的区别是移动的字节大小。这和 x86 的汇编不一样,在 x86 汇编中,决定移动字节大小的是寄存器。</description>
</item>
<item>
<title>Golang-map的操作</title>
<link>https://www.yici.xin/post/tech/golang-map%E7%9A%84%E6%93%8D%E4%BD%9C/</link>
<pubDate>Fri, 30 Dec 2022 10:49:41 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang-map%E7%9A%84%E6%93%8D%E4%BD%9C/</guid>
<description>创建 map 创建 map 的语法很简单
// 不指定map长度 ageMp := make(map[string]int) // 指定map长度 ageMp := make(map[string]int, 8) // ageMp 为 nil,不能向其添加元素,会直接panic var ageMp map[string]int 通过汇编语言,我们能够跟踪到 map 的创建最终会调用 runtime.makemap 方法
// makemap implements Go map creation for make(map[k]v, hint). // If the compiler has determined that the map or the first bucket // can be created on the stack, h and/or bucket may be non-nil. // If h != nil, the map can be created directly in h.</description>
</item>
<item>
<title>如何在Keynote中贴出好看的代码</title>
<link>https://www.yici.xin/post/tech/keynote%E4%B8%AD%E8%B4%B4%E4%BB%A3%E7%A0%81/</link>
<pubDate>Tue, 27 Dec 2022 18:30:01 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/keynote%E4%B8%AD%E8%B4%B4%E4%BB%A3%E7%A0%81/</guid>
<description>brew install highlight 需要安装highlight工具。
用法:
转化剪贴板中的代码:
pbpaste | highlight --syntax=go --style=github -k &#34;Fira Code&#34; -K 36 -u &#34;utf-8&#34; -t 4 -O rtf | pbcopy --syntax指定代码语法格式,-u指定编码,否则中文会乱码,--style指定高亮的样式,-K指定代码的字大小,rtf是要转出的格式,还可以是其他的如html。
转化代码文件:
highlight --style=github -k &#34;Fira Code&#34; -K 36 -u &#34;utf-8&#34; -t 4 -O rtf hello.go | pbcopy </description>
</item>
<item>
<title>Golang读写锁</title>
<link>https://www.yici.xin/post/tech/golang%E8%AF%BB%E5%86%99%E9%94%81/</link>
<pubDate>Tue, 27 Dec 2022 18:26:23 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang%E8%AF%BB%E5%86%99%E9%94%81/</guid>
<description>之前写过关于互斥锁的内容, Golang互斥锁-Mutex 日期: 2022-12-27 &nbsp; 标签: #golang&nbsp; #锁&nbsp; #源码解析&nbsp; 互斥锁结构 // A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 } state表示当前互斥锁的状态, sema是用于控制锁状态的信号量 在默认情况下,互斥锁的所有状态位都是 0,int32 中的不同位分别表示了不同的状态: mutexLocked — 表示互斥锁的锁定状态; mutexWoken — 唤醒模式,此时释放锁的g不会唤醒休眠的g; mutexStarving — 当前的互斥锁进入饥饿状态; waitersCount — 当前互斥锁上等待的 Goroutine 个数; 正常模式和饥饿模式的区别: 在正常模式下,锁的等待者会按照先进先出的顺序获取锁。但是刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁,为了减少这种情况的出现,一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被『饿死』。 .</description>
</item>
<item>
<title>Golang内存回收</title>
<link>https://www.yici.xin/post/tech/golang%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6/</link>
<pubDate>Tue, 27 Dec 2022 18:26:23 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6/</guid>
<description>GC源码分布: 流程 文件 标记准备 runtime/mgc.go 调步策略 runtime/mgcpacer.go 并发标记 runtime/mgcmark.go 清扫流程 runtime/msweep.go 位图标识 runtime/mbitmap.go 触发屏障 runtime/mbwbuf.go 内存回收 runtime/mgcscavenge.go GC触发链路: 触发 GC 的事件类型可以分为如下三种:
类型 触发事件 校验条件 gcTriggerHeap 分配对象时触发 堆已分配内存达到阈值 gcTriggerTime 由 forcegchelper 守护协程定时触发 每2分钟触发一次 gcTriggerCycle 用户调用 runtime.GC 方法 上一轮 GC 已结束 触发类型的源码:
type gcTriggerKind int const ( // 堆触发 gcTriggerHeap gcTriggerKind = iota // 定时触发 gcTriggerTime // 手动触发 gcTriggerCycle ) // 进行触发条件的校验 func (t gcTrigger) test() bool { if !memstats.enablegc || panicking.Load() !</description>
</item>
<item>
<title>Golang互斥锁-Mutex</title>
<link>https://www.yici.xin/post/tech/golang%E4%BA%92%E6%96%A5%E9%94%81/</link>
<pubDate>Tue, 27 Dec 2022 18:22:59 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang%E4%BA%92%E6%96%A5%E9%94%81/</guid>
<description>互斥锁结构 // A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 } state表示当前互斥锁的状态, sema是用于控制锁状态的信号量
在默认情况下,互斥锁的所有状态位都是 0,int32 中的不同位分别表示了不同的状态:
mutexLocked — 表示互斥锁的锁定状态;
mutexWoken — 唤醒模式,此时释放锁的g不会唤醒休眠的g;
mutexStarving — 当前的互斥锁进入饥饿状态;
waitersCount — 当前互斥锁上等待的 Goroutine 个数;
正常模式和饥饿模式的区别:
在正常模式下,锁的等待者会按照先进先出的顺序获取锁。但是刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁,为了减少这种情况的出现,一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被『饿死』。</description>
</item>
<item>
<title>pprof使用</title>
<link>https://www.yici.xin/post/tech/pprof%E4%BD%BF%E7%94%A8/</link>
<pubDate>Tue, 27 Dec 2022 18:18:12 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/pprof%E4%BD%BF%E7%94%A8/</guid>
<description>项目地址:
https://github.com/google/pprof
安装:
go install github.com/google/pprof@latest 什么是profile? A Profile is a collection of stack traces showing the call sequences that led to instances of a particular event, such as allocation. Packages can create and maintain their own profiles; the most common use is for tracking resources that must be explicitly closed, such as files or network connections.
Each Profile has a unique name. A few profiles are predefined:
goroutine - stack traces of all current goroutines heap - a sampling of all heap allocations threadcreate - stack traces that led to the creation of new OS threads block - stack traces that led to blocking on synchronization primitives mutex - stack traces of holders of contended mutexes There are 7 places you can get profiles in the default webserver: the ones mentioned above</description>
</item>
<item>
<title>Wire 依赖注入</title>
<link>https://www.yici.xin/post/tech/wire-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/</link>
<pubDate>Tue, 27 Dec 2022 18:09:30 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/wire-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/</guid>
<description>用法 一般会在一个名为wire.go的文件里提供需要自动注入的函数,在开头添加好tag,在函数中使用wire.Build方法,并提供创建依赖的方法,多个依赖可以组装为一个Set。
示例 // +build wireinject // tag不能忘 var ormSet = wire.NewSet(mysql.Newengine) var DataSourceSet = wire.NewSet(ormSet, japi.GetClient, cache.NewRedisCache) func InitAuthorRepo() AuthorRepoInterface { wire.Build( NewAuthorRepo, // 可以用这种方式替代New函数,特别是New的参数比较多时,*表示对结构体的全部字段进行生成 // wire.Struct(new(authorRepo), &#34;*&#34;), // 如果NewAuthorRepo的返回值不是显示声明AuthorRepoInterface,wire会提示无AuthorRepoInterface的provider // 此时就需要下面的语句进行绑定 // wire.Bind(new(AuthorRepoInterface), new(*authorRepo)), repo.DataSourceSet, ) return nil } 特性 wire.Struct type App struct { Foo *Foo Bar *Bar NoInject int `wire:&#34;-&#34;` } 该方法可以代替New函数,特别是New函数参数较多,不太好写的时候。
wire.Struct(new(App),&quot;Foo&quot;,&quot;Bar&quot;)生成指定的字段。
wire.Struct(new(App), &quot;*&quot;)表示结构体的全部字段都自动生成。
若是有某些字段不想在*时生成,可以给它添加tag。
wire.Bind wire.Bind(new(AuthorRepoInterface), new(*authorRepo)) wire.Bind可以指定结构体实现的接口,如果结构体的New函数返回值不是显示声明为其实现的接口,那么wire会报错,此时就需要使用Bind方法进行绑定。
wire.Value // provider.go type Foo struct { X int }// wire.</description>
</item>
<item>
<title>Protobuf语法</title>
<link>https://www.yici.xin/post/tech/protobuf%E8%AF%AD%E6%B3%95/</link>
<pubDate>Tue, 27 Dec 2022 18:02:40 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/protobuf%E8%AF%AD%E6%B3%95/</guid>
<description>什么是protobuf? 一种序列化结构数据的方式,与语言无关,与平台无关,可扩展。和json一样,但是更小更快。
V3语法介绍 举一个简单的例子,一个分页查询的请求参数,新建一个.proto文件并写入:
syntax = &#34;proto3&#34;; message SearchRequest { string query = 1; int32 page_num = 2; int32 page_size = 3; } .proto文件的第一行指定了当前正在使用proto3语法,如果未写,proto的编译器会默认使用proto2的语法进行解析。
SearchRequest定义了三个字段,它们都有类型和名称,并且在最后给了一个唯一编号,编号的作用是在序列化后的二进制序列中标识字段。当编号为1-15的范围内时,在编码时只需要一个字节,而16-2047就需要两个字节,具体原因可以了解一下protobuf的编码原理。所以,1-15的编号最好要保留给那些最经常出现的字段。你能使用的最小编号是1,最大是$2^{29}$-1,不过你不能使用19000-19999,这些编号为protobuf的实现而预保留了,如果你使用了,是过不了编译的。
在单个.proto文件中能定义多个message类型:
syntax = &#34;proto3&#34;; message SearchRequest { string query = 1; int32 page_num = 2; int32 page_size = 3; } message SearchResponse { repeated string data = 1; int32 total = 2; } repeated表示该字段是一个可重复值,可以理解为数组。
注释的写法:
syntax = &#34;proto3&#34;; /* 请求结构 */ message SearchRequest { string query = 1; int32 page_num = 2; int32 page_size = 3; } /* 响应结构 */ message SearchResponse { repeated string data = 1; int32 total = 2; // 总数 } 如果我们在更新message type时,是直接删除或者注释了字段,在之后其他人来更新时可能会复用被删除或注释的字段编号,如果这时加载了相同proto的旧版本,那么就会导致一些错误。所以protobuf提供了保留字段的机制,使用reserved关键字,可以保留编号或者字段名。</description>
</item>
<item>
<title>Protobuf原理</title>
<link>https://www.yici.xin/post/tech/protobuf%E5%8E%9F%E7%90%86/</link>
<pubDate>Tue, 27 Dec 2022 17:52:06 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/protobuf%E5%8E%9F%E7%90%86/</guid>
<description>protobuf 介绍 ProtoBuf(Protocol Buffers)是一种具有跨平台、语言无关、可扩展等特性的序列化结构数据的方法,可用于网络数据交换及存储。和我们平时使用的 json、xml 是同一种东西。它在解析性能和数据压缩后大小方面比 json、xml 更优秀,目前常用于 rpc。
protobuf 序列化机制 不同于传统的序列化方式,protobuf 在序列化数据之前,需要先定义一个结构体,它是记录数据在序列化和解析时的参照,我们简单看看它的语法:
message Person { required int32 id = 1; required string name = 2; required string hobby = 3; } 在该结构体的字段定义中,有几个关键位置,分别是类型(如 int32)、字段名(如 name),以及一个编号(如 1、2、3),
使用 protobuf 的双方都需要依照这个结构体定义来进行序列化和解析。
我们通过几个问题来了解其序列化机制吧:
QUESTION1 🙋♂️
在解析序列化数据时,怎么确定当前解析的数据对应哪个字段? 在传统的序列化机制中,序列化数据与其对应的字段,都会通过字段名进行联系,但在 protobuf 的序列化结果中,是没有id、name和hobby这样的字段名的,它们会被编号替代。
QUESTION2 🙋♂️
序列化后的字节流,如何分隔各个字段? 我们都知道,数据最终在传输的过程中,是以一个个字节的二进制形式传输的,面对一长串的字节流,如何确定每个字段的字节流段呢? 在每个字段的字节流最前面,会有几个特殊字节,称为 Tag,Tag 就充当了边界的作用,分隔各个字段。
如下图,我们可以把字节流看成一个个由 Tag 和数据(value)组成的结构。
Tag 不仅仅只是起到了分隔字段的作用,它还承载了类型信息,我们来看看 Tag 是如何生成的:
static int makeTag(final int fieldNumber, final int wireType) { return (filedNumber &lt;&lt; 3) | wireType; } 其中的fieldNumber,就是我们定义结构体时给的编号,wireType有以下几个值:</description>
</item>
<item>
<title>一些rust基础知识</title>
<link>https://www.yici.xin/post/tech/%E4%B8%80%E4%BA%9Brust%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</link>
<pubDate>Wed, 21 Dec 2022 18:14:01 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/%E4%B8%80%E4%BA%9Brust%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</guid>
<description>变量与可变性 变量的可变性 rust中进行变量的声明时,使用let关键字,如:
let x; 这样声明出来的变量是不可变的,不可以进行第二次赋值。 如果需要后续可以修改这个变量,在声明时需要加上mut关键字:
let mut x; 常量 声明常量使用 const 关键字而不是 let,并且必须注明值的类型,且声明时就必须赋值。
// ✅ const THREE_HOURS_IN_SECONDS: u32 = 3 * 60 * 60; // ❌ const THREE_HOURS_IN_SECONDS: u32; THREE_HOURS_IN_SECONDS = 3 * 60 * 60 不可变变量与常量有什么区别呢?
首先一个区别是,声明常量时提供的值必须是编译期间就可以计算出来的,而不可变变量虽然不可变,但是声明时可以使用运行时值。 内存布局角度来看,常量一般都是存储在数据段,而变量会在栈区或堆区。 隐藏 rust中,可以使用let定义一个与之前变量同名的新变量,此时同名的第一个变量隐藏了。
fn main() { // 定义x let x = 5; // 再次定义x,此时第一个x被隐藏 let x = 6; { // 再次定义x,但是仅在该作用域内生效 let x = x * 2; println!(&#34;The value of x in the inner scope is: {x}&#34;); // 12 } println!</description>
</item>
<item>
<title>Go Sync Pool</title>
<link>https://www.yici.xin/post/tech/go-sync-pool/</link>
<pubDate>Mon, 21 Nov 2022 17:05:22 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/go-sync-pool/</guid>
<description>go sync pool</description>
</item>
<item>
<title>Go channel</title>
<link>https://www.yici.xin/post/tech/go-channel/</link>
<pubDate>Mon, 21 Nov 2022 09:45:13 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/go-channel/</guid>
<description>数据结构 channel 的底层源码和相关实现在/src/runtime/chan.go中。
type hchan struct { qcount uint // 队列中所有数据总数 dataqsiz uint // 环形队列的 size buf unsafe.Pointer // 指向 dataqsiz 长度的数组 elemsize uint16 // 元素大小 closed uint32 elemtype *_type // 元素类型 sendx uint // 已发送的元素在环形队列中的位置 recvx uint // 已接收的元素在环形队列中的位置 recvq waitq // 接收者的等待队列 sendq waitq // 发送者的等待队列 // lock 锁保护 hchan 中的所有字段,以及此通道上被阻塞的 sudogs 中的多个字段。 // 持有 lock 的时候,禁止更改另一个 G 的状态(特别是不要使 G 状态变成ready), // 因为这会因为堆栈 shrinking 而发生死锁。 lock mutex } recvq 和 sendq 是等待队列,waitq 类型是一个双向链表:</description>
</item>
<item>
<title>Short_code</title>
<link>https://www.yici.xin/post/tech/short_code/</link>
<pubDate>Sat, 19 Nov 2022 18:05:50 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/short_code/</guid>
<description>Short_code 1. mark高亮 这是一个重点标记 文字居中
🤓
这是一条信息 <!DOCTYPE HTML> <!DOCTYPE HTML> MacOS Linux Windows MacOS This is tab MacOS content.
Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat stringit, frustra Saturnius uteroque inter! Oculis non ritibus Telethusa protulit, sed sed aere valvis inhaesuro Pallas animam: qui quid, ignes. Miseratus fonte Ditis conubia.
Linux This is tab Linux content.
Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat stringit, frustra Saturnius uteroque inter!</description>
</item>
<item>
<title>golang-map介绍</title>
<link>https://www.yici.xin/post/tech/golang-map%E4%BB%8B%E7%BB%8D/</link>
<pubDate>Wed, 05 Jan 2022 23:38:06 +0800</pubDate>
<guid>https://www.yici.xin/post/tech/golang-map%E4%BB%8B%E7%BB%8D/</guid>
<description>本文中使用的 go 版本:
go version go1.17.2 darwin/amd64
内容概述 本文介绍 golang 中经常用到的结构-map,简称哈希表、字典。介绍其结构及设计思路。
map 在源码中的结构——hmap Go 语言采用的核心数据结构是哈希查找表,使用链表解决哈希冲突。
在源码中$GOROOT/src/runtime/map.go,map 的核心结构体是这样的:
// A header for a Go map. type hmap struct { count int // map中的元素数量,即len(map)时的返回值 flags uint8 B uint8 // buckets的以2为底的对数, 即2^B=buckets noverflow uint16 // 溢出桶的近似数; see incrnoverflow for details hash0 uint32 // 哈希种子 buckets unsafe.Pointer // 2^B个bucket的数组,may be nil if count==0. oldbuckets unsafe.Pointer // 哈希在扩容时用于保存之前 buckets 的字段,它的大小是当前 buckets 的一半; nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields } 在上面我们需要关注的核心是buckets,它是一个指针,最终指向了bmap结构体数组</description>
</item>
</channel>
</rss>