This commit is contained in:
Xargin 2017-05-18 10:49:56 +08:00
commit 0dbb9f4bae
9 changed files with 14 additions and 14 deletions

View File

@ -65,6 +65,8 @@ func (dec *Decoder) Decode(v interface{}) error
第二个工具名字也叫godoc它提供可以相互交叉引用的HTML页面但是包含和`go doc`命令相同以及更多的信息。图10.1演示了time包的文档11.6节将看到godoc演示可以交互的示例程序。godoc的在线服务 https://godoc.org ,包含了成千上万的开源包的检索工具。 第二个工具名字也叫godoc它提供可以相互交叉引用的HTML页面但是包含和`go doc`命令相同以及更多的信息。图10.1演示了time包的文档11.6节将看到godoc演示可以交互的示例程序。godoc的在线服务 https://godoc.org ,包含了成千上万的开源包的检索工具。
![](../images/ch10-01.png)
你也可以在自己的工作区目录运行godoc服务。运行下面的命令然后在浏览器查看 http://localhost:8000/pkg 页面: 你也可以在自己的工作区目录运行godoc服务。运行下面的命令然后在浏览器查看 http://localhost:8000/pkg 页面:
``` ```

View File

@ -4,8 +4,6 @@
有时候,一个中间的状态可能也是有用的,标识符对于一小部分信任的包是可见的,但并不是对所有调用者都可见。例如,当我们计划将一个大的包拆分为很多小的更容易维护的子包,但是我们并不想将内部的子包结构也完全暴露出去。同时,我们可能还希望在内部子包之间共享一些通用的处理包,或者我们只是想实验一个新包的还并不稳定的接口,暂时只暴露给一些受限制的用户使用。 有时候,一个中间的状态可能也是有用的,标识符对于一小部分信任的包是可见的,但并不是对所有调用者都可见。例如,当我们计划将一个大的包拆分为很多小的更容易维护的子包,但是我们并不想将内部的子包结构也完全暴露出去。同时,我们可能还希望在内部子包之间共享一些通用的处理包,或者我们只是想实验一个新包的还并不稳定的接口,暂时只暴露给一些受限制的用户使用。
![](../images/ch10-01.png)
为了满足这些需求Go语言的构建工具对包含internal名字的路径段的包导入路径做了特殊处理。这种包叫internal包一个internal包只能被和internal目录有同一个父目录的包所导入。例如net/http/internal/chunked内部包只能被net/http/httputil或net/http包导入但是不能被net/url包导入。不过net/url包却可以导入net/http/httputil包。 为了满足这些需求Go语言的构建工具对包含internal名字的路径段的包导入路径做了特殊处理。这种包叫internal包一个internal包只能被和internal目录有同一个父目录的包所导入。例如net/http/internal/chunked内部包只能被net/http/httputil或net/http包导入但是不能被net/url包导入。不过net/url包却可以导入net/http/httputil包。
``` ```

View File

@ -73,7 +73,7 @@ fmt.Println(c == Celsius(f)) // "true"!
命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里只说些简单用法。 命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里只说些简单用法。
下面的声明语句Celsius类型的参数c出现在了函数名的前面表示声明的是Celsius类型的一个名叫String的方法该方法返回该类型对象c带着°C温度单位的字符串 下面的声明语句Celsius类型的参数c出现在了函数名的前面表示声明的是Celsius类型的一个名叫String的方法该方法返回该类型对象c带着°C温度单位的字符串
```Go ```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }

View File

