1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

ch1: 规范化内部段落编号

This commit is contained in:
chai2010 2018-08-05 08:01:21 +08:00
parent 2c6bdbc83b
commit 23ba7def78
7 changed files with 41 additions and 40 deletions

View File

@ -14,7 +14,7 @@ Go语言很多时候被描述为“类C语言”或者是“21世纪的C语
Go语言其它的一些特性零散地来自于其他一些编程语言比如iota语法是从APL语言借鉴词法作用域与嵌套函数等特性来自于Scheme语言和其他很多编程语言。Go语言中也有很多自己发明创新的设计。比如Go语言的切片为轻量级动态数组提供了有效的随机存取的性能这可能会让人联想到链表的底层的共享机制。还有Go语言新发明的defer语句Ken发明也是神来之笔。
## 来自贝尔实验室特有基因
## 1.1.1 来自贝尔实验室特有基因
作为Go语言标志性的并发编程特性则来自于贝尔实验室的Tony Hoare于1978年发表鲜为外界所知的关于并发研究的基础文献顺序通信进程 communicating sequential processes 缩写为CSP。在最初的CSP论文中程序只是一组没有中间共享状态的平行运行的处理过程它们之间使用管道进行通信和控制同步。Tony Hoare的CSP并发模型只是一个用于描述并发性基本概念的描述语言它并不是一个可以编写可执行程序的通用编程语言。
@ -32,7 +32,7 @@ CSP并发模型最经典的实际应用是来自爱立信发明的Erlang编程
纵观整个贝尔实验室的编程语言的发展进程从B语言、C语言、Newsqueak、Alef、Limbo语言一路走来Go语言继承了来着贝尔实验室的半个世纪的软件设计基因终于完成了C语言革新的使命。纵观这几年来的发展趋势Go语言已经成为云计算、云存储时代最重要的基础编程语言。
## 你好, 世界
## 1.1.2 你好, 世界
按照惯例介绍所有编程语言的第一个程序都是“Hello, World!”。虽然本教假设读者已经了解了Go语言但是我们还是不想打破这个惯例因为这个传统正是从Go语言的前辈C语言传承而来的。不过Go语言的这个程序输出的是中文“你好, 世界!”。

View File

@ -4,7 +4,7 @@
![](../images/ch1-01-go-history.png)
## B语言 - Ken Thompson, 1972
## 1.2.1 B语言 - Ken Thompson, 1972
首先是B语言B语言是Go语言之父贝尔实验室的Ken Thompson早年间开发的一种通用的程序设计语言设计目的是为了用于辅助UNIX系统的开发。但是因为B语言缺乏灵活的类型系统导致使用比较困难。后来Ken Thompson的同事Dennis Ritchie以B语言为基础开发出了C语言C语言提供了丰富的类型极大地增加了语言的表达能力。到目前为止它依然是世界上最常用的程序语言之一。而B语言自从被它取代之后则就只存在于各种文献之中成为了历史。
@ -25,7 +25,7 @@ c 'orld';
总体来说B语言简单功能也比较简陋。
## C语言 - Dennis Ritchie, 1974 ~ 1989
## 1.2.2 C语言 - Dennis Ritchie, 1974 ~ 1989
C语言是由Dennis Ritchie在B语言的基础上改进而来它增加了丰富的数据类型并最终实现了用它重写UNIX的伟大目标。C语言可以说是现代IT行业最重要的软件基石目前主流的操作系统几乎全部是由C语言开发的许多基础系统软件也是C语言开发的。C系家族的编程语言占据统治地位达几十年之久半个多世纪以来依然充满活力。
@ -76,7 +76,7 @@ main(void)
至此C语言本身的进化基本完成。后面的C92/C99/C11都只是针对一些语言细节做了完善。因为各种历史因素C89依然是使用最广泛的标准。
## Newsqueak - Rob Pike, 1989
## 1.2.3 Newsqueak - Rob Pike, 1989
Newsqueak是Rob Pike发明的老鼠语言的第二代是他用于实践CSP并发编程模型的战场。Newsqueak是新的squeak语言的意思其中squeak是老鼠吱吱吱的叫声也可以看作是类似鼠标点击的声音。Squeak是一个提供鼠标和键盘事件处理的编程语言Squeak语言的管道是静态创建的。改进版的Newsqueak语言则提供了类似C语言语句和表达式的语法和类似Pascal语言的推导语法。Newsqueak是一个带自动垃圾回收的纯函数式语言它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中管道是动态创建的属于第一类值因此可以保存到变量中。
@ -140,7 +140,7 @@ prime := sieve();
Newsqueak语言中并发体和管道的语法和Go语言已经比较接近了后置的类型声明和Go语言的语法也很相似。
## Alef - Phil Winterbottom, 1993
## 1.2.4 Alef - Phil Winterbottom, 1993
在Go语言出现之前Alef语言是作者心中比较完美的并发语言Alef语法和运行时基本是无缝兼容C语言。Alef语言中的对线程和进程的并发体都提供了支持其中`proc receive(c)`用于启动一个进程,`task receive(c)`用于启动一个线程,它们之间通过管道`c`进行通讯。不过由于Alef缺乏内存自动回收机制导致并发体的内存资源管理异常复杂。而且Alef语言只在Plan9系统中提供过短暂的支持其它操作系统并没有实际可以运行的Alef开发环境。而且Alef语言只有《Alef语言规范》和《Alef编程向导》两个公开的文档因此在贝尔实验室之外关于Alef语言的讨论并不多。
@ -176,7 +176,7 @@ void main(void) {
Alef的语法和C语言基本保持一致可以认为它是在C语言的语法基础上增加了并发编程相关的特性可以看作是另一个维度的C++语言。
## Limbo - Sean Dorward, Phil Winterbottom, Rob Pike, 1995
## 1.2.5 Limbo - Sean Dorward, Phil Winterbottom, Rob Pike, 1995
Limbo地狱是用于开发运行在小型计算机上的分布式应用的编程语言它支持模块化编程编译期和运行时的强类型检查进程内基于具有类型的通信管道原子性垃圾收集和简单的抽象数据类型。Limbo被设计为即便是在没有硬件内存保护的小型设备上也能安全运行。Limbo语言主要运行在Inferno系统之上。
@ -202,11 +202,11 @@ init(ctxt: ref Draw->Context, args: list of string)
从这个版本的“Hello World”程序中我们已经可以发现很多Go语言特性的雏形。第一句`implement Hello;`基本对应Go语言的`package Hello`包声明语句。然后是`include "sys.m"; sys: Sys;``include "draw.m";`语句用于导入其它的模块类似Go语言的`import "sys"``import "draw"`语句。然后Hello包模块还提供了模块初始化函数`init`并且函数的参数的类型也是后置的不过Go语言的初始化函数是没有参数的。
## Go语言 - 2007~2009
## 1.2.6 Go语言 - 2007~2009
贝尔实验室后来经历了多次动荡包括Ken Thompson在内的Plan9项目原班人马最终加入了Google公司。在发明Limbo等前辈语言诞生10多年之后在2007年底Go语言三个最初的作者因为偶然的因素聚集到一起批斗C++传说是C++语言的布道师在Google公司到处鼓吹的C++11各种牛逼特性彻底惹恼了他们他们终于抽出了20%的自由时间创造了Go语言。最初的Go语言规范从2008年3月开始编写最初的Go程序也是直接编译到C语言然后再二次编译为机器码。到了2008年5月Google公司的领导们终于发现了Go语言的巨大潜力从而开始全力支持这个项目Google的创始人甚至还贡献了`func`关键字让他们可以将全部工作时间投入到Go语言的设计和开发中。在Go语言规范初版完成之后Go语言的编译器终于可以直接生成机器码了。
### hello.go - 2008年6月
### 1.2.6.1 hello.go - 2008年6月
```go
package main
@ -219,7 +219,7 @@ func main() int {
这是初期Go语言程序正式开始测试的版本。其中内置的用于调试的`print`语句已经存在,不过是以命令的方式使用。入口`main`函数还和C语言中的`main`函数一样返回`int`类型的值,而且需要`return`显式地返回值。每个语句末尾的分号也还存在。
### hello.go - 2008年6月27日
### 1.2.6.2 hello.go - 2008年6月27日
```go
package main
@ -231,7 +231,7 @@ func main() {
入口函数`main`已经去掉了返回值,程序默认通过隐式调用`exit(0)`来返回。Go语言朝着简单的方向逐步进化。
### hello.go - 2008年8月11日
### 1.2.6.3 hello.go - 2008年8月11日
```go
package main
@ -243,7 +243,7 @@ func main() {
用于调试的内置的`print`由开始的命令改为普通的内置函数,使得语法更加简单一致。
### hello.go - 2008年10月24日
### 1.2.6.4 hello.go - 2008年10月24日
```go
package main
@ -257,7 +257,7 @@ func main() {
作为C语言中招牌的`printf`格式化函数已经移植了到了Go语言中函数放在`fmt`包中(`fmt`是格式化单词`format`的缩写)。不过`printf`函数名的开头字母依然是小写字母,采用大写字母表示导出的特性还没有出现。
### hello.go - 2009年1月15日
### 1.2.6.5 hello.go - 2009年1月15日
```go
package main
@ -271,7 +271,7 @@ func main() {
Go语言开始采用是否大小写首字母来区分符号是否可以被导出。大写字母开头表示导出的公共符号小写字母开头表示包内部的私有符号。国内用户需要注意的是汉字中没有大小写字母的概念因此以汉字开头的符号目前是无法导出的针对问题中国用户已经给出相关建议等Go2之后或许会调整对汉字的导出规则
### hello.go - 2009年12月11日
### 1.2.6.7 hello.go - 2009年12月11日
```go
package main
@ -286,7 +286,7 @@ func main() {
Go语言终于移除了语句末尾的分号。这是Go语言在2009年11月10号正式开源之后第一个比较重要的语法改进。从1978年C语言教程第一版引入的分号分割的规则到现在Go语言的作者们花了整整32年终于移除了语句末尾的分号。在这32年的演化的过程中必然充满了各种八卦故事我想这一定是Go语言设计者深思熟虑的结果现在Swift等新的语言也是默认忽略分号的可见分号确实并不是那么的重要
## 你好, 世界! - V2.0
## 1.2.7 你好, 世界! - V2.0
在经过半个世纪的涅槃重生之后Go语言不仅仅打印出了Unicode版本的“Hello, World”而且可以方便地向全球用户提供打印服务。下面版本通过`http`服务向每个访问的客户端打印中文的“你好, 世界!”和当前的时间信息。

View File

@ -4,7 +4,7 @@
Go语言中数组、字符串和切片三者是密切相关的数据结构。这三种数据类型在底层原始数据有着相同的内存结构在上层因为语法的限制而有着不同的行为表现。首先Go语言的数组是一种值类型虽然数组的元素可以被修改但是数组本身的赋值和函数传参都是以整体复制的方式处理的。Go语言字符串底层数据也是对应的字节数组但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度而不会导致底层数据的复制。切片的行为更为灵活切片的结构和字符串结构类似但是解除了只读限制。切片的底层数据虽然也是对应数据类型的数组但是每个切片还有独立的长度和容量信息切片赋值和函数传参数时也是将切片头信息部分按传值方式处理。因为切片头含有底层数据的指针所以它的赋值也不会导致底层数据的复制。其实Go语言的赋值和函数传参规则很简单除了闭包函数以引用的方式对外部变量访问之外其它赋值和函数传参数都是以传值的方式处理。要理解数组、字符串和切片三种不同的处理方式的原因需要详细了解它们的底层数据结构。
## 数组
## 1.3.1 数组
数组是一个由固定长度的特定类型元素组成的序列一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一个部分不同长度或不同类型的数据组成的数组都是不同的类型因此在Go语言中很少直接使用数组不同长度的数组因为类型不同无法直接赋值。和数组对应的类型是切片切片是可以动态增长和收缩的序列切片的功能也更加灵活但是要理解切片的工作原理还是要先理解数组。
@ -141,7 +141,7 @@ var f = [...]int{} // 定义一个长度为0的数组
在Go语言中数组类型是切片和字符串等结构的基础。以上数组的很多操作都可以直接用于字符串或切片中。
## 字符串
## 1.3.2 字符串
一个字符串是一个不可改变的字节序列字符串通常是用来包含人类可读的文本数据。和数组不同的是字符串的元素不可修改是一个只读的字节数组。每个字符串的长度虽然也是固定的但是字符串的长度并不是字符串类型的一部分。由于Go语言的源代码要求是UTF8编码导致Go源代码中出现的字符串面值常量一般也是UTF8编码的。源代码中的文本字符串通常被解释为采用UTF8编码的Unicode码点rune序列。因为字节序列对应的是只读的字节序列因此字符串可以包含任意的数据包括byte值0。我们也可以用字符串表示GBK等非UTF8编码的数据不过这种时候将字符串看作是一个只读的二进制数组更准确因为`for range`等语法并不能支持非UTF8编码的字符串的遍历。
@ -344,7 +344,7 @@ func runes2string(s []int32) string {
同样因为底层内存结构的差异,`[]rune`到字符串的转换也必然会导致重新构造字符串。这种强制转换并不存在前面提到的优化情况。
## 切片(slice)
## 1.3.3 切片(slice)
简单地说切片就是一种简化版的动态数组。因为动态数组的长度是不固定切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方但是数组的类型和操作都不够灵活因此在Go代码中数组使用的并不多。而切片则使用得相当广泛理解切片的原理和用法是一个Go程序员的必备技能。

View File

@ -8,7 +8,7 @@ Go语言程序的初始化和执行总是从`main.main`函数开始的。但是
要注意的是,在`main.main`函数执行之前所有代码都运行在同一个goroutine也就是程序的主系统线程中。因此如果某个`init`函数内部用go关键字启动了新的goroutine的话新的goroutine只有在进入`main.main`函数之后才可能被执行到。
## 函数
## 1.4.1 函数
在Go语言中函数是第一类对象我们可以将函数保持到变量中。函数主要有具名和匿名之分包级函数一般都是具名函数具名函数是匿名函数的一种特例。当然Go语言中每个类型还可以有自己的方法方法其实也是函数的一种。
@ -157,7 +157,7 @@ func g() int {
第一个函数直接返回了函数参数变量的地址——这似乎是不可以的因为如果参数变量在栈上的话函数返回之后栈变量就失效了返回的地址自然也应该失效了。但是Go语言的编译器和运行时比我们聪明的多它会保证指针指向的变量在合适的地方。第二个函数内部虽然调用`new`函数创建了`*int`类型的指针对象但是依然不知道它具体保存在哪里。对于有C/C++编程经验的程序员需要强调的是不用关心Go语言中函数栈和堆的问题编译器和运行时会帮我们搞定同样不要假设变量在内存中的位置是固定不变的指针随时可能会变化特别是在你不期望它变化的时候。
## 方法
## 1.4.2 方法
方法一般是面向对象编程(OOP)的一个特性在C++语言中方法对应一个类对象的成员函数是关联到具体对象上的虚表中的。但是Go语言的方法却是关联到类型的这样可以在编译阶段完成方法的静态绑定。一个面向对象的程序会用方法来表达其属性和对应的操作这样使用这个对象的用户就不需要直接去操作对象而是借助方法来做这些事情。面向对象编程(OOP)进入主流开发领域一般认为是从C++开始的C++就是在兼容C语言的基础之上支持了class等面向对象的特性。然后Java编程则号称是纯粹的面向对象语言因为Java中函数是不能独立存在的每个函数都必然是属于某个类的。
@ -319,7 +319,7 @@ func (p *Cache) Lookup(key string) string {
在传统的面向对象语言(eg.C++或Java)的继承中,子类的方法是在运行时动态绑定到对象的,因此基类实现的某些方法看到的`this`可能不是基类类型对应的对象这个特性会导致基类方法运行的不确定性。而在Go语言通过嵌入匿名的成员来“继承”的基类方法`this`就是实现该方法的类型的对象Go语言中方法是编译时静态绑定的。如果需要虚函数的多态特性我们需要借助Go语言接口来实现。
## 接口
## 1.4.3 接口
Go语言之父Rob Pike曾说过一句名言那些试图避免白痴行为的语言最终自己变成了白痴语言Languages that try to disallow idiocy become themselves idiotic。一般静态编程语言都有着严格的类型系统这使得编译器可以深入检查程序员没有作出什么出格的举动。但是过于严格的类型系统却会使得编程太过繁琐让程序员把大好的青春都浪费在了和编译器的斗争中。Go语言试图让程序员能在安全和灵活的编程之间取得一个平衡。它在提供严格的类型检查的同时通过接口类型实现了对鸭子类型的支持使得安全动态的编程变得相对容易。

View File

@ -6,7 +6,7 @@
常见的并行编程有多种模型主要有多线程、消息传递等。从理论上来看多线程和基于消息的并发编程是等价的。由于多线程并发模型可以自然对应到多核的处理器主流的操作系统因此也都提供了系统级的多线程支持同时从概念上讲多线程似乎也更直观因此多线程编程模型逐步被吸纳到主流的编程语言特性或语言扩展库中。而主流编程语言对基于消息的并发编程模型支持则相比较少Erlang语言是支持基于消息传递并发编程模型的代表者它的并发体之间不共享内存。Go语言是基于消息并发模型的集大成者它将基于CSP模型的并发编程内置到了语言中通过一个go关键字就可以轻易地启动一个Goroutine与Erlang不同的是Go语言的Goroutine之间是共享内存的。
## Goroutine和系统线程
## 1.5.1 Goroutine和系统线程
Goroutine是Go语言特有的并发体是一种轻量级的线程由go关键字启动。在真实的Go语言的实现中goroutine和系统线程也不是等价的。尽管两者的区别实际上只是一个量的区别但正是这个量变引发了Go语言并发编程质的飞跃。
@ -16,7 +16,7 @@ Go的运行时还包含了其自己的调度器这个调度器使用了一些
在Go语言中启动一个Goroutine不仅和调用函数一样简单而且Goroutine之间调度代价也很低这些因素极大地促进了并发编程的流行和发展。
## 原子操作
## 1.5.2 原子操作
所谓的原子操作就是并发编程中“最小的且不可并行化”的操作。通常,有多个并发体对一个共享资源的操作是原子操作的话,同一时刻最多只能有一个并发体对该资源进行操作。从线程角度看,在当前线程修改共享资源期间,其它的线程是不能访问该资源的。原子操作对于多线程并发编程模型来说,不会发生有别于单线程的意外情况,共享资源的完整性可以得到保证。
@ -181,7 +181,7 @@ for i := 0; i < 10; i++ {
这是一个简化的生产者、消费者模型:后台线程生成最新的配置信息;前台多个工作者线程获取最新的配置信息。所有线程共享配置信息资源。
## 顺序一致性内存模型
## 1.5.3 顺序一致性内存模型
如果只是想简单地在线程之间进行数据同步的话,原子操作已经为编程人员提供了一些同步保障。不过这种保障有一个前提:顺序一致性的内存模型。要了解顺序一致性,我们先看看一个简单的例子:
@ -253,7 +253,7 @@ func main() {
可以确定后台线程的`mu.Unlock()`必然在`println("你好, 世界")`完成后发生(同一个线程满足顺序一致性),`main`函数的第二个`mu.Lock()`必然在后台线程的`mu.Unlock()`之后发生(`sync.Mutex`保证),此时后台线程的打印工作已经顺利完成了。
## 初始化顺序
## 1.5.4 初始化顺序
前面函数章节中我们已经简单介绍过程序的初始化顺序这是属于Go语言面向并发的内存模型的基础规范。
@ -265,7 +265,7 @@ Go程序的初始化和执行总是从`main.main`函数开始的。但是如果`
因为所有的`init`函数和`main`函数都是在主线程完成,它们也是满足顺序一致性模型的。
## Goroutine的创建
## 1.5.5 Goroutine的创建
`go`语句会在当前Goroutine对应函数返回前创建新的Goroutine. 例如:
@ -285,7 +285,7 @@ func hello() {
执行`go f()`语句创建Goroutine和`hello`函数是在同一个Goroutine中执行, 根据语句的书写顺序可以确定Goroutine的创建发生在`hello`函数返回之前, 但是新创建Goroutine对应的`f()`的执行事件和`hello`函数返回的事件则是不可排序的,也就是并发的。调用`hello`可能会在将来的某一时刻打印`"hello, world"`,也很可能是在`hello`函数执行完成后才打印。
## 基于Channel的通信
## 1.5.6 基于Channel的通信
Channel通信是在Goroutine之间进行同步的主要方法。在无缓存的Channel上的每一次发送操作都有与其对应的接收操作相配对发送和接收操作通常发生在不同的Goroutine上在同一个Goroutine上执行2个操作很容易导致死锁。**无缓存的Channel上的发送操作总在对应的接收操作完成前发生.**
@ -368,7 +368,7 @@ func main() {
最后一句`select{}`是一个空的管道选择语句,该语句会导致`main`线程阻塞,从而避免程序过早退出。还有`for{}``<-make(chan int)`等诸多方法可以达到类似的效果。因为`main`线程被阻塞了,如果需要程序正常退出的话可以通过调用`os.Exit(0)`实现。
## 不靠谱的同步
## 1.5.7 不靠谱的同步
前面我们已经分析过,下面代码无法保证正常打印结果。实际的运行效果也是大概率不能正常输出结果。

View File

@ -11,7 +11,8 @@ Go语言最吸引人的地方是它内建的并发支持。Go语言并发体系
> 不要通过共享内存来通信,而应通过通信来共享内存。
这是更高层次的并发编程哲学(通过管道来传值是Go语言推荐的做法)。虽然像引用计数这类简单的并发问题通过原子操作或互斥锁就能很好地实现,但是通过信道来控制访问能够让你写出更简洁正确的程序。
## 并发版本的Hello world
## 1.6.1 并发版本的Hello world
我们先以在一个新的Goroutine中输出“Hello world”`main`等待后台线程输出工作完成之后退出,这样一个简单的并发程序作为热身。
@ -128,7 +129,7 @@ func main() {
其中`wg.Add(1)`用于增加等待事件的个数,必须确保在后台线程启动之前执行(如果放到后台线程之中执行则不能保证被正常执行到)。当后台线程完成打印工作之后,调用`wg.Done()`表示完成一个事件。`main`函数的`wg.Wait()`是等待全部的事件完成。
## 生产者消费者模型
## 1.6.2 生产者消费者模型
并发编程中最常见的例子就是生产者/消费者模式该模式主要通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。简单地说就是生产者生产一些数据然后放到成果队列中同时消费者从成果队列中来取这些数据。这样就让生产消费变成了异步的两个过程。当成果队列中没有数据时消费者就进入饥饿的等待中而当成果队列中数据已满时生产者则面临因产品挤压导致CPU被剥夺的下岗问题。
@ -181,7 +182,7 @@ func main() {
我们这个例子中有2个生产者并且2个生产者之间并无同步事件可参考它们是并发的。因此消费者输出的结果序列的顺序是不确定的这并没有问题生产者和消费者依然可以相互配合工作。
## 发布订阅模型
## 1.6.3 发布订阅模型
发布订阅publish-and-subscribe模型通常被简写为pubsub模型。在这个模型中消息生产者成为发布者publisher而消息消费者则称对应订阅者subscriber生产者和消费者是MN的关系。在传统生产者和消费者模型中成果是将消息发送到一个队列中而发布/订阅模型则是将消息发布给一个主题。
@ -318,7 +319,7 @@ func main() {
在发布订阅模型中,每条消息都会传送给多个订阅者。发布者通常不会知道、也不关心哪一个订阅者正在接收主题消息。订阅者和发布者可以在运行时动态添加是一种松散的耦合关心,这使得系统的复杂性可以随时间的推移而增长。在现实生活中,不同城市的象天气预报之类的应用就可以应用这个并发模式。
## 控制并发数
## 1.6.4 控制并发数
很多用户在适应了Go语言强大的并发特性之后都倾向于编写最大并发的程序因为这样似乎可以提供最大的性能。在现实中我们行色匆匆但有时却需要我们放慢脚步享受生活并发的程序也是一样有时候我们需要适当地控制并发的程度因为这样不仅仅可给其它的应用/任务让出/预留一定的CPU资源也可以适当降低功耗缓解电池的压力。
@ -382,7 +383,7 @@ func (fs gatefs) Lstat(p string) (os.FileInfo, error) {
我们不仅可以控制最大的并发数目而且可以通过带缓存Channel的使用量和最大容量比例来判断程序运行的并发率。当管道为空的时候可以认为是空闲状态当管道满了时任务是繁忙状态这对于后台一些低级任务的运行是有参考价值的。
## 赢者为王
## 1.6.5 赢者为王
采用并发编程的动机有很多并发编程可以简化问题比如一类问题对应一个处理线程会更简单并发编程还可以提升性能在一个多核CPU上开2个线程一般会比开1个线程快一些。其实对于提升性能而言程序并不是简单地运行速度快就表示用户体验好的很多时候程序能快速响应用户请求才是最重要的当没有用户请求需要处理的时候才合适处理一些低优先级的后台任务。
@ -411,7 +412,7 @@ func main() {
通过适当开启一些冗余的线程,尝试用不同途径去解决同样的问题,最终以赢者为王的方式提升了程序的相应性能。
## 素数筛
## 1.6.6 素数筛
在“Hello world 的革命”一节中我们为了演示Newsqueak的并发特性文中给出了并发版本素数筛的实现。并发版本的素数筛是一个经典的并发例子通过它我们可以更深刻地理解Go语言的并发特性。“素数筛”的原理如图
@ -470,7 +471,7 @@ func main() {
素数筛展示了一种优雅的并发程序结构。但是因为每个并发体处理的任务粒度太细微程序整体的性能并不理想。对于细力度的并发程序CSP模型中固有的消息传递的代价太高了多线程并发模型同样要面临线程启动的代价
## 并发的安全退出
## 1.6.7 并发的安全退出
有时候我们需要通知goroutine停止它正在干的事情特别是当它工作在错误的方向上的时候。Go语言并没有提供在一个直接终止Goroutine的方法由于这样会导致goroutine之间的共享变量落在未定义的状态上。但是如果我们想要退出两个或者任意多个Goroutine怎么办呢
@ -612,7 +613,7 @@ func main() {
现在每个工作者并发体的创建、运行、暂停和退出都是在`main`函数的安全控制之下了。
## context包
## 1.6.8 context包
在Go1.7发布时,标准库增加了一个`context`用来简化对于处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作官方有博文对此做了专门介绍。我们可以用`context`包来重新实现前面的线程安全退出或超时的控制:

View File

@ -43,7 +43,7 @@ func main() {
捕获异常不是最终的目的。如果异常不可预测,直接输出异常信息是最好的处理方式。
## 错误处理策略
## 1.7.1 错误处理策略
让我们演示一个文件复制的例子:函数需要打开两个文件,然后将其中一个文件的内容复制到另一个文件:
@ -107,7 +107,7 @@ func ParseJSON(input string) (s *Syntax, err error) {
Go语言库的实现习惯: 即使在包内部使用了`panic`,但是在导出函数时会被转化为明确的错误值。
# 获取错误的上下文
## 1.7.2 获取错误的上下文
有时候为了方便上层用户理解;很多时候底层实现者会将底层的错误重新包装为新的错误类型返回给用户:
@ -247,7 +247,7 @@ if err != nil {
Go语言中大部分函数的代码结构几乎相同首先是一系列的初始检查用于防止错误发生之后是函数的实际逻辑。
# 错误的错误返回
## 1.7.3 错误的错误返回
Go语言中的错误是一种接口类型。接口信息中包含了原始类型和原始的值。只有当接口的类型和原始的值都为空的时候接口的值才对应`nil`。其实当接口中类型为空的时候,原始值必然也是空的;反之,当接口对应的原始值为空的时候,接口对应的原始类型并不一定为空的。
@ -278,7 +278,7 @@ func returnsError() error {
Go语言作为一个强类型语言不同类型之间必须要显式的转换而且必须有相同的基础类型。但是Go语言中`interface`是一个例外:非接口类型到接口类型,或者是接口类型之间的转换都是隐式的。这是为了支持方便的鸭子面向对象编程,当然会牺牲一定的安全特性。
# 剖析异常
## 1.7.4 剖析异常
`panic`支持抛出任意类型的异常(而不仅仅是`error`类型的错误),`recover`函数调用的返回值和`panic`函数的输入参数类型一致,它们的函数签名如下: