mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
Merge branch 'master' of github.com:gopl-zh/gopl-zh.github.com
This commit is contained in:
commit
065317c447
@ -16,6 +16,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
- http://shifei.me/gopl-zh/
|
||||
- http://2goo.info/media/html/gopl-zh-gh-pages/
|
||||
- http://docs.plhwin.com/gopl-zh/
|
||||
- http://gopl-zh.simple-is-best.tk/
|
||||
|
||||
**注意,在线预览不是最新版,最新以仓库里的内容为准**
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
中文译者 | 章节
|
||||
-------------------------------------- | -------------------------
|
||||
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
|
||||
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
|
||||
`chai2010 <chaishushan@gmail.com>` | 前言/第2 ~ 4章/第10 ~ 13章
|
||||
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8 ~ 9章
|
||||
`CrazySssst` | 第5章
|
||||
`foreversmart <njutree@gmail.com>` | 第7章
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第一章 入门
|
||||
# 第一章 入门
|
||||
|
||||
本章介绍Go语言的基础组件。本章提供了足够的信息和示例程序,希望可以帮你尽快入门, 写出有用的程序。本章和之后章节的示例程序都针对你可能遇到的现实案例。先了解几个Go程序,涉及的主题从简单的文件处理、图像处理到互联网客户端和服务端并发。当然,第一章不会解释细枝末节,但用这些程序来学习一门新语言还是很有效的。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第十章 包和工具
|
||||
# 第十章 包和工具
|
||||
|
||||
现在随便一个小程序的实现都可能包含超过10000个函数。然而作者一般只需要考虑其中很小的一部分和做很少的设计,因为绝大部分代码都是由他人编写的,它们通过类似包或模块的方式被重用。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第十一章 测试
|
||||
# 第十一章 测试
|
||||
|
||||
Maurice Wilkes,第一个存储程序计算机EDSAC的设计者,1949年他在实验室爬楼梯时有一个顿悟。在《计算机先驱回忆录》(Memoirs of a Computer Pioneer)里,他回忆到:“忽然间有一种醍醐灌顶的感觉,我整个后半生的美好时光都将在寻找程序BUG中度过了”。肯定从那之后的大部分正常的码农都会同情Wilkes过分悲观的想法,虽然也许会有人困惑于他对软件开发的难度的天真看法。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第十二章 反射
|
||||
# 第十二章 反射
|
||||
|
||||
Go语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
|
||||
|
||||
|
@ -15,17 +15,17 @@ Sizeof函数返回的大小只包括数据结构中固定的部分,例如字
|
||||
|
||||
|
||||
类型 | 大小
|
||||
----------------------------- | ----
|
||||
bool | 1个字节
|
||||
intN, uintN, floatN, complexN | N/8个字节(例如float64是8个字节)
|
||||
int, uint, uintptr | 1个机器字
|
||||
*T | 1个机器字
|
||||
string | 2个机器字(data,len)
|
||||
[]T | 3个机器字(data,len,cap)
|
||||
map | 1个机器字
|
||||
func | 1个机器字
|
||||
chan | 1个机器字
|
||||
interface | 2个机器字(type,value)
|
||||
------------------------------- | -----------------------------
|
||||
`bool` | 1个字节
|
||||
`intN, uintN, floatN, complexN` | N/8个字节(例如float64是8个字节)
|
||||
`int, uint, uintptr` | 1个机器字
|
||||
`*T` | 1个机器字
|
||||
`string` | 2个机器字(data,len)
|
||||
`[]T` | 3个机器字(data,len,cap)
|
||||
`map` | 1个机器字
|
||||
`func` | 1个机器字
|
||||
`chan` | 1个机器字
|
||||
`interface` | 2个机器字(type,value)
|
||||
|
||||
Go语言的规范并没有要求一个字段的声明顺序和内存中的顺序是一致的,所以理论上一个编译器可以随意地重新排列每个字段的内存位置,虽然在写作本书的时候编译器还没有这么做。下面的三个结构体虽然有着相同的字段,但是第一种写法比另外的两个需要多50%的内存。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第13章 底层编程
|
||||
# 第十三章 底层编程
|
||||
|
||||
Go语言的设计包含了诸多安全策略,限制了可能导致程序运行出错的用法。编译时类型检查可以发现大多数类型不匹配的操作,例如两个字符串做减法的错误。字符串、map、slice和chan等所有的内置类型,都有严格的类型转换规则。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第二章 程序结构
|
||||
# 第二章 程序结构
|
||||
|
||||
Go语言和其他编程语言一样,一个大的程序是由很多小的基础构件组成的。变量保存值,简单的加法和减法运算被组合成较复杂的表达式。基础类型被聚合为数组或结构体等更复杂的数据结构。然后使用if和for之类的控制语句来组织和控制表达式的执行流程。然后多个语句被组织到一个个函数中,以便代码的隔离和复用。函数以源文件和包的方式被组织。
|
||||
|
||||
|
@ -12,7 +12,7 @@ Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unic
|
||||
|
||||
不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。
|
||||
|
||||
其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的值域是从$$-2^{n-1}$$到$$2^{n-1}-1$$。无符号整数的所有bit位都用于表示非负数,值域是0到$$2^n-1$$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。
|
||||
其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的值域是从$-2^{n-1}$到$2^{n-1}-1$。无符号整数的所有bit位都用于表示非负数,值域是0到$2^n-1$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。
|
||||
|
||||
下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照先级递减的顺序的排列:
|
||||
|
||||
@ -101,7 +101,7 @@ fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
|
||||
|
||||
(6.5节给出了一个可以远大于一个字节的整数集的实现。)
|
||||
|
||||
在`x<<n`和`x>>n`移位运算中,决定了移位操作bit数部分必须是无符号数;被操作的x数可以是有符号或无符号数。算术上,一个`x<<n`左移运算等价于乘以$$2^n$$,一个`x>>n`右移运算等价于除以$$2^n$$。
|
||||
在`x<<n`和`x>>n`移位运算中,决定了移位操作bit数部分必须是无符号数;被操作的x数可以是有符号或无符号数。算术上,一个`x<<n`左移运算等价于乘以$2^n$,一个`x>>n`右移运算等价于除以$2^n$。
|
||||
|
||||
左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。
|
||||
|
||||
@ -114,7 +114,7 @@ for i := len(medals) - 1; i >= 0; i-- {
|
||||
}
|
||||
```
|
||||
|
||||
另一个选择对于上面的例子来说将是灾难性的。如果len函数返回一个无符号数,那么i也将是无符号的uint类型,然后条件`i >= 0`则永远为真。在三次迭代之后,也就是`i == 0`时,i--语句将不会产生-1,而是变成一个uint类型的最大值(可能是$$2^64-1$$),然后medals[i]表达式将发生运行时panic异常(§5.9),也就是试图访问一个slice范围以外的元素。
|
||||
另一个选择对于上面的例子来说将是灾难性的。如果len函数返回一个无符号数,那么i也将是无符号的uint类型,然后条件`i >= 0`则永远为真。在三次迭代之后,也就是`i == 0`时,i--语句将不会产生-1,而是变成一个uint类型的最大值(可能是$2^64-1$),然后medals[i]表达式将发生运行时panic异常(§5.9),也就是试图访问一个slice范围以外的元素。
|
||||
|
||||
出于这个原因,无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。
|
||||
|
||||
|
@ -89,7 +89,7 @@ func mandelbrot(z complex128) color.Color {
|
||||
|
||||
**练习 3.6:** 升采样技术可以降低每个像素对计算颜色值和平均值的影响。简单的方法是将每个像素分成四个子像素,实现它。
|
||||
|
||||
**练习 3.7:** 另一个生成分形图像的方式是使用牛顿法来求解一个复数方程,例如$$z^4-1=0$$。每个起点到四个根的迭代次数对应阴影的灰度。方程根对应的点用颜色表示。
|
||||
**练习 3.7:** 另一个生成分形图像的方式是使用牛顿法来求解一个复数方程,例如$z^4-1=0$。每个起点到四个根的迭代次数对应阴影的灰度。方程根对应的点用颜色表示。
|
||||
|
||||
**练习 3.8:** 通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64、complex128、big.Float和big.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少?
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
\\ 反斜杠
|
||||
```
|
||||
|
||||
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但是不能超过`\377`(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。
|
||||
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是`\xhh`,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是`\ooo`,包含三个八进制的o数字(0到7),但是不能超过`\377`(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。
|
||||
|
||||
一个原生的字符串面值形式是\`...\`,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值内部是无法直接写\`字符的,可以用八进制或十六进制转义或+"\`"链接字符串常量完成)。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的,包括那些把回车也放入文本文件的系统(译注:Windows系统会把回车和换行一起放入文本文件中)。
|
||||
|
||||
|
@ -13,7 +13,7 @@ UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码
|
||||
|
||||
Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数组,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。
|
||||
|
||||
有很多Unicode字符很难直接从键盘输入,并且还有很多字符有着相似的结构;有一些甚至是不可见的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值:
|
||||
有很多Unicode字符很难直接从键盘输入,并且还有很多字符有着相似的结构;有一些甚至是不可见的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:`\uhhhh`对应16bit的码点值,`\Uhhhhhhhh`对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值:
|
||||
|
||||
```
|
||||
"世界"
|
||||
@ -30,7 +30,7 @@ Unicode转义也可以使用在rune字符中。下面三个字符是等价的:
|
||||
'世' '\u4e16' '\U00004e16'
|
||||
```
|
||||
|
||||
对于小于256码点值可以写在一个十六进制转义字节中,例如'\x41'对应字符'A',但是对于更大的码点则必须使用\u或\U转义形式。因此,'\xe4\xb8\x96'并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。
|
||||
对于小于256码点值可以写在一个十六进制转义字节中,例如`\x41`对应字符'A',但是对于更大的码点则必须使用`\u`或`\U`转义形式。因此,`\xe4\xb8\x96`并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。
|
||||
|
||||
得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀:
|
||||
|
||||
@ -115,7 +115,7 @@ for range s {
|
||||
|
||||
正如我们前面提到的,文本字符串采用UTF8编码只是一种惯例,但是对于循环的真正字符串并不是一个惯例,这是正确的。如果用于循环的字符串只是一个普通的二进制数据,或者是含有错误编码的UTF8数据,将会发送什么呢?
|
||||
|
||||
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符'\uFFFD',在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"<22>"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。
|
||||
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符`\uFFFD`,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"<22>"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。
|
||||
|
||||
UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。
|
||||
|
||||
@ -144,7 +144,7 @@ fmt.Println(string(65)) // "A", not "65"
|
||||
fmt.Println(string(0x4eac)) // "京"
|
||||
```
|
||||
|
||||
如果对应码点的字符是无效的,则用'\uFFFD'无效字符作为替换:
|
||||
如果对应码点的字符是无效的,则用`\uFFFD`无效字符作为替换:
|
||||
|
||||
```Go
|
||||
fmt.Println(string(1234567)) // "<22>"
|
||||
|
@ -55,7 +55,7 @@ func basename(s string) string {
|
||||
}
|
||||
```
|
||||
|
||||
path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。斜杠本身不应该用于文件名,但是在其他一些领域可能会用于文件名,例如URL路径组件。相比之下,path/filepath包则使用操作系统本身的路径规则,例如POSIX系统使用/foo/bar,而Microsoft Windows使用c:\foo\bar等。
|
||||
path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。斜杠本身不应该用于文件名,但是在其他一些领域可能会用于文件名,例如URL路径组件。相比之下,path/filepath包则使用操作系统本身的路径规则,例如POSIX系统使用/foo/bar,而Microsoft Windows使用`c:\foo\bar`等。
|
||||
|
||||
让我们继续另一个字符串的例子。函数的功能是将一个表示整值的字符串,每隔三个字符插入一个逗号分隔符,例如“12345”处理后成为“12,345”。这个版本只适用于整数类型;支持浮点数类型的支持留作练习。
|
||||
|
||||
|
@ -26,7 +26,7 @@ var y float64 = Pi64
|
||||
var z complex128 = complex128(Pi64)
|
||||
```
|
||||
|
||||
对于常量面值,不同的写法可能会对应不同的类型。例如0、0.0、0i和'\u0000'虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。
|
||||
对于常量面值,不同的写法可能会对应不同的类型。例如0、0.0、0i和`\u0000`虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。
|
||||
|
||||
前面说过除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果:
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第三章 基础数据类型
|
||||
# 第三章 基础数据类型
|
||||
|
||||
虽然从底层而言,所有的数据都是由比特组成,但计算机一般操作的是固定大小的数,如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起,就可表达更多的对象,例如数据包、像素点、诗歌,甚至其他任何对象。Go语言提供了丰富的数据组织形式,这依赖于Go语言内置的数据类型。这些内置的数据类型,兼顾了硬件的特性和表达复杂数据结构的便捷性。
|
||||
|
||||
|
@ -6,7 +6,7 @@ Go语言对于这些标准格式的编码和解码都有良好的支持,由标
|
||||
|
||||
JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和对象——Unicode本文编码。它可以用有效可读的方式表示第三章的基础数据类型和本章的数组、slice、结构体和map等聚合数据类型。
|
||||
|
||||
基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过JSON使用的是\Uhhhh转义数字来表示一个UTF-16编码(译注:UTF-16和UTF-8一样是一种变长的编码,有些Unicode码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的rune类型。
|
||||
基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过JSON使用的是`\Uhhhh`转义数字来表示一个UTF-16编码(译注:UTF-16和UTF-8一样是一种变长的编码,有些Unicode码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的rune类型。
|
||||
|
||||
这些基础类型可以通过JSON的数组和对象类型进行递归组合。一个JSON数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个JSON数组可以用于编码Go语言的数组和slice。一个JSON对象是一个字符串到值的映射,写成以系列的name:value对形式,用花括号包含并以逗号分隔;JSON的对象类型可以用于编码Go语言的map类型(key类型是字符串)和结构体。例如:
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第四章 复合数据类型
|
||||
# 第四章 复合数据类型
|
||||
|
||||
在第三章我们讨论了基本数据类型,它们可以用于构建程序中数据结构,是Go语言的世界的原子。在本章,我们将讨论复合数据类型,它是以不同的方式组合基本类型可以构造出来的复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体——同时在本章的最后,我们将演示如何使用结构体来解码和编码到对应JSON格式的数据,并且通过结合使用模板来生成HTML页面。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被成为匿名函数(anonymous function)。
|
||||
|
||||
函数字面量允许我们在使用时函数时,再定义它。通过这种技巧,我们可以改写之前对strings.Map的调用:
|
||||
函数字面量允许我们在使用函数时,再定义它。通过这种技巧,我们可以改写之前对strings.Map的调用:
|
||||
|
||||
```Go
|
||||
strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第五章 函数
|
||||
# 第五章 函数
|
||||
|
||||
函数可以让我们将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用。函数的机制可以让我们将一个大的工作分解为小的任务,这样的小任务可以让不同程序员在不同时间、不同地方独立完成。一个函数同时对用户隐藏了其实现细节。由于这些因素,对于任何编程语言来说,函数都是一个至关重要的部分。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第六章 方法
|
||||
# 第六章 方法
|
||||
|
||||
从90年代早期开始,面向对象编程(OOP)就成为了称霸工程界和教育界的编程范式,所以之后几乎所有大规模被应用的语言都包含了对OOP的支持,go语言也不例外。
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第七章 接口
|
||||
# 第七章 接口
|
||||
|
||||
接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。
|
||||
|
||||
|
@ -8,9 +8,9 @@ func squarer(out, in chan int)
|
||||
func printer(in chan int)
|
||||
```
|
||||
|
||||
其中squarer计算平方的函数在两个串联Channels的中间,因此拥有两个channels类型的参数,一个用于输入一个用于输出。每个channels都用有相同的类型,但是它们的使用方式想反:一个只用于接收,另一个只用于发送。参数的名字in和out已经明确表示了这个意图,但是并无法保证squarer函数向一个in参数对应的channels发送数据或者从一个out参数对应的channels接收数据。
|
||||
其中squarer计算平方的函数在两个串联Channels的中间,因此拥有两个channels类型的参数,一个用于输入一个用于输出。每个channels都用有相同的类型,但是它们的使用方式相反:一个只用于接收,另一个只用于发送。参数的名字in和out已经明确表示了这个意图,但是并无法保证squarer函数向一个in参数对应的channels发送数据或者从一个out参数对应的channels接收数据。
|
||||
|
||||
这种场景是典型的。当一个channel作为一个函数参数是,它一般总是被专门用于只发送或者只接收。
|
||||
这种场景是典型的。当一个channel作为一个函数参数时,它一般总是被专门用于只发送或者只接收。
|
||||
|
||||
为了表明这种意图并防止被滥用,Go语言的类型系统提供了单方向的channel类型,分别用于只发送或只接收的channel。类型`chan<- int`表示一个只发送int的channel,只能发送不能接收。相反,类型`<-chan int`表示一个只接收int的channel,只能接收不能发送。(箭头`<-`和关键字chan的相对位置表明了channel的方向。)这种限制将在编译期检测。
|
||||
|
||||
|
@ -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的调度器机制是紧密相连的,一个发送操作——或许是整个程序——可能会永远阻塞。如果你只是需要一个简单的队列,使用slice就可以了。
|
||||
|
||||
下面的例子展示了一个使用了带缓存channel的应用。它并发地向三个镜像站点发出请求,三个镜像站点分散在不同的地理位置。它们分别将收到的响应发送到带缓存channel,最后接收者只接收第一个收到的响应,也就是最快的那个响应。因此mirroredQuery函数可能在另外两个响应慢的镜像站点响应之前就返回了结果。(顺便说一下,多个goroutines并发地向同一个channel发送数据,或从同一个channel接收数据都是常见的用法。)
|
||||
|
||||
@ -73,7 +73,7 @@ func request(hostname string) (response string) { /* ... */ }
|
||||
|
||||
Channel的缓存也可能影响程序的性能。想象一家蛋糕店有三个厨师,一个烘焙,一个上糖衣,还有一个将每个蛋糕传递到它下一个厨师在生产线。在狭小的厨房空间环境,每个厨师在完成蛋糕后必须等待下一个厨师已经准备好接受它;这类似于在一个无缓存的channel上进行沟通。
|
||||
|
||||
如果在每个厨师之间有一个放置一个蛋糕的额外空间,那么每个厨师就可以将一个完成的蛋糕临时放在那里而马上进入下一个蛋糕在制作中;这类似于将channel的缓存队列的容量设置为1。只要每个厨师的平均工作效率相近,那么其中大部分的传输工作将是迅速的,个体之间细小的效率差异将在交接过程中弥补。如果厨师之间有更大的额外空间——也是就更大容量的缓存队列——将可以在不停止生产线的前提下消除更大的效率波动,例如一个厨师可以短暂地休息,然后在加快赶上进度而不影响其他人。
|
||||
如果在每个厨师之间有一个放置一个蛋糕的额外空间,那么每个厨师就可以将一个完成的蛋糕临时放在那里而马上进入下一个蛋糕在制作中;这类似于将channel的缓存队列的容量设置为1。只要每个厨师的平均工作效率相近,那么其中大部分的传输工作将是迅速的,个体之间细小的效率差异将在交接过程中弥补。如果厨师之间有更大的额外空间——也是就更大容量的缓存队列——将可以在不停止生产线的前提下消除更大的效率波动,例如一个厨师可以短暂地休息,然后再加快赶上进度而不影响其他人。
|
||||
|
||||
另一方面,如果生产线的前期阶段一直快于后续阶段,那么它们之间的缓存在大部分时间都将是满的。相反,如果后续阶段比前期阶段更快,那么它们之间的缓存在大部分时间都将是空的。对于这类场景,额外的缓存并没有带来任何好处。
|
||||
|
||||
|
@ -8,9 +8,9 @@
|
||||
ch := make(chan int) // ch has type 'chan int'
|
||||
```
|
||||
|
||||
和map类似,channel也一个对应make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者何被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
|
||||
和map类似,channel也一个对应make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
|
||||
|
||||
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那么比较的结果为真。一个channel也可以和nil进行比较。
|
||||
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相同的对象,那么比较的结果为真。一个channel也可以和nil进行比较。
|
||||
|
||||
一个channel有发送和接受两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都是用`<-`运算符。在发送语句中,`<-`运算符分割channel和要发送的值。在接收语句中,`<-`运算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的。
|
||||
|
||||
@ -20,7 +20,7 @@ x = <-ch // a receive expression in an assignment statement
|
||||
<-ch // a receive statement; result is discarded
|
||||
```
|
||||
|
||||
Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel之行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话讲产生一个零值的数据。
|
||||
Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话讲产生一个零值的数据。
|
||||
|
||||
使用内置的close函数就可以关闭一个channel:
|
||||
|
||||
|
@ -98,7 +98,7 @@ func makeThumbnails4(filenames []string) error {
|
||||
}
|
||||
```
|
||||
|
||||
这个程序有一个微秒的bug。当它遇到第一个非nil的error时会直接将error返回到调用方,使得没有一个goroutine去排空errors channel。这样剩下的worker goroutine在向这个channel中发送值时,都会永远地阻塞下去,并且永远都不会退出。这种情况叫做goroutine泄露(§8.4.4),可能会导致整个程序卡住或者跑出out of memory的错误。
|
||||
这个程序有一个微妙的bug。当它遇到第一个非nil的error时会直接将error返回到调用方,使得没有一个goroutine去排空errors channel。这样剩下的worker goroutine在向这个channel中发送值时,都会永远地阻塞下去,并且永远都不会退出。这种情况叫做goroutine泄露(§8.4.4),可能会导致整个程序卡住或者跑出out of memory的错误。
|
||||
|
||||
最简单的解决办法就是用一个具有合适大小的buffered channel,这样这些worker goroutine向channel中发送错误时就不会被阻塞。(一个可选的解决办法是创建一个另外的goroutine,当main goroutine返回第一个错误的同时去排空channel)
|
||||
|
||||
|
@ -64,7 +64,7 @@ func main() {
|
||||
```
|
||||
|
||||
|
||||
下面这个例子更微秒。ch这个channel的buffer大小是1,所以会交替的为空或为满,所以只有一个case可以进行下去,无论i是奇数或者偶数,它都会打印0 2 4 6 8。
|
||||
下面这个例子更微妙。ch这个channel的buffer大小是1,所以会交替的为空或为满,所以只有一个case可以进行下去,无论i是奇数或者偶数,它都会打印0 2 4 6 8。
|
||||
|
||||
```go
|
||||
ch := make(chan int, 1)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第八章 Goroutines和Channels
|
||||
# 第八章 Goroutines和Channels
|
||||
|
||||
并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要。Web服务器会一次处理成千上万的请求。平板电脑和手机app在渲染用户画面同时还会后台执行各种计算任务和网络请求。即使是传统的批处理问题--读取数据,计算,写输出--现在也会用并发来隐藏掉I/O的操作延迟以充分利用现代计算机设备的多个核心。计算机的性能每年都在以非线性的速度增长。
|
||||
|
||||
|
@ -8,4 +8,4 @@
|
||||
|
||||
竞争检查器会报告所有的已经发生的数据竞争。然而,它只能检测到运行时的竞争条件;并不能证明之后不会发生数据竞争。所以为了使结果尽量正确,请保证你的测试并发地覆盖到了你到包。
|
||||
|
||||
由于需要额外的记录,因此构建时加了竞争检测的程序跑起来会慢一些,且需要更大的内存,即时是这样,这些代价对于很多生产环境的工作来说还是可以接受的。对于一些偶发的竞争条件来说,让竞争检查器来干活可以节省无数日夜的debugging。(译注:多少服务端C和C艹程序员为此尽折腰)
|
||||
由于需要额外的记录,因此构建时加了竞争检测的程序跑起来会慢一些,且需要更大的内存,即使是这样,这些代价对于很多生产环境的工作来说还是可以接受的。对于一些偶发的竞争条件来说,让竞争检查器来干活可以节省无数日夜的debugging。(译注:多少服务端C和C艹程序员为此竟折腰)
|
||||
|
@ -163,9 +163,6 @@ type Memo struct {
|
||||
|
||||
// Get is concurrency-safe.
|
||||
func (memo *Memo) Get(key string) (value interface{}, err error) {
|
||||
res, ok := memo.cache[key]
|
||||
if !ok {
|
||||
res.value, res.err = memo.f(key)
|
||||
memo.cache[key] = res
|
||||
memo.mu.Lock()
|
||||
res, ok := memo.cache[key]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 第九章 基于共享变量的并发
|
||||
# 第九章 基于共享变量的并发
|
||||
|
||||
前一章我们介绍了一些使用goroutine和channel这样直接而自然的方式来实现并发的方法。然而这样做我们实际上屏蔽掉了在写并发代码时必须处理的一些重要而且细微的问题。
|
||||
|
||||
|
1
links.md
1
links.md
@ -37,7 +37,6 @@
|
||||
[Squeak]: http://doc.cat-v.org/bell_labs/squeak/
|
||||
|
||||
<!-- 系统 -->
|
||||
[Unix]: http://doc.cat-v.org/unix/
|
||||
[UNIX]: http://doc.cat-v.org/unix/
|
||||
[Linux]: http://www.linux.org/
|
||||
[FreeBSD]: https://www.freebsd.org/
|
||||
|
@ -16,6 +16,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
- http://shifei.me/gopl-zh/
|
||||
- http://2goo.info/media/html/gopl-zh-gh-pages/
|
||||
- http://docs.plhwin.com/gopl-zh/
|
||||
- http://gopl-zh.simple-is-best.tk/
|
||||
|
||||
{% include "./version.md" %}
|
||||
|
||||
@ -23,7 +24,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
|
||||
# 译者序
|
||||
|
||||
在上个世纪70年代,贝尔实验室的[Ken Thompson][KenThompson]和[Dennis M. Ritchie][DennisRitchie]合作发明了[UNIX](http://doc.cat-v.org/unix/)操作系统,同时[Dennis M. Ritchie][DennisRitchie]为了解决[UNIX](http://doc.cat-v.org/unix/)系统的移植性问题而发明了C语言,贝尔实验室的[UNIX](http://doc.cat-v.org/unix/)和C语言两大发明奠定了整个现代IT行业最重要的软件基础(目前的三大桌面操作系统的中[Linux](http://www.linux.org/)和[Mac OS X](http://www.apple.com/cn/osx/)都是源于[UINX]()系统,两大移动平台的操作系统iOS和Android也都是源于[UNIX](http://doc.cat-v.org/unix/)系统。C系家族的编程语言占据统治地位达几十年之久)。在[UINX]()和C语言发明40年之后,目前已经在Google工作的[Ken Thompson](http://genius.cat-v.org/ken-thompson/)和[Rob Pike](http://genius.cat-v.org/rob-pike/)(他们在贝尔实验室时就是同事)、还有[Robert Griesemer](http://research.google.com/pubs/author96.html)(设计了V8引擎和HotSpot虚拟机)一起合作,为了解决在21世纪多核和网络化环境下越来越复杂的编程问题而发明了Go语言。从Go语言库早期代码库日志可以看出它的演化历程(Git用`git log --before={2008-03-03} --reverse`命令查看):
|
||||
在上个世纪70年代,贝尔实验室的[Ken Thompson][KenThompson]和[Dennis M. Ritchie][DennisRitchie]合作发明了[UNIX](http://doc.cat-v.org/unix/)操作系统,同时[Dennis M. Ritchie][DennisRitchie]为了解决[UNIX](http://doc.cat-v.org/unix/)系统的移植性问题而发明了C语言,贝尔实验室的[UNIX](http://doc.cat-v.org/unix/)和C语言两大发明奠定了整个现代IT行业最重要的软件基础(目前的三大桌面操作系统的中[Linux](http://www.linux.org/)和[Mac OS X](http://www.apple.com/cn/osx/)都是源于[UNIX]()系统,两大移动平台的操作系统iOS和Android也都是源于[UNIX](http://doc.cat-v.org/unix/)系统。C系家族的编程语言占据统治地位达几十年之久)。在[UNIX]()和C语言发明40年之后,目前已经在Google工作的[Ken Thompson](http://genius.cat-v.org/ken-thompson/)和[Rob Pike](http://genius.cat-v.org/rob-pike/)(他们在贝尔实验室时就是同事)、还有[Robert Griesemer](http://research.google.com/pubs/author96.html)(设计了V8引擎和HotSpot虚拟机)一起合作,为了解决在21世纪多核和网络化环境下越来越复杂的编程问题而发明了Go语言。从Go语言库早期代码库日志可以看出它的演化历程(Git用`git log --before={2008-03-03} --reverse`命令查看):
|
||||
|
||||
![](./images/go-log04.png)
|
||||
|
||||
@ -35,7 +36,7 @@ Go语言圣经 [《The Go Programming Language》](http://gopl.io) 中文版本
|
||||
|
||||
该书英文版约从2015年10月开始公开发售,其中日文版本最早参与翻译和审校(参考致谢部分)。在2015年10月,我们并不知道中文版是否会及时引进、将由哪家出版社引进、引进将由何人来翻译、何时能出版,这些信息都成了一个秘密。中国的Go语言社区是全球最大的Go语言社区,我们从一开始就始终紧跟着Go语言的发展脚步。我们应该也完全有能力以中国Go语言社区的力量同步完成Go语言圣经中文版的翻译工作。与此同时,国内有很多Go语言爱好者也在积极关注该书(本人也在第一时间购买了纸质版本,[亚马逊价格314人民币](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/)。补充:国内也即将出版英文版,[价格79元](http://product.china-pub.com/4912464))。为了Go语言的学习和交流,大家决定合作免费翻译该书。
|
||||
|
||||
翻译工作从2015年11月20日前后开始,到2016年1月底初步完成,前后历时约2个月时间(在其它语言版本中,全球第一个完成翻译的,基本做到和原版同步)。其中,[chai2010](https://github.com/chai2010)翻译了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻译了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻译了第5章,[foreversmart](https://github.com/foreversmart)翻译了第7章,大家共同参与了基本的校验工作,还有其他一些朋友提供了积极的反馈建议。如果大家还有任何问题或建议,可以直接到中文版项目页面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果发现英文版原文在[勘误](http://www.gopl.io/errata.html)中未提到的任何错误,可以直接去[英文版项目](https://github.com/adonovan/gopl.io/)提交。
|
||||
翻译工作从2015年11月20日前后开始,到2016年1月底初步完成,前后历时约2个月时间(在其它语言版本中,全球第一个完成翻译的,基本做到和原版同步)。其中,[chai2010](https://github.com/chai2010)翻译了前言、第2 ~ 4章、第10 ~ 13章,[Xargin](https://github.com/cch123)翻译了第1章、第6章、第8 ~ 9章,[CrazySssst](https://github.com/CrazySssst)翻译了第5章,[foreversmart](https://github.com/foreversmart)翻译了第7章,大家共同参与了基本的校验工作,还有其他一些朋友提供了积极的反馈建议。如果大家还有任何问题或建议,可以直接到中文版项目页面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果发现英文版原文在[勘误](http://www.gopl.io/errata.html)中未提到的任何错误,可以直接去[英文版项目](https://github.com/adonovan/gopl.io/)提交。
|
||||
|
||||
最后,希望这本书能够帮助大家用Go语言快乐地编程。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user