From f18a620af94b3e0077b56ace7926a5221813a87e Mon Sep 17 00:00:00 2001 From: Lu Bohan <126435268+Eromenos8@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:03:41 +0800 Subject: [PATCH 1/5] Change stored value in concurrency example Updated stored value in atomic.Value example. --- src/essential/senior/110.concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/essential/senior/110.concurrency.md b/src/essential/senior/110.concurrency.md index ff98ba703..0de9a5913 100644 --- a/src/essential/senior/110.concurrency.md +++ b/src/essential/senior/110.concurrency.md @@ -2371,7 +2371,7 @@ func main() { func main() { var val atomic.Value val.Store("hello world") - val.Store(114154) + val.Store(114514) fmt.Println(val.Load()) } // panic: sync/atomic: store of inconsistently typed value into Value From bd4c0475e19ea2b2f6c4dcfc97922955edf4e060 Mon Sep 17 00:00:00 2001 From: Rudeus Date: Mon, 20 Oct 2025 16:20:39 +0800 Subject: [PATCH 2/5] Fix spelling of 'atomic' in CAS explanation Corrected the spelling of 'atomic' in the CAS section and ensured consistency in the explanation of optimistic locking. --- src/essential/senior/110.concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/essential/senior/110.concurrency.md b/src/essential/senior/110.concurrency.md index ff98ba703..271ece93a 100644 --- a/src/essential/senior/110.concurrency.md +++ b/src/essential/senior/110.concurrency.md @@ -2302,7 +2302,7 @@ func main() { ### CAS -`atmoic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`,它是乐观锁的一种典型实现。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式。之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发安全效率相对于锁要高一些,许多并发安全的数据结构都采用了 `cAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子: +`atmoic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`,它是乐观锁的一种典型实现。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式。之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发安全效率相对于锁要高一些,许多并发安全的数据结构都采用了 `CAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子: ```go var lock sync.Mutex From 30872a77ea8a69769b2d2d42c39a21a16f1d7e20 Mon Sep 17 00:00:00 2001 From: Rudeus Date: Tue, 21 Oct 2025 22:12:25 +0800 Subject: [PATCH 3/5] Refine test documentation for clarity and examples Updated text for clarity and consistency in examples. --- src/essential/senior/120.test.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/essential/senior/120.test.md b/src/essential/senior/120.test.md index ab90e905a..6491b1092 100644 --- a/src/essential/senior/120.test.md +++ b/src/essential/senior/120.test.md @@ -128,7 +128,7 @@ PASS ok golearn/test 0.038s ``` -上面三种情况虽然都完成了测试,但是输出结果太简介了,这时可以加上参数`-v`,来使输出结果更加详细一点,例如 +上面三种情况虽然都完成了测试,但是输出结果太简洁了,这时可以加上参数`-v`,来使输出结果更加详细一点,例如 ```sh $ go test ./ -v @@ -226,7 +226,7 @@ func ExampleWithDeadline() { } ``` -表面上看该测试函数就是一个普通的函数,不过示例测试主要是由`Output`注释来体现的,待测试函数只有一行输出时,使用`Output`注释来检测输出。首先创建一个`hello.go`的文化,写入如下代码 +表面上看该测试函数就是一个普通的函数,不过示例测试主要是由`Output`注释来体现的,待测试函数只有一行输出时,使用`Output`注释来检测输出。首先创建一个名为`hello.go`的文件,写入如下代码 ```go package say @@ -525,7 +525,7 @@ ok command-line-arguments 0.462s ### Helper -通过`t.Helper()`可以将当前函数标记为帮助函数,帮助函数不会单独作为一个测试用例用于执行,在记录日志时输出的行号也是帮助函数的调用者的行号,这样可以使得分析日志时定位更准确,避免的冗杂的其他信息。比如将上述`t.Cleanup`的例子就可以修改为帮助函数,如下。 +通过`t.Helper()`可以将当前函数标记为帮助函数,帮助函数不会单独作为一个测试用例用于执行,在记录日志时输出的行号也是帮助函数的调用者的行号,这样可以使得分析日志时定位更准确,避免的冗杂的其他信息。比如上述`t.Cleanup`的例子就可以修改为帮助函数,如下。 ```go package tool_test @@ -748,7 +748,7 @@ ok golearn/tool_test 0.450s ### 表格风格 -在上述的单元测试中,测试的输入数据都是手动声明的一个个变量,当数据量小的时候无伤大雅,但如果想要测试多组数据时,就不太可能再去声明变量来创建测试数据,所以一般情况下都是尽量采用结构体切片的形式,结构体是临时声明的匿名结构体,因为这样的编码风格看起来就跟表格一样,所以称为`table-driven`,下面举个例子,这是一个手动声明多个变量来创建测试数据的例子,如果有多组数据狠起来就不是很直观,所以将其修改为表格风格 +在上述的单元测试中,测试的输入数据都是手动声明的一个个变量,当数据量小的时候无伤大雅,但如果想要测试多组数据时,就不太可能再去声明变量来创建测试数据,所以一般情况下都是尽量采用结构体切片的形式,结构体是临时声明的匿名结构体,因为这样的编码风格看起来就跟表格一样,所以称为`table-driven`。下面举个例子,这是一个手动声明多个变量来创建测试数据的例子,如果有多组数据看起来就不是很直观,所以将其修改为表格风格 ```go func TestEqual(t *testing.T) { @@ -1020,7 +1020,7 @@ geomean 543.6 541.7 -0.33% ² all samples are equal ``` -从结果中可以看出 benchstat 将其分为了三组,分别是耗时,内存占用和内存分配次数,其中`geomoean`为平均值,`p`为 样本的显著性水平,临界区间通常为 0.05,高于 0.05 就不太可信,取其中一条数据如下: +从结果中可以看出 benchstat 将其分为了三组,分别是耗时,内存占用和内存分配次数,其中`geomean`为平均值,`p`为样本的显著性水平,临界区间通常为0.05,高于0.05就不太可信,取其中一条数据如下: ``` │ sec/op │ sec/op vs base │ @@ -1211,7 +1211,7 @@ func main() { e4 ``` -究其原因在于 Go 在字符串单位是字节,而不是字符。所以再次修改待测源代码,如果传入的是非 utf8 字符串,直接返回错误。 +究其原因在于`\xe4`代表一个字节,但并不是一个有效的UTF-8序列(UTF-8编码中`\xe4`是一个三字节字符的开始,但后面缺少两个字节).转换成`[]rune`时,Golang自动将它变成含单个Unicode字符的`[]rune{"\uFFFD"}`,其反转后仍是`[]rune{"\uFFFD"}`,转换回`string`时该Unicode字符又被替换为其UTF-8编码`\xef\xbf\xbd`。因此一个解决办法是如果传入的是非 utf8 字符串,直接返回错误: ```go func Reverse(s string) (string, error) { From fb765d4e2d18759f8e9c1dca4aeb9c286666787c Mon Sep 17 00:00:00 2001 From: Rudeus Date: Tue, 21 Oct 2025 22:42:15 +0800 Subject: [PATCH 4/5] Enhance CAS explanation and optimistic locking context Clarified the explanation of CAS and optimistic locking, emphasizing its role in concurrent programming and providing a clearer comparison with mutexes. --- src/essential/senior/110.concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/essential/senior/110.concurrency.md b/src/essential/senior/110.concurrency.md index ff98ba703..49e4675cb 100644 --- a/src/essential/senior/110.concurrency.md +++ b/src/essential/senior/110.concurrency.md @@ -2302,7 +2302,7 @@ func main() { ### CAS -`atmoic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`,它是乐观锁的一种典型实现。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式。之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发安全效率相对于锁要高一些,许多并发安全的数据结构都采用了 `cAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子: +`atmoic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`。它是实现乐观锁和无锁数据结构的核心。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式:线程/协程在修改数据前,不会先加锁,而是先读取数据,进行计算,然后在提交修改时使用`CAS`来判断在此期间是否有其他线程修改过该数据。如果没有(值仍等于之前读取的值),则修改成功;否则,失败并重试。因此之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发,其安全性和效率相对于锁要高一些,许多并发安全的数据结构都采用了 `CAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子: ```go var lock sync.Mutex From 30d165e1bc1dfc227dae7295412d599b9b188a4b Mon Sep 17 00:00:00 2001 From: Rudeus Date: Tue, 21 Oct 2025 23:43:18 +0800 Subject: [PATCH 5/5] Refine array slicing and slice capacity explanations Updated examples and explanations regarding array slicing and slice capacity in Go. --- src/essential/base/60.slice.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/essential/base/60.slice.md b/src/essential/base/60.slice.md index c68a2514c..b70b9da2d 100644 --- a/src/essential/base/60.slice.md +++ b/src/essential/base/60.slice.md @@ -100,17 +100,17 @@ cap(nums) ### 切割 -切割数组的格式为`arr[startIndex:endIndex]`,切割的区间为**左闭右开**,例子如下: +切割数组的格式为`arr[startIndex:endIndex]`,切割的区间为**左闭右开**。且数组在切割后,就会变为切片类型。例子如下: ```go nums := [5]int{1, 2, 3, 4, 5} -nums[1:] // 子数组范围[1,5) -> [2 3 4 5] -nums[:5] // 子数组范围[0,5) -> [1 2 3 4 5] -nums[2:3] // 子数组范围[2,3) -> [3] -nums[1:3] // 子数组范围[1,3) -> [2 3] -``` -数组在切割后,就会变为切片类型 +nums[:] // 子切片范围[0,5) -> [1 2 3 4 5] +nums[1:] // 子切片范围[1,5) -> [2 3 4 5] +nums[:5] // 子切片范围[0,5) -> [1 2 3 4 5] +nums[2:3] // 子切片范围[2,3) -> [3] +nums[1:3] // 子切片范围[1,3) -> [2 3] +``` ```go func main() { @@ -184,7 +184,7 @@ nums := new([]int) // 指针 ::: tip -切片的底层实现依旧是数组,是引用类型,可以简单理解为是指向底层数组的指针。 +切片的底层实现依旧是数组,是引用类型,可以简单理解为是指向底层数组的指针(本质上切片在Go中是一个结构体,包含指向底层数组的指针、长度值、容量值)。因此切片作为函数参数传递时不复制底层数组,函数内对传入切片的修改会反映在原切片中。 ::: @@ -414,8 +414,7 @@ s2 := s1[3:4] // cap = 9 - 3 = 6 ```go s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9 s2 := s1[3:4] // cap = 9 - 3 = 6 -// 添加新元素,由于容量为6.所以没有扩容,直接修改底层数组 -s2 = append(s2, 1) +s2 = append(s2, 1) // 添加新元素,由于容量为6.所以没有扩容,直接修改底层数组 fmt.Println(s2) fmt.Println(s1) ``` @@ -433,8 +432,7 @@ fmt.Println(s1) func main() { s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9 s2 := s1[3:4:4] // cap = 4 - 3 = 1 - // 容量不足,分配新的底层数组 - s2 = append(s2, 1) + s2 = append(s2, 1) // 容量不足,分配新的底层数组 fmt.Println(s2) fmt.Println(s1) }