@ -1,6 +1,6 @@
### 2.6.1. 导入包 ### 2.6.1. 导入包
在Go语言程序中每个包都有一个全局唯一的导入路径。导入语句中类似"gopl.io/ch2/tempconv"的字符串对应包的导入路径。Go语言的规范并没有定义这些字符串的具体含义或包来自哪里它们是由构建工具来解释的。当使用Go语言自带的go工具箱时第十章一个导入路径代表一个目录中的一个或多个Go源文件。 在Go语言程序中每个包都有一个全局唯一的导入路径。导入语句中类似"gopl.io/ch2/tempconv"的字符串对应包的导入路径。Go语言的规范并没有定义这些字符串的具体含义或包来自哪里它们是由构建工具来解释的。当使用Go语言自带的go工具箱时第十章一个导入路径代表一个目录中的一个或多个Go源文件。
除了包的导入路径每个包还有一个包名包名一般是短小的名字并不要求包名是唯一的包名在包的声明处指定。按照惯例一个包的名字和包的导入路径的最后一个字段相同例如gopl.io/ch2/tempconv包的名字一般是tempconv。 除了包的导入路径每个包还有一个包名包名一般是短小的名字并不要求包名是唯一的包名在包的声明处指定。按照惯例一个包的名字和包的导入路径的最后一个字段相同例如gopl.io/ch2/tempconv包的名字一般是tempconv。

View File

@ -14,7 +14,7 @@ Unicode字符rune类型是和int32等价的类型通常用于表示一个Unic
其中有符号整数采用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语言中关于算术运算、逻辑运算和比较运算的二元运算符它们按照先级递减的顺序排列: 下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符它们按照先级递减的顺序排列:
``` ```
* / % << >> & &^ * / % << >> & &^
@ -30,7 +30,7 @@ Unicode字符rune类型是和int32等价的类型通常用于表示一个Unic
算术运算符`+`、`-`、`*`和`/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3`和`-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25但是5/4的结果是1因为整数除法会向着0方向截断余数。 算术运算符`+`、`-`、`*`和`/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3`和`-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25但是5/4的结果是1因为整数除法会向着0方向截断余数。
如果一个算术运算的结果不管是有符号或者是无符号的如果需要更多的bit位才能正确表示的话就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型而且最左边的bit是1的话那么最终结果可能是负的例如int8的例子 一个算术运算的结果不管是有符号或者是无符号的如果需要更多的bit位才能正确表示的话就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型而且最左边的bit是1的话那么最终结果可能是负的例如int8的例子
```Go ```Go
var u uint8 = 255 var u uint8 = 255
@ -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集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。 出于这个原因无符号数往往只有在位运算或其它特殊的运算场景才会使用就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。

View File

@ -2,4 +2,4 @@
虽然从底层而言所有的数据都是由比特组成但计算机一般操作的是固定大小的数如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起就可表达更多的对象例如数据包、像素点、诗歌甚至其他任何对象。Go语言提供了丰富的数据组织形式这依赖于Go语言内置的数据类型。这些内置的数据类型兼顾了硬件的特性和表达复杂数据结构的便捷性。 虽然从底层而言所有的数据都是由比特组成但计算机一般操作的是固定大小的数如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起就可表达更多的对象例如数据包、像素点、诗歌甚至其他任何对象。Go语言提供了丰富的数据组织形式这依赖于Go语言内置的数据类型。这些内置的数据类型兼顾了硬件的特性和表达复杂数据结构的便捷性。
Go语言将数据类型分为四类基础类型、复合类型、引用类型和接口类型。本章介绍基础类型包括数字、字符串和布尔型。复合数据类型——数组§4.1和结构体§4.2——是通过组合简单类型来表达更加复杂的数据结构。引用类型包括指针§2.3.2、切片§4.2)字典§4.3、函数§5、通道§8虽然数据种类很多但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。我们将在第7章介绍接口类型。 Go语言将数据类型分为四类基础类型、复合类型、引用类型和接口类型。本章介绍基础类型包括数字、字符串和布尔型。复合数据类型——数组§4.1和结构体§4.2——是通过组合简单类型来表达更加复杂的数据结构。引用类型包括指针§2.3.2、切片§4.2)字典§4.3、函数§5、通道§8虽然数据种类很多但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。我们将在第7章介绍接口类型。

View File

@ -2,7 +2,7 @@
在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话)以此类推。在有两个或更多goroutine的程序中每一个goroutine内的语句也是按照既定的顺序去执行的但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序x是在y之前还是之后还是同时发生是没法判断的。当我们能够没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话就说明x和y这两个事件是并发的。 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话)以此类推。在有两个或更多goroutine的程序中每一个goroutine内的语句也是按照既定的顺序去执行的但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序x是在y之前还是之后还是同时发生是没法判断的。当我们能够没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话就说明x和y这两个事件是并发的。
考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,如果这个类型是并发安全的话,那么所有它的访问方法和操作就都是并发安全的。 考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的方法和操作都是并发安全的话,那么类型便是并发安全的。
在一个程序中有非并发安全的类型的情况下我们依然可以使这个程序并发安全。确实并发安全的类型是例外而不是规则所以只有当文档中明确地说明了其是并发安全的情况下你才可以并发地去访问它。我们会避免并发访问大多数的类型无论是将变量局限在单一的一个goroutine内还是用互斥条件维持更高级别的不变性都是为了这个目的。我们会在本章中说明这些术语。 在一个程序中有非并发安全的类型的情况下我们依然可以使这个程序并发安全。确实并发安全的类型是例外而不是规则所以只有当文档中明确地说明了其是并发安全的情况下你才可以并发地去访问它。我们会避免并发访问大多数的类型无论是将变量局限在单一的一个goroutine内还是用互斥条件维持更高级别的不变性都是为了这个目的。我们会在本章中说明这些术语。
@ -24,7 +24,7 @@ func Balance() int { return balance }
(当然我们也可以把Deposit存款函数写成balance += amount这种形式也是等价的不过长一些的形式解释起来更方便一些。) (当然我们也可以把Deposit存款函数写成balance += amount这种形式也是等价的不过长一些的形式解释起来更方便一些。)
对于这个具体的程序而言,我们可以瞅一眼各种存款和查余额的顺序调用,都能给出正确的结果。也就是说Balance函数会给出之前的所有存入的额度之和。然而当我们并发地而不是顺序地调用这些函数的话Balance就再也没办法保证结果正确了。考虑一下下面的两个goroutine其代表了一个银行联合账户的两笔交易 对于这个简单的程序而言我们一眼就能看出以任意顺序调用函数Deposit和Balance都会得到正确的结果。也就是说Balance函数会给出之前的所有存入的额度之和。然而当我们并发地而不是顺序地调用这些函数的话Balance就再也没办法保证结果正确了。考虑一下下面的两个goroutine其代表了一个银行联合账户的两笔交易
```go ```go
// Alice: // Alice:
@ -145,7 +145,7 @@ func init() {
} }
``` ```
即使当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine绑定依然是并发问题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为在这两者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下一阶段再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。 即使当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine绑定依然是并发问题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为在这两者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下一阶段再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。
下面的例子中Cakes会被严格地顺序访问先是baker gorouine然后是icer gorouine 下面的例子中Cakes会被严格地顺序访问先是baker gorouine然后是icer gorouine

View File

