From 1d4ee4c460da0dd29a3b8b986e53b178df1f3f91 Mon Sep 17 00:00:00 2001 From: wukoo Date: Sat, 18 Nov 2017 13:10:03 +0800 Subject: [PATCH 01/17] Update ch3-05-3.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit utf8 最长4个字节,最多往前找3个字节,就可以找到当字符的起始字节。英文版里也是说3个字节 --- ch3/ch3-05-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch3/ch3-05-3.md b/ch3/ch3-05-3.md index c91dbb1..683f4d9 100644 --- a/ch3/ch3-05-3.md +++ b/ch3/ch3-05-3.md @@ -9,7 +9,7 @@ UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused) ``` -变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 +变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多3个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。 From d28d4fb53ab11d1f794689c62a0f4e7e32d20e85 Mon Sep 17 00:00:00 2001 From: JimLee1996 <59061463@qq.com> Date: Tue, 21 Nov 2017 14:32:14 +0800 Subject: [PATCH 02/17] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=B0=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function and arguemnt expressions are evaluated when the staement is executed, butt the actual call is deferred until the function that contains... 原文翻译很有歧义望修改! --- ch5/ch5-08.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ch5/ch5-08.md b/ch5/ch5-08.md index 9c25d1f..1ee8015 100644 --- a/ch5/ch5-08.md +++ b/ch5/ch5-08.md @@ -46,7 +46,7 @@ title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/ resp.Body.close调用了多次,这是为了确保title在所有执行路径下(即使函数运行失败)都关闭了网络连接。随着函数变得复杂,需要处理的错误也变多,维护清理逻辑变得越来越困难。而Go语言独有的defer机制可以让事情变得简单。 -你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 +你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当执行到该条语句时,函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。 defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。在下面的代码中,一条defer语句替代了之前的所有resp.Body.Close @@ -221,4 +221,4 @@ func fetch(url string) (filename string, n int64, err error) { 对resp.Body.Close延迟调用我们已经见过了,在此不做解释。上例中,通过os.Create打开文件进行写入,在关闭文件时,我们没有对f.close采用defer机制,因为这会产生一些微妙的错误。许多文件系统,尤其是NFS,写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息,可能会导致数据丢失,而我们还误以为写入操作成功。如果io.Copy和f.close都失败了,我们倾向于将io.Copy的错误信息反馈给调用者,因为它先于f.close发生,更有可能接近问题的本质。 -**练习5.18:**不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。 \ No newline at end of file +**练习5.18:**不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。 From 3a284d83e37d05c0710f36247918fbbd0ce09402 Mon Sep 17 00:00:00 2001 From: A1014280203 <1014280203@qq.com> Date: Tue, 28 Nov 2017 12:01:30 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E8=A1=A5=E5=85=85sync.Once=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充了go1.9中,sync.Once的新实现方式带来的不同。 --- ch9/ch9-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-05.md b/ch9/ch9-05.md index b335e9e..6a6a5cc 100644 --- a/ch9/ch9-05.md +++ b/ch9/ch9-05.md @@ -101,6 +101,6 @@ func Icon(name string) image.Image { } ``` -每一次对Do(loadIcons)的调用都会锁定mutex,并会检查boolean变量。在第一次调用时,boolean变量的值是false,Do会调用loadIcons并会将boolean变量设置为true。随后的调用什么都不会做,但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话,我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 +每一次对Do(loadIcons)的调用都会锁定mutex,并会检查boolean变量(译注:Go1.9中会先判断boolean变量是否为1(true),只有不为1才锁定mutex,不再需要每次都锁定mutex)。在第一次调用时,boolean变量的值是false,Do会调用loadIcons并会将boolean变量设置为true。随后的调用什么都不会做,但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话,我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 **练习 9.2:** 重写2.6.2节中的PopCount的例子,使用sync.Once,只在第一次需要用到的时候进行初始化。(虽然实际上,对PopCount这样很小且高度优化的函数进行同步可能代价没法接受) From 740cc92d15e17cce33809d59b63bb8783d28b9e4 Mon Sep 17 00:00:00 2001 From: MiFalyzz Date: Thu, 30 Nov 2017 11:25:28 +0800 Subject: [PATCH 04/17] Update ch5-07.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interface{} 而不是 interfac{} --- ch5/ch5-07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch5/ch5-07.md b/ch5/ch5-07.md index 6a68da2..b7d3514 100644 --- a/ch5/ch5-07.md +++ b/ch5/ch5-07.md @@ -51,7 +51,7 @@ linenum, name := 12, "count" errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count" ``` -interfac{}表示函数的最后一个参数可以接收任意类型,我们会在第7章详细介绍。 +interface{}表示函数的最后一个参数可以接收任意类型,我们会在第7章详细介绍。 **练习5.15:** 编写类似sum的可变参数函数max和min。考虑不传参时,max和min该如何处理,再编写至少接收1个参数的版本。 From d280c6c7aa849b11214b9d1807e4661eb9a1e500 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Fri, 1 Dec 2017 00:08:06 +0800 Subject: [PATCH 05/17] Update ch7-13.md --- ch7/ch7-13.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ch7/ch7-13.md b/ch7/ch7-13.md index 77490e5..aab7328 100644 --- a/ch7/ch7-13.md +++ b/ch7/ch7-13.md @@ -48,11 +48,11 @@ switch语句可以简化if-else链,如果这个if-else链对一连串值做相 ```go switch x.(type) { - case nil: // ... - case int, uint: // ... - case bool: // ... - case string: // ... - default: // ... +case nil: // ... +case int, uint: // ... +case bool: // ... +case string: // ... +default: // ... } ``` From ce363dd19ee713f2a3eca90495b545f5164b930c Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Sat, 2 Dec 2017 16:08:06 +0800 Subject: [PATCH 06/17] Update ch8-04-1.md --- ch8/ch8-04-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-04-1.md b/ch8/ch8-04-1.md index 740fa1f..ce961bd 100644 --- a/ch8/ch8-04-1.md +++ b/ch8/ch8-04-1.md @@ -29,7 +29,7 @@ func main() { } ``` -当用户关闭了标准输入,主goroutine中的mustCopy函数调用将返回,然后调用conn.Close()关闭读和写方向的网络连接。关闭网络连接中的写方向的连接将导致server程序收到一个文件(end-of-file)结束的信号。关闭网络连接中读方向的连接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”(“从关闭的连接读”)类似的错误,因此我们临时移除了错误日志语句;在练习8.3将会提供一个更好的解决方案。(需要注意的是go语句调用了一个函数字面量,这Go语言中启动goroutine常用的形式。) +当用户关闭了标准输入,主goroutine中的mustCopy函数调用将返回,然后调用conn.Close()关闭读和写方向的网络连接。关闭网络连接中的写方向的连接将导致server程序收到一个文件(end-of-file)结束的信号。关闭网络连接中读方向的连接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”(“从关闭的连接读”)类似的错误,因此我们临时移除了错误日志语句;在练习8.3将会提供一个更好的解决方案。(需要注意的是go语句调用了一个函数字面量,这是Go语言中启动goroutine常用的形式。) 在后台goroutine返回之前,它先打印一个日志信息,然后向done对应的channel发送一个值。主goroutine在退出前先等待从done对应的channel接收一个值。因此,总是可以在程序退出前正确输出“done”消息。 From 74e4184c86021c1e982742989e356c0365d1afa8 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Sat, 2 Dec 2017 17:19:21 +0800 Subject: [PATCH 07/17] Update ch8-04-4.md --- ch8/ch8-04-4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-04-4.md b/ch8/ch8-04-4.md index 0c877de..d7bfe9a 100644 --- a/ch8/ch8-04-4.md +++ b/ch8/ch8-04-4.md @@ -51,7 +51,7 @@ fmt.Println(<-ch) // "B" fmt.Println(<-ch) // "C" ``` -在这个例子中,发送和接收操作都发生在同一个goroutine中,但是在真实的程序中它们一般由不同的goroutine执行。Go语言新手有时候会将一个带缓存的channel当作同一个goroutine中的队列使用,虽然语法看似简单,但实际上这是一个错误。Channel和goroutine的调度器机制是紧密相连的,一个发送操作——或许是整个程序——可能会永远阻塞。如果你只是需要一个简单的队列,使用slice就可以了。 +在这个例子中,发送和接收操作都发生在同一个goroutine中,但是在真实的程序中它们一般由不同的goroutine执行。Go语言新手有时候会将一个带缓存的channel当作同一个goroutine中的队列使用,虽然语法看似简单,但实际上这是一个错误。Channel和goroutine的调度器机制是紧密相连的,如果没有其他goroutine从channel接收,发送者——或许是整个程序——将会面临永远阻塞的风险。如果你只是需要一个简单的队列,使用slice就可以了。 下面的例子展示了一个使用了带缓存channel的应用。它并发地向三个镜像站点发出请求,三个镜像站点分散在不同的地理位置。它们分别将收到的响应发送到带缓存channel,最后接收者只接收第一个收到的响应,也就是最快的那个响应。因此mirroredQuery函数可能在另外两个响应慢的镜像站点响应之前就返回了结果。(顺便说一下,多个goroutines并发地向同一个channel发送数据,或从同一个channel接收数据都是常见的用法。) From 620a1c7fd1784ff870b2076c8dc36f5964f11fce Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Mon, 4 Dec 2017 22:56:28 +0800 Subject: [PATCH 08/17] Update SUMMARY.md --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index 798a1f9..f405fbb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -79,7 +79,7 @@ * [并发的循环](ch8/ch8-05.md) * [示例: 并发的Web爬虫](ch8/ch8-06.md) * [基于select的多路复用](ch8/ch8-07.md) - * [示例: 并发的字典遍历](ch8/ch8-08.md) +  * [示例: 并发的目录遍历](ch8/ch8-08.md) * [并发的退出](ch8/ch8-09.md) * [示例: 聊天服务](ch8/ch8-10.md) * [基于共享变量的并发](ch9/ch9.md) From e1358aac8662be4d430376dac0df692dffc111ca Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 00:20:21 +0800 Subject: [PATCH 09/17] Update ch8-08.md --- ch8/ch8-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-08.md b/ch8/ch8-08.md index 0ca44bf..e79330d 100644 --- a/ch8/ch8-08.md +++ b/ch8/ch8-08.md @@ -131,7 +131,7 @@ $ ./du2 -v $HOME /usr /bin /etc 213201 files 62.7 GB ``` -然而这个程序还是会花上很长时间才会结束。无法对walkDir做并行化处理没什么别的原因,无非是因为磁盘系统并行限制。下面这个第三个版本的du,会对每一个walkDir的调用创建一个新的goroutine。它使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,另一个goroutine会在计数器减为零的时候将fileSizes这个channel关闭。 +然而这个程序还是会花上很长时间才会结束。完全可以并发调用walkDir,从而发挥磁盘系统的并行性能。下面这个第三个版本的du,会对每一个walkDir的调用创建一个新的goroutine。它使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,另一个goroutine会在计数器减为零的时候将fileSizes这个channel关闭。 gopl.io/ch8/du3 ```go From 7b3d3a6aad9b4fd438fc78a28af74f2f4601dd05 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 20:59:49 +0800 Subject: [PATCH 10/17] Update ch8-10.md --- ch8/ch8-10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch8/ch8-10.md b/ch8/ch8-10.md index 553a778..4e09c2b 100644 --- a/ch8/ch8-10.md +++ b/ch8/ch8-10.md @@ -114,7 +114,7 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived 当与n个客户端保持聊天session时,这个程序会有2n+2个并发的goroutine,然而这个程序却并不需要显式的锁(§9.2)。clients这个map被限制在了一个独立的goroutine中,broadcaster,所以它不能被并发地访问。多个goroutine共享的变量只有这些channel和net.Conn的实例,两个东西都是并发安全的。我们会在下一章中更多地讲解约束,并发安全以及goroutine中共享变量的含义。 -**练习 8.12:** 使broadcaster能够将arrival事件通知当前所有的客户端。为了达成这个目的,你需要有一个客户端的集合,并且在entering和leaving的channel中记录客户端的名字。 +**练习 8.12:** 使broadcaster能够将arrival事件通知当前所有的客户端。这需要你在clients集合中,以及entering和leaving的channel中记录客户端的名字。 **练习 8.13:** 使聊天服务器能够断开空闲的客户端连接,比如最近五分钟之后没有发送任何消息的那些客户端。提示:可以在其它goroutine中调用conn.Close()来解除Read调用,就像input.Scanner()所做的那样。 From dfefed699c878bedc4814243fb377b4357cd8091 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 22:47:19 +0800 Subject: [PATCH 11/17] Update ch9-01.md --- ch9/ch9-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..64172fc 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -2,7 +2,7 @@ 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法判断的。当我们没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话,就说明x和y这两个事件是并发的。 -考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的方法和操作都是并发安全的话,那么类型便是并发安全的。 +考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的方法和操作都是并发安全的话,那么该类型便是并发安全的。 在一个程序中有非并发安全的类型的情况下,我们依然可以使这个程序并发安全。确实,并发安全的类型是例外,而不是规则,所以只有当文档中明确地说明了其是并发安全的情况下,你才可以并发地去访问它。我们会避免并发访问大多数的类型,无论是将变量局限在单一的一个goroutine内,还是用互斥条件维持更高级别的不变性,都是为了这个目的。我们会在本章中说明这些术语。 From 75a37c10668fd49cc12d1f447a1984f16596fa30 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Tue, 5 Dec 2017 23:20:21 +0800 Subject: [PATCH 12/17] Update ch9-01.md --- ch9/ch9-01.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..dd963ee 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -40,11 +40,11 @@ go bank.Deposit(100) // B Alice存了$200,然后检查她的余额,同时Bob存了$100。因为A1和A2是和B并发执行的,我们没法预测他们发生的先后顺序。直观地来看的话,我们会认为其执行顺序只有三种可能性:“Alice先”,“Bob先”以及“Alice/Bob/Alice”交错执行。下面的表格会展示经过每一步骤后balance变量的值。引号里的字符串表示余额单。 ``` -Alice first Bob first Alice/Bob/Alice -0 0 0 -A1 200 B 100 A1 200 -A2 "=200" A1 300 B 300 -B 300 A2 "=300" A2 "=300" +Alice first Bob first Alice/Bob/Alice + 0 0 0 + A1 200 B 100 A1 200 + A2 "= 200" A1 300 B 300 + B 300 A2 "= 300" A2 "= 300" ``` 所有情况下最终的余额都是$300。唯一的变数是Alice的余额单是否包含了Bob交易,不过无论怎么着客户都不会在意。 From 4283571cf34b566ed027c78e2066b528f2da9e51 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 00:20:55 +0800 Subject: [PATCH 13/17] Update ch9-01.md --- ch9/ch9-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 557b732..3bfd8f5 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -110,7 +110,7 @@ var icons = map[string]image.Image{ func Icon(name string) image.Image { return icons[name] } ``` -上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始执行之前就完成了的。只要初始化完成了,icons就再也不会修改的或者不变量是本来就并发安全的,这种变量不需要进行同步。不过显然我们没法用这种方法,因为update操作是必要的操作,尤其对于银行账户来说。 +上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始执行之前就完成了的。只要初始化完成了,icons就再也不会被修改。数据结构如果从不被修改或是不变量则是并发安全的,无需进行同步。不过显然,如果update操作是必要的,我们就没法用这种方法,比如说银行账户。 第二种避免数据竞争的方法是,避免从多个goroutine访问变量。这也是前一章中大多数程序所采用的方法。例如前面的并发web爬虫(§8.6)的main goroutine是唯一一个能够访问seen map的goroutine,而聊天服务器(§8.10)中的broadcaster goroutine是唯一一个能够访问clients map的goroutine。这些变量都被限定在了一个单独的goroutine中。 From c1b75dda196226939251832135b7ca1c0f3fb509 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 14:14:13 +0800 Subject: [PATCH 14/17] Update ch9-02.md --- ch9/ch9-02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-02.md b/ch9/ch9-02.md index e9ad9b6..008a625 100644 --- a/ch9/ch9-02.md +++ b/ch9/ch9-02.md @@ -102,7 +102,7 @@ func Withdraw(amount int) bool { 上面这个例子中,Deposit会调用mu.Lock()第二次去获取互斥锁,但因为mutex已经锁上了,而无法被重入(译注:go里没有重入锁,关于重入锁的概念,请参考java)--也就是说没法对一个已经锁上的mutex来再次上锁--这会导致程序死锁,没法继续执行下去,Withdraw会永远阻塞下去。 -关于Go的互斥量不能重入这一点我们有很充分的理由。互斥量的目的是为了确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”。但实际上这里对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。在其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性。(译注:这段翻译有点晕) +关于Go的mutex不能重入这一点我们有很充分的理由。mutex的目的是确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”,但实际上这里对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。在其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性。(译注:这段翻译有点晕) 一个通用的解决方案是将一个函数分离为多个函数,比如我们把Deposit分离成两个:一个不导出的函数deposit,这个函数假设锁总是会被保持并去做实际的操作,另一个是导出的函数Deposit,这个函数会调用deposit,但在调用前会先去获取锁。同理我们可以将Withdraw也表示成这种形式: From e287bc9f23195e8b847f9e3daf573a72072786e5 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 18:27:50 +0800 Subject: [PATCH 15/17] Update SUMMARY.md --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index f405fbb..7fe80e7 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -87,7 +87,7 @@ * [sync.Mutex互斥锁](ch9/ch9-02.md) * [sync.RWMutex读写锁](ch9/ch9-03.md) * [内存同步](ch9/ch9-04.md) - * [sync.Once初始化](ch9/ch9-05.md) +  * [sync.Once惰性初始化](ch9/ch9-05.md) * [竞争条件检测](ch9/ch9-06.md) * [示例: 并发的非阻塞缓存](ch9/ch9-07.md) * [Goroutines和线程](ch9/ch9-08.md) From 7c0b29af8ee80ad7a5ae5956536b8b6e1867053a Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 18:29:24 +0800 Subject: [PATCH 16/17] Update ch9-05.md --- ch9/ch9-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch9/ch9-05.md b/ch9/ch9-05.md index 6a6a5cc..75246a0 100644 --- a/ch9/ch9-05.md +++ b/ch9/ch9-05.md @@ -1,4 +1,4 @@ -## 9.5. sync.Once初始化 +## 9.5. sync.Once惰性初始化 如果初始化成本比较大的话,那么将初始化延迟到需要的时候再去做就是一个比较好的选择。如果在程序启动的时候就去做这类初始化的话,会增加程序的启动时间,并且因为执行的时候可能也并不需要这些变量,所以实际上有一些浪费。让我们来看在本章早一些时候的icons变量: From 84d5d2bf5e673acd204d7bb64458e5e598b80c89 Mon Sep 17 00:00:00 2001 From: Hanlei Qin Date: Wed, 6 Dec 2017 22:37:52 +0800 Subject: [PATCH 17/17] Update ch10-06.md --- ch10/ch10-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch10/ch10-06.md b/ch10/ch10-06.md index 3c4ceee..5faeba4 100644 --- a/ch10/ch10-06.md +++ b/ch10/ch10-06.md @@ -4,7 +4,7 @@ 当创建一个包,一般要用短小的包名,但也不能太短导致难以理解。标准库中最常用的包有bufio、bytes、flag、fmt、http、io、json、os、sort、sync和time等包。 -它们的名字都简洁明了。例如,不要将一个类似imageutil或ioutilis的通用包命名为util,虽然它看起来很短小。要尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用户重命名导入包,例如前面看到的path包。 +尽可能让命名有描述性且无歧义。例如,类似imageutil或ioutilis的工具包命名已经足够简洁了,就无须再命名为util了。要尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用户重命名导入包,例如前面看到的path包。 包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。