@ -50,7 +50,7 @@ func Balance() int {
每次一个goroutine访问bank变量时(这里只有balance余额变量)它都会调用mutex的Lock方法来获取一个互斥锁。如果其它的goroutine已经获得了这个锁的话这个操作会被阻塞直到其它goroutine调用了Unlock使该锁变回可用状态。mutex会保护共享变量。惯例来说被mutex所保护的变量是在mutex变量声明之后立刻声明的。如果你的做法和惯例不符确保在文档里对你的做法进行说明。 每次一个goroutine访问bank变量时(这里只有balance余额变量)它都会调用mutex的Lock方法来获取一个互斥锁。如果其它的goroutine已经获得了这个锁的话这个操作会被阻塞直到其它goroutine调用了Unlock使该锁变回可用状态。mutex会保护共享变量。惯例来说被mutex所保护的变量是在mutex变量声明之后立刻声明的。如果你的做法和惯例不符确保在文档里对你的做法进行说明。
在Lock和Unlock之间的代码段中的内容goroutine可以随便读取或者修改这个代码段叫做临界区。goroutine在结束后释放锁是必要的无论以哪条路径通过函数都需要释放即使是在错误路径中也要记得释放。 在Lock和Unlock之间的代码段中的内容goroutine可以随便读取或者修改这个代码段叫做临界区。锁的持有者在其他goroutine获取该锁之前需要调用Unlock。goroutine在结束后释放锁是必要的无论以哪条路径通过函数都需要释放即使是在错误路径中也要记得释放。
上面的bank程序例证了一种通用的并发模式。一系列的导出函数封装了一个或多个变量那么访问这些变量唯一的方式就是通过这些函数来做(或者方法,对于一个对象的变量来说)。每一个函数在一开始就获取互斥锁并在最后释放锁从而保证共享变量不会被并发访问。这种函数、互斥锁和变量的编排叫作监控monitor(这种老式单词的monitor是受"monitor goroutine"的术语启发而来的。两种用法都是一个代理人保证变量被顺序访问)。 上面的bank程序例证了一种通用的并发模式。一系列的导出函数封装了一个或多个变量那么访问这些变量唯一的方式就是通过这些函数来做(或者方法,对于一个对象的变量来说)。每一个函数在一开始就获取互斥锁并在最后释放锁从而保证共享变量不会被并发访问。这种函数、互斥锁和变量的编排叫作监控monitor(这种老式单词的monitor是受"monitor goroutine"的术语启发而来的。两种用法都是一个代理人保证变量被顺序访问)。

View File

@ -87,7 +87,7 @@ func Icon(name string) image.Image {
``` ```
上面的代码有两个临界区。goroutine首先会获取一个查询map然后释放锁。如果条目被找到了(一般情况下)那么会直接返回。如果没有找到那goroutine会获取一个写锁。不释放共享锁的话也没有任何办法来将一个共享锁升级为一个互斥锁所以我们必须重新检查icons变量是否为nil以防止在执行这一段代码的时候icons变量已经被其它gorouine初始化过了。 上面的代码有两个临界区。goroutine首先会获取一个查询map然后释放锁。如果条目被找到了(一般情况下)那么会直接返回。如果没有找到那goroutine会获取一个写锁。不释放共享锁的话也没有任何办法来将一个共享锁升级为一个互斥锁所以我们必须重新检查icons变量是否为nil以防止在执行这一段代码的时候icons变量已经被其它gorouine初始化过了。
上面的模板使我们的程序能够更好的并发但是有一点太复杂且容易出错。幸运的是sync包为我们提供了一个专门的方案来解决这种一次性初始化的问题sync.Once。概念上来讲一次性的初始化需要一个互斥量mutex和一个boolean变量来记录初始化是不是已经完成了互斥量用来保护boolean变量和客户端数据结构。Do这个唯一的方法需要接收初始化函数作为其参数。让我们用sync.Once来简化前面的Icon函数吧 上面的模板使我们的程序能够更好的并发但是有一点太复杂且容易出错。幸运的是sync包为我们提供了一个专门的方案来解决这种一次性初始化的问题sync.Once。概念上来讲一次性的初始化需要一个互斥量mutex和一个boolean变量来记录初始化是不是已经完成了互斥量用来保护boolean变量和客户端数据结构。Do这个唯一的方法需要接收初始化函数作为其参数。让我们用sync.Once来简化前面的Icon函数吧
@ -101,6 +101,6 @@ func Icon(name string) image.Image {
} }
``` ```
每一次对Do(loadIcons)的调用都会锁定mutex并会检查boolean变量。在第一次调用时变量的值是falseDo会调用loadIcons并会将boolean设置为true。随后的调用什么都不会做但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 每一次对Do(loadIcons)的调用都会锁定mutex并会检查boolean变量。在第一次调用时boolean变量的值是falseDo会调用loadIcons并会将boolean变量设置为true。随后的调用什么都不会做但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话我们能够避免在变量被构建完成之前和其它goroutine共享该变量。
**练习 9.2** 重写2.6.2节中的PopCount的例子使用sync.Once只在第一次需要用到的时候进行初始化。(虽然实际上对PopCount这样很小且高度优化的函数进行同步可能代价没法接受) **练习 9.2** 重写2.6.2节中的PopCount的例子使用sync.Once只在第一次需要用到的时候进行初始化。(虽然实际上对PopCount这样很小且高度优化的函数进行同步可能代价没法接受)