1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-29 08:12:21 +00:00

Merge pull request #2 from chai2010/master

merge with origin
This commit is contained in:
Stupig 2018-07-22 23:11:47 +08:00 committed by GitHub
commit 080131aff4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 418 additions and 453 deletions

View File

@ -1,4 +1 @@
## Expected Behavior 提示:哪一章节的问题,建议如何修改
## Actual Behavior

View File

@ -1,14 +1 @@
## Description 提示:解决了什么问题,也可以讲下理由。
A few sentences describing the overall goals of the pull request's commits.
## Designs
If it's a feature, please write your code designs here.
## Notes to reviewers
Review the following things, including but not limit to:
1. Design
2. Code (styles, magic...)

View File

@ -8,7 +8,7 @@
* [1.5. 面向并发的内存模型](ch1-basic/ch1-05-mem.md) * [1.5. 面向并发的内存模型](ch1-basic/ch1-05-mem.md)
* [1.6. 常见的并发模式](ch1-basic/ch1-06-goroutine.md) * [1.6. 常见的并发模式](ch1-basic/ch1-06-goroutine.md)
* [1.7. 错误和异常](ch1-basic/ch1-07-error-and-panic.md) * [1.7. 错误和异常](ch1-basic/ch1-07-error-and-panic.md)
* [1.8. 配置开发环境](ch1-basic/ch1-08-ide.md) * [1.8. 补充说明](ch1-basic/ch1-08-ext.md)
* [第二章 CGO编程](ch2-cgo/readme.md) * [第二章 CGO编程](ch2-cgo/readme.md)
* [2.1. 快速入门](ch2-cgo/ch2-01-hello-cgo.md) * [2.1. 快速入门](ch2-cgo/ch2-01-hello-cgo.md)
* [2.2. CGO基础](ch2-cgo/ch2-02-basic.md) * [2.2. CGO基础](ch2-cgo/ch2-02-basic.md)
@ -19,9 +19,8 @@
* [2.7. CGO内存模型](ch2-cgo/ch2-07-memory.md) * [2.7. CGO内存模型](ch2-cgo/ch2-07-memory.md)
* [2.8. C++类包装](ch2-cgo/ch2-08-class.md) * [2.8. C++类包装](ch2-cgo/ch2-08-class.md)
* [2.9. 静态库和动态库](ch2-cgo/ch2-09-static-shared-lib.md) * [2.9. 静态库和动态库](ch2-cgo/ch2-09-static-shared-lib.md)
* [2.10. Go实现Python模块](ch2-cgo/ch2-10-py-module.md) * [2.10. 编译和链接参数](ch2-cgo/ch2-10-link.md)
* [2.11. 编译和链接参数](ch2-cgo/ch2-11-link.md) * [2.11. 补充说明](ch2-cgo/ch2-11-ext.md)
* [2.12. 补充说明](ch2-cgo/ch2-12-faq.md)
* [第三章 汇编语言](ch3-asm/readme.md) * [第三章 汇编语言](ch3-asm/readme.md)
* [3.1. 快速入门](ch3-asm/ch3-01-basic.md) * [3.1. 快速入门](ch3-asm/ch3-01-basic.md)
* [3.2. 计算机结构](ch3-asm/ch3-02-arch.md) * [3.2. 计算机结构](ch3-asm/ch3-02-arch.md)
@ -31,7 +30,7 @@
* [3.6. 再论函数](ch3-asm/ch3-06-func-again.md) * [3.6. 再论函数](ch3-asm/ch3-06-func-again.md)
* [3.7. 例子Goroutine ID](ch3-asm/ch3-07-goroutine-id.md) * [3.7. 例子Goroutine ID](ch3-asm/ch3-07-goroutine-id.md)
* [3.8. Delve调试器](ch3-asm/ch3-08-debug.md) * [3.8. Delve调试器](ch3-asm/ch3-08-debug.md)
* [3.9. 补充说明](ch3-asm/ch3-09-faq.md) * [3.9. 补充说明](ch3-asm/ch3-09-ext.md)
* [第四章 RPC和Protobuf](ch4-rpc/readme.md) * [第四章 RPC和Protobuf](ch4-rpc/readme.md)
* [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md) * [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
* [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md) * [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
@ -40,7 +39,7 @@
* [4.5. GRPC进阶](ch4-rpc/ch4-05-grpc-hack.md) * [4.5. GRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
* [4.6. GRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md) * [4.6. GRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
* [4.7. pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md) * [4.7. pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md)
* [4.8. 补充说明](ch4-rpc/ch4-08-faq.md) * [4.8. 补充说明](ch4-rpc/ch4-08-ext.md)
* [第五章 Go和Web](ch5-web/readme.md) * [第五章 Go和Web](ch5-web/readme.md)
* [5.1. Web开发简介](ch5-web/ch5-01-introduction.md) * [5.1. Web开发简介](ch5-web/ch5-01-introduction.md)
* [5.2. Router请求路由](ch5-web/ch5-02-router.md) * [5.2. Router请求路由](ch5-web/ch5-02-router.md)

View File

@ -1,23 +0,0 @@
# 附录B参考资料
## 参考网站
- Go语言官网: https://golang.org
- SWIG官网: http://swig.org
- GopherJS官网: http://www.gopherjs.org
- GRPC官网: http://www.grpc.io
- rsc博客: http://research.swtch.com
## 参考书目
- 《Go语言圣经》: https://gopl.io
- 《Go语言圣经(中文版)》: https://github.com/golang-china/gopl-zh
- 《Go语言·云动力》: http://www.ituring.com.cn/book/1040
- 《Go语言编程》: http://www.ituring.com.cn/book/967
- 《Go语言程序设计》: http://www.ptpress.com.cn/Book.aspx?id=35714
- 《C程序设计语言》: http://product.china-pub.com/14975
- 《汇编语言:基于X86处理器》: http://product.china-pub.com/4934543
- 《现代x86汇编语言程序设计》: http://product.china-pub.com/5006762
- 《深入理解程序设计使用Linux汇编语言》: http://product.china-pub.com/3768972
- 《代码的未来》: http://product.china-pub.com/3767536

105
appendix/draft/ch1-02.md Normal file
View File

@ -0,0 +1,105 @@
## CGO版本
Go语言开源初期就支持通过CGO和C语言保持交互。CGO通过导入一个虚拟的`"C"`包来访问C语言中的函数。下面是CGO版本的“Hello World”程序
```go
package main
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func main() {
msg := C.CString("Hello, World!\n")
defer C.free(unsafe.Pointer(msg))
C.fputs(msg, C.stdout)
}
```
先通过`C.CString`函数将Go语言字符串转为C语言字符串然后调用C语言的`C.fputs`函数向标准输出窗口打印转换后的C字符串。`defer`延迟语句保证程序返回前通过`C.free`释放分配的C字符串。需要注意的是, CGO不支持C语言中的可变参数函数因为Go语言每次函数调用的栈帧大小是固定的而且Go语言中可变参数语法只是切片的一个语法糖而已因此在Go语言中是无法通过CGO访问C语言的`printf`等可变参数函数的。同时CGO只能访问C语言的函数、变量和简单的宏定义常量CGO并不支持访问C++语言的符号C++和C语言符号的名字修饰规则不同CGO采用C语言的名字修饰规则
其实CGO不仅仅用于在Go语言中调用C语言函数还可以用于导出Go语言函数给C语言函数调用。在用Go语言编写生成C语言的静、动态库时也可以用CGO导出对应的接口函数。正是CGO的存在才保证了Go语言和C语言资源的双向互通同时保证了Go语言可以继承C语言已有的庞大的软件资产。
## SWIG版本
Go语言开源初期除了支持通过CGO访问C语言资源外还支持通过SWIG访问C/C++接口。SWIG是从2010年10月04日发布的SWIG-2.0.1版本开始正式支持Go语言的。可以将SWIG看作一个高级的CGO代码自动生成器同时通过生成C语言桥接代码增加了对C++类的支持。下面是SWIG版本的"Hello World"程序:
首先是创建一个`hello.cc`文件,里面有`SayHello`函数用于打印(这里的`SayHello`函数采用C++的名字修饰规则):
```c++
#include <iostream>
void SayHello() {
std::cout << "Hello, World!" << std::endl;
}
```
然后创建一个`hello.swigcxx`文件以SWIG语法导出上面的C++函数`SayHello`:
```swig
%module main
%inline %{
extern void SayHello();
%}
```
然后在Go语言中直接访问`SayHello`函数(首字母自动转为大写字母):
```go
package main
import (
hello "."
)
func main() {
hello.SayHello()
}
```
需要将上述3个文件放到同一个目录中并且`hello.swigcxx`和Go文件对应同一个包。系统除了需要安装Go语言环境外还需要安装对应版本的SWIG工具。最后运行`go build`就可以构建了。
*注: 在Windows系统下, 路径最长为260个字符. 这个程序生成的中间cgo文件可能导致某些文件的绝对路径长度超出Windows系统限制, 可能导致程序构建失败. 这是由于`go build`调用swig和cgo等命令生成中间文件时生成的不合适的超长文件名导致作者提交ISSUE3358Go1.8已经修复)。*
## Go汇编语言版本
Go语言底层使用了自己独有的跨操作系统汇编语言该汇编语言是从Plan9系统的汇编语言演化而来。Go汇编语言并不能独立使用它是属于Go语言的一个组成部分必须以Go语言包的方式被组织。下面是Go汇编语言版本的“Hello World”程序
先创建一个`main.go`文件以Go语言的语法声明包和声明汇编语言对应的函数签名函数签名不能有函数体
```go
package main
func main()
```
然后创建`main_amd64.s`文件对应Go汇编语言实现AMD64架构的`main`函数:
```asm
#include "textflag.h"
#include "funcdata.h"
// "Hello World!\n"
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!\n"
GLOBL text<>(SB),NOPTR,$16
// func main()
TEXT ·main(SB), $16-0
NO_LOCAL_POINTERS
MOVQ $text<>+0(SB), AX
MOVQ AX, (SP)
MOVQ $16, 8(SP)
CALL runtime·printstring(SB)
RET
```
代码中`#include "textflag.h"`语句包含运行时库定义的头文件, 里面含有`NOPTR`/`NO_LOCAL_POINTERS`等基本的宏的定义。`DATA`汇编指令用于定义数据每个数据的宽度必须是1/2/4/8然后`GLOBL`汇编命令在当前文件内导出`text`变量符号。`TEXT ·main(SB), $16-0`用于定义`main`函数,其中`$16-0`表示`main`函数的帧大小是16个字节对应string头的大小用于给`runtime·printstring`函数传递参数),`0`表示`main`函数没有参数和返回值。`main`函数内部通过调用运行时内部的`runtime·printstring(SB)`函数来打印字符串。
Go汇编语言虽然针对每种CPU架构主要有386/AMD64/ARMARM64等有对应的指令和寄存器但是汇编语言的基本语法和函数调用规范是一致的不同操作系统之间用法是一致的。在Go语言标准库中`runtime`运行时库、`math`数学库和`crypto`密码相关的函数很多是采用汇编语言实现的。其中`runtime`运行时库中采用部分汇编语言并不完全是为了性能而是运行时的某些特性功能比如goroutine上下文的切换等无法用纯Go实现因此需要汇编代码实现某些辅助功能。对于普通用户而言Go汇编语言的最大价值在于性能的优化对于性能比较关键的地方可以尝试用Go汇编语言实现终极优化。

258
appendix/draft/ch1-06.md Normal file
View File

@ -0,0 +1,258 @@
## 控制并发数
增加的方法如下:
```go
func (g gate) Len() int { return len(g) }
func (g gate) Cap() int { return cap(g) }
func (g gate) Idle() bool { return len(g) == 0 }
func (g gate) Busy() bool { return len(g) == cap(g) }
func (g gate) Fraction() float64 {
return float64(len(g)) / float64(cap(g))
}
```
然后我们可以在相对空闲的时候处理一些后台低优先级的任务,在并发相对繁忙或超出一定比例的时候提供预警:
```go
func New(fs vfs.FileSystem, gate chan bool) *gatefs {
p := &gatefs{fs, gate}
// 后台监控线程
go func() {
for {
switch {
case p.gate.Idle():
// 处理后台任务
case p.gate.Fraction() >= 0.7:
// 并发预警
default:
time.Sleep(time.Second)
}
}
}()
return p
}
```
这样我们通过后台线程就可以根据程序的状态动态调整自己的工作模式。
## 消费海量的请求
在前面的生产者、消费者并发模型中,只有当生产者和消费的速度近似相等时才会达到最佳的效果,同时通过引入带缓存的管道可以消除因临时效率波动产生的影响。但是当生产者和消费者的速度严重不匹配时,我们是无法通过带缓存的管道来提高性能的(缓存的管道只能延缓问题发生的时间,无法消除速度差异带来的问题)。当消费者无法及时消费生产者的输出时,时间积累会导致问题越来越严重。
对于生产者、消费者并发模型我们当然可以通过降低生产者的产能来避免资源的浪费。但在很多场景中生产者才是核心对象它们生产出各种问题或任务单据这时候产出的问题是必须要解决的、任务单据也是必须要完成的。在现实生活中制造各种生活垃圾的海量人类其实就是垃圾生产者而清理生活垃圾的少量的清洁工就是垃圾消费者。在网络服务中提交POST数据的海量用户则变成了生产者Web后台服务则对应POST数据的消费者。海量生产者的问题也就变成了如何构造一个能够处理海量请求的Web服务假设每分钟百万级请求
在Web服务中用户提交的每个POST请求可以看作是一个Job任务而服务器是通过后台的Worker工作者来消费这些Job任务。当面向海量的Job处理时我们一般可以通过构造一个Worker工作者池来提高Job的处理效率通过一个带缓存的Job管道来接收新的任务请求避免任务请求功能无法响应Job请求接收管道和Worker工作者池通过分发系统来衔接。
我们可以用管道来模拟工作者池:当需要处理一个任务时,先从工作者池取一个工作者,处理完任务之后将工作者返回给工作者池。`WorkerPool`对应工作者池,`Worker`对应工作者。
```go
type WorkerPool struct {
workers []*Worker
pool chan *Worker
}
// 构造工作者池
func NewWorkerPool(maxWorkers int) *WorkerPool {
p := &WorkerPool{
workers: make([]*Worker, maxWorkers)
pool: make(chan *Worker, maxWorkers)
}
// 初始化工作者
for i, _ := range p.workers {
worker := NewWorker(0)
p.workers[i] = worker
p.pool <- worker
}
return p
}
// 启动工作者
func (p *WorkerPool) Start() {
for _, worker := range p.workers {
worker.Start()
}
}
// 停止工作者
func (p *WorkerPool) Stop() {
for _, worker := range p.workers {
worker.Stop()
}
}
// 获取工作者(阻塞)
func (p *WorkerPool) Get() *Worker {
return <-p.pool
}
// 返回工作者
func (p *WorkerPool) Put(w *Worker) {
p.pool <- w
}
```
工作者池通过一个带缓存的管道来提高工作者的管理。当所有工作者都在处理任务时,工作者的获取会阻塞自动有工作者可用为止。
`Worker`对应工作者实现具体任务由后台一个固定的Goroutine完成和外界通过专有的管道通信工作者的私有管道也可以选择带有一定的缓存具体实现如下
```go
type Worker struct {
job chan interface{}
quit chan bool
wg sync.WaitGroup
}
// 构造工作者
func NewWorker(maxJobs int) *Worker {
return &Worker{
job: make(chan interface{}, maxJobs),
quit: make(chan bool),
}
}
// 启动任务
func (w *Worker) Start() {
p.wg.Add(1)
go func() {
defer p.wg.Done()
for {
// 接收任务
// 此时工作中已经从工作者池中取出
select {
case job := <-p.job:
// 处理任务
case <-w.quit:
return
}
}
}()
}
// 关闭任务
func (p *Worker) Stop() {
p.quit <- true
p.wg.Wait()
}
// 提交任务
func (p *Worker) AddJob(job interface{}) {
p.job <- job
}
```
任务的分发系统在`Service`对象中完成:
```go
type Service struct {
workers *WorkerPool
jobs chan interface{}
maxJobs int
wg sync.WaitGroup
}
func NewService(maxWorkers, maxJobs int) *Service {
return &Service {
workers: NewWorkerPool(maxWorkers),
jobs: make(chan interface{}, maxJobs),
}
}
func (p *Service) Start() {
p.jobs = make(chan interface{}, maxJobs)
p.wg.Add(1)
p.workers.Start()
go func() {
defer p.wg.Done()
for job := range p.jobs:
go func(job Job) {
// 从工作者池取一个工作者
worker := p.workers.Get()
// 完成任务后返回给工作者池
defer p.workers.Put(worker)
// 提交任务处理(异步)
worker.AddJob(job)
}(job)
}
}()
}
func (p *Service) Stop() {
p.workers.Stop()
close(p.jobs)
p.wg.Wait()
}
// 提交任务
// 任务管道带较大的缓存, 延缓阻塞的时间
func (p *Service) AddJob(job interface{}) {
p.jobs <- job
}
```
主程序可以是一个web服务器
```go
var (
MaxWorker = os.Getenv("MAX_WORKERS")
MaxQueue = os.Getenv("MAX_QUEUE")
)
func main() {
service := NewService(MaxWorker, MaxQueue)
service.Start()
defer service.Stop()
// 处理海量的任务
http.HandleFunc("/jobs", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Job以JSON格式提交
var jobs []Job
err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(&jobs)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusBadRequest)
return
}
// 处理任务
for _, job := range jobs {
service.AddJob(job)
}
// OK
w.WriteHeader(http.StatusOK)
})
// 启动web服务
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
基于Go语言特有的管道和Goroutine特性我们以非常简单的方式设计了一个针对海量请求的处理系统结构。在实际的系统中用户可以根据任务的具体类型和特性将管道定义为具体类型以避免接口等动态特性导致的开销。

View File

@ -2,8 +2,6 @@
Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明设计新语言的最初的洪荒之力来自于对超级复杂的C++11特性的吹捧报告的鄙视最终的目标是设计网络和多核时代的C语言。到2008年中期语言的大部分特性设计已经完成并开始着手实现编译器和运行时大约在这一年Russ Cox作为主力开发者加入。到了2010年Go语言已经逐步趋于稳定并在9月正式发布Go语言并开源了代码。 Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明设计新语言的最初的洪荒之力来自于对超级复杂的C++11特性的吹捧报告的鄙视最终的目标是设计网络和多核时代的C语言。到2008年中期语言的大部分特性设计已经完成并开始着手实现编译器和运行时大约在这一年Russ Cox作为主力开发者加入。到了2010年Go语言已经逐步趋于稳定并在9月正式发布Go语言并开源了代码。
![](../images/ch1-01-go-father.jpg)
Go语言很多时候被描述为“类C语言”或者是“21世纪的C语言”。从各种角度看Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想还有彻底继承和发扬了C语言简单直接的暴力编程哲学等。下面是《Go语言圣经》中给出的Go语言的基因图谱我们可以从中看到有哪些编程语言对Go语言产生了影响。 Go语言很多时候被描述为“类C语言”或者是“21世纪的C语言”。从各种角度看Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想还有彻底继承和发扬了C语言简单直接的暴力编程哲学等。下面是《Go语言圣经》中给出的Go语言的基因图谱我们可以从中看到有哪些编程语言对Go语言产生了影响。
![](../images/ch1-01-go-family-tree.png) ![](../images/ch1-01-go-family-tree.png)

View File

@ -285,109 +285,6 @@ func main() {
Go语言终于移除了语句末尾的分号。这是Go语言在2009年11月10号正式开源之后第一个比较重要的语法改进。从1978年C语言教程第一版引入的分号分割的规则到现在Go语言的作者们花了整整32年终于移除了语句末尾的分号。在这32年的演化的过程中必然充满了各种八卦故事我想这一定是Go语言设计者深思熟虑的结果现在Swift等新的语言也是默认忽略分号的可见分号确实并不是那么的重要 Go语言终于移除了语句末尾的分号。这是Go语言在2009年11月10号正式开源之后第一个比较重要的语法改进。从1978年C语言教程第一版引入的分号分割的规则到现在Go语言的作者们花了整整32年终于移除了语句末尾的分号。在这32年的演化的过程中必然充满了各种八卦故事我想这一定是Go语言设计者深思熟虑的结果现在Swift等新的语言也是默认忽略分号的可见分号确实并不是那么的重要
## CGO版本
Go语言开源初期就支持通过CGO和C语言保持交互。CGO通过导入一个虚拟的`"C"`包来访问C语言中的函数。下面是CGO版本的“Hello World”程序
```go
package main
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func main() {
msg := C.CString("Hello, World!\n")
defer C.free(unsafe.Pointer(msg))
C.fputs(msg, C.stdout)
}
```
先通过`C.CString`函数将Go语言字符串转为C语言字符串然后调用C语言的`C.fputs`函数向标准输出窗口打印转换后的C字符串。`defer`延迟语句保证程序返回前通过`C.free`释放分配的C字符串。需要注意的是, CGO不支持C语言中的可变参数函数因为Go语言每次函数调用的栈帧大小是固定的而且Go语言中可变参数语法只是切片的一个语法糖而已因此在Go语言中是无法通过CGO访问C语言的`printf`等可变参数函数的。同时CGO只能访问C语言的函数、变量和简单的宏定义常量CGO并不支持访问C++语言的符号C++和C语言符号的名字修饰规则不同CGO采用C语言的名字修饰规则
其实CGO不仅仅用于在Go语言中调用C语言函数还可以用于导出Go语言函数给C语言函数调用。在用Go语言编写生成C语言的静、动态库时也可以用CGO导出对应的接口函数。正是CGO的存在才保证了Go语言和C语言资源的双向互通同时保证了Go语言可以继承C语言已有的庞大的软件资产。
## SWIG版本
Go语言开源初期除了支持通过CGO访问C语言资源外还支持通过SWIG访问C/C++接口。SWIG是从2010年10月04日发布的SWIG-2.0.1版本开始正式支持Go语言的。可以将SWIG看作一个高级的CGO代码自动生成器同时通过生成C语言桥接代码增加了对C++类的支持。下面是SWIG版本的"Hello World"程序:
首先是创建一个`hello.cc`文件,里面有`SayHello`函数用于打印(这里的`SayHello`函数采用C++的名字修饰规则):
```c++
#include <iostream>
void SayHello() {
std::cout << "Hello, World!" << std::endl;
}
```
然后创建一个`hello.swigcxx`文件以SWIG语法导出上面的C++函数`SayHello`:
```swig
%module main
%inline %{
extern void SayHello();
%}
```
然后在Go语言中直接访问`SayHello`函数(首字母自动转为大写字母):
```go
package main
import (
hello "."
)
func main() {
hello.SayHello()
}
```
需要将上述3个文件放到同一个目录中并且`hello.swigcxx`和Go文件对应同一个包。系统除了需要安装Go语言环境外还需要安装对应版本的SWIG工具。最后运行`go build`就可以构建了。
*注: 在Windows系统下, 路径最长为260个字符. 这个程序生成的中间cgo文件可能导致某些文件的绝对路径长度超出Windows系统限制, 可能导致程序构建失败. 这是由于`go build`调用swig和cgo等命令生成中间文件时生成的不合适的超长文件名导致作者提交ISSUE3358Go1.8已经修复)。*
## Go汇编语言版本
Go语言底层使用了自己独有的跨操作系统汇编语言该汇编语言是从Plan9系统的汇编语言演化而来。Go汇编语言并不能独立使用它是属于Go语言的一个组成部分必须以Go语言包的方式被组织。下面是Go汇编语言版本的“Hello World”程序
先创建一个`main.go`文件以Go语言的语法声明包和声明汇编语言对应的函数签名函数签名不能有函数体
```go
package main
func main()
```
然后创建`main_amd64.s`文件对应Go汇编语言实现AMD64架构的`main`函数:
```asm
#include "textflag.h"
#include "funcdata.h"
// "Hello World!\n"
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!\n"
GLOBL text<>(SB),NOPTR,$16
// func main()
TEXT ·main(SB), $16-0
NO_LOCAL_POINTERS
MOVQ $text<>+0(SB), AX
MOVQ AX, (SP)
MOVQ $16, 8(SP)
CALL runtime·printstring(SB)
RET
```
代码中`#include "textflag.h"`语句包含运行时库定义的头文件, 里面含有`NOPTR`/`NO_LOCAL_POINTERS`等基本的宏的定义。`DATA`汇编指令用于定义数据每个数据的宽度必须是1/2/4/8然后`GLOBL`汇编命令在当前文件内导出`text`变量符号。`TEXT ·main(SB), $16-0`用于定义`main`函数,其中`$16-0`表示`main`函数的帧大小是16个字节对应string头的大小用于给`runtime·printstring`函数传递参数),`0`表示`main`函数没有参数和返回值。`main`函数内部通过调用运行时内部的`runtime·printstring(SB)`函数来打印字符串。
Go汇编语言虽然针对每种CPU架构主要有386/AMD64/ARMARM64等有对应的指令和寄存器但是汇编语言的基本语法和函数调用规范是一致的不同操作系统之间用法是一致的。在Go语言标准库中`runtime`运行时库、`math`数学库和`crypto`密码相关的函数很多是采用汇编语言实现的。其中`runtime`运行时库中采用部分汇编语言并不完全是为了性能而是运行时的某些特性功能比如goroutine上下文的切换等无法用纯Go实现因此需要汇编代码实现某些辅助功能。对于普通用户而言Go汇编语言的最大价值在于性能的优化对于性能比较关键的地方可以尝试用Go汇编语言实现终极优化。
## 你好, 世界! - V2.0 ## 你好, 世界! - V2.0

View File

@ -318,34 +318,6 @@ func main() {
在发布订阅模型中,每条消息都会传送给多个订阅者。发布者通常不会知道、也不关心哪一个订阅者正在接收主题消息。订阅者和发布者可以在运行时动态添加是一种松散的耦合关心,这使得系统的复杂性可以随时间的推移而增长。在现实生活中,不同城市的象天气预报之类的应用就可以应用这个并发模式。 在发布订阅模型中,每条消息都会传送给多个订阅者。发布者通常不会知道、也不关心哪一个订阅者正在接收主题消息。订阅者和发布者可以在运行时动态添加是一种松散的耦合关心,这使得系统的复杂性可以随时间的推移而增长。在现实生活中,不同城市的象天气预报之类的应用就可以应用这个并发模式。
## 赢者为王
采用并发编程的动机有很多并发编程可以简化问题比如一类问题对应一个处理线程会更简单并发编程还可以提升性能在一个多核CPU上开2个线程一般会比开1个线程快一些。其实对于提升性能而言程序并不是简单地运行速度快就表示用户体验好的很多时候程序能快速响应用户请求才是最重要的当没有用户请求需要处理的时候才合适处理一些低优先级的后台任务。
假设我们想快速地检索“golang”相关的主题我们可能会同时打开Bing、Google或百度等多个检索引擎。当某个检索最先返回结果后就可以关闭其它检索页面了。因为受限于网络环境和检索引擎算法的影响某些检索引擎可能很快返回检索结果某些检索引擎也可能遇到等到他们公司倒闭也没有完成检索的情况。我们可以采用类似的策略来编写这个程序
```go
func main() {
ch := make(chan string, 32)
go func() {
ch <- searchByBing("golang")
}
go func() {
ch <- searchByGoogle("golang")
}
go func() {
ch <- searchByBaidu("golang")
}
fmt.Println(<-ch)
}
```
首先,我们创建了一个带缓存的管道,管道的缓存数目要足够大,保证不会因为缓存的容量引起不必要的阻塞。然后我们开启了多个后台线程,分别向不同的检索引擎提交检索请求。当任意一个检索引擎最先有结果之后,都会马上将结果发到管道中(因为管道带了足够的缓存,这个过程不会阻塞)。但是最终我们只从管道取第一个结果,也就是最先返回的结果。
通过适当开启一些冗余的线程,尝试用不同途径去解决同样的问题,最终以赢者为王的方式提升了程序的相应性能。
## 控制并发数 ## 控制并发数
很多用户在适应了Go语言强大的并发特性之后都倾向于编写最大并发的程序因为这样似乎可以提供最大的性能。在现实中我们行色匆匆但有时却需要我们放慢脚步享受生活并发的程序也是一样有时候我们需要适当地控制并发的程度因为这样不仅仅可给其它的应用/任务让出/预留一定的CPU资源也可以适当降低功耗缓解电池的压力。 很多用户在适应了Go语言强大的并发特性之后都倾向于编写最大并发的程序因为这样似乎可以提供最大的性能。在现实中我们行色匆匆但有时却需要我们放慢脚步享受生活并发的程序也是一样有时候我们需要适当地控制并发的程度因为这样不仅仅可给其它的应用/任务让出/预留一定的CPU资源也可以适当降低功耗缓解电池的压力。
@ -381,6 +353,7 @@ func main() {
} }
``` ```
不过`gatefs`对此做一个抽象类型`gate`,增加了`enter``leave`方法分别对应并发代码的进入和离开。当超出并发数目限制的时候,`enter`方法会阻塞直到并发数降下来为止。 不过`gatefs`对此做一个抽象类型`gate`,增加了`enter``leave`方法分别对应并发代码的进入和离开。当超出并发数目限制的时候,`enter`方法会阻塞直到并发数降下来为止。
```go ```go
@ -392,6 +365,7 @@ func (g gate) leave() { <-g }
`gatefs`包装的新的虚拟文件系统就是将需要控制并发的方法增加了`enter``leave`调用而已: `gatefs`包装的新的虚拟文件系统就是将需要控制并发的方法增加了`enter``leave`调用而已:
```go ```go
type gatefs struct { type gatefs struct {
fs vfs.FileSystem fs vfs.FileSystem
@ -405,45 +379,37 @@ func (fs gatefs) Lstat(p string) (os.FileInfo, error) {
} }
``` ```
我们不仅可以控制最大的并发数目而且可以通过带缓存Channel的使用量和最大容量比例来判断程序运行的并发率。当管道为空的时候可以认为是空闲状态当管道满了时任务是繁忙状态这对于后台一些低级任务的运行是有参考价值的。增加的方法如下 我们不仅可以控制最大的并发数目而且可以通过带缓存Channel的使用量和最大容量比例来判断程序运行的并发率。当管道为空的时候可以认为是空闲状态当管道满了时任务是繁忙状态这对于后台一些低级任务的运行是有参考价值的。
## 赢者为王
采用并发编程的动机有很多并发编程可以简化问题比如一类问题对应一个处理线程会更简单并发编程还可以提升性能在一个多核CPU上开2个线程一般会比开1个线程快一些。其实对于提升性能而言程序并不是简单地运行速度快就表示用户体验好的很多时候程序能快速响应用户请求才是最重要的当没有用户请求需要处理的时候才合适处理一些低优先级的后台任务。
假设我们想快速地检索“golang”相关的主题我们可能会同时打开Bing、Google或百度等多个检索引擎。当某个检索最先返回结果后就可以关闭其它检索页面了。因为受限于网络环境和检索引擎算法的影响某些检索引擎可能很快返回检索结果某些检索引擎也可能遇到等到他们公司倒闭也没有完成检索的情况。我们可以采用类似的策略来编写这个程序
```go ```go
func (g gate) Len() int { return len(g) } func main() {
func (g gate) Cap() int { return cap(g) } ch := make(chan string, 32)
func (g gate) Idle() bool { return len(g) == 0 }
func (g gate) Busy() bool { return len(g) == cap(g) }
func (g gate) Fraction() float64 {
return float64(len(g)) / float64(cap(g))
}
```
然后我们可以在相对空闲的时候处理一些后台低优先级的任务,在并发相对繁忙或超出一定比例的时候提供预警:
```go
func New(fs vfs.FileSystem, gate chan bool) *gatefs {
p := &gatefs{fs, gate}
// 后台监控线程
go func() { go func() {
for { ch <- searchByBing("golang")
switch { }
case p.gate.Idle(): go func() {
// 处理后台任务 ch <- searchByGoogle("golang")
case p.gate.Fraction() >= 0.7: }
// 并发预警 go func() {
default: ch <- searchByBaidu("golang")
time.Sleep(time.Second) }
}
}
}()
return p fmt.Println(<-ch)
} }
``` ```
这样我们通过后台线程就可以根据程序的状态动态调整自己的工作模式。 首先,我们创建了一个带缓存的管道,管道的缓存数目要足够大,保证不会因为缓存的容量引起不必要的阻塞。然后我们开启了多个后台线程,分别向不同的检索引擎提交检索请求。当任意一个检索引擎最先有结果之后,都会马上将结果发到管道中(因为管道带了足够的缓存,这个过程不会阻塞)。但是最终我们只从管道取第一个结果,也就是最先返回的结果。
通过适当开启一些冗余的线程,尝试用不同途径去解决同样的问题,最终以赢者为王的方式提升了程序的相应性能。
## 素数筛 ## 素数筛
@ -645,215 +611,8 @@ func main() {
现在每个工作者并发体的创建、运行、暂停和退出都是在`main`函数的安全控制之下了。 现在每个工作者并发体的创建、运行、暂停和退出都是在`main`函数的安全控制之下了。
## 消费海量的请求
在前面的生产者、消费者并发模型中,只有当生产者和消费的速度近似相等时才会达到最佳的效果,同时通过引入带缓存的管道可以消除因临时效率波动产生的影响。但是当生产者和消费者的速度严重不匹配时,我们是无法通过带缓存的管道来提高性能的(缓存的管道只能延缓问题发生的时间,无法消除速度差异带来的问题)。当消费者无法及时消费生产者的输出时,时间积累会导致问题越来越严重。 ## context包
对于生产者、消费者并发模型我们当然可以通过降低生产者的产能来避免资源的浪费。但在很多场景中生产者才是核心对象它们生产出各种问题或任务单据这时候产出的问题是必须要解决的、任务单据也是必须要完成的。在现实生活中制造各种生活垃圾的海量人类其实就是垃圾生产者而清理生活垃圾的少量的清洁工就是垃圾消费者。在网络服务中提交POST数据的海量用户则变成了生产者Web后台服务则对应POST数据的消费者。海量生产者的问题也就变成了如何构造一个能够处理海量请求的Web服务假设每分钟百万级请求
在Web服务中用户提交的每个POST请求可以看作是一个Job任务而服务器是通过后台的Worker工作者来消费这些Job任务。当面向海量的Job处理时我们一般可以通过构造一个Worker工作者池来提高Job的处理效率通过一个带缓存的Job管道来接收新的任务请求避免任务请求功能无法响应Job请求接收管道和Worker工作者池通过分发系统来衔接。
我们可以用管道来模拟工作者池:当需要处理一个任务时,先从工作者池取一个工作者,处理完任务之后将工作者返回给工作者池。`WorkerPool`对应工作者池,`Worker`对应工作者。
```go
type WorkerPool struct {
workers []*Worker
pool chan *Worker
}
// 构造工作者池
func NewWorkerPool(maxWorkers int) *WorkerPool {
p := &WorkerPool{
workers: make([]*Worker, maxWorkers)
pool: make(chan *Worker, maxWorkers)
}
// 初始化工作者
for i, _ := range p.workers {
worker := NewWorker(0)
p.workers[i] = worker
p.pool <- worker
}
return p
}
// 启动工作者
func (p *WorkerPool) Start() {
for _, worker := range p.workers {
worker.Start()
}
}
// 停止工作者
func (p *WorkerPool) Stop() {
for _, worker := range p.workers {
worker.Stop()
}
}
// 获取工作者(阻塞)
func (p *WorkerPool) Get() *Worker {
return <-p.pool
}
// 返回工作者
func (p *WorkerPool) Put(w *Worker) {
p.pool <- w
}
```
工作者池通过一个带缓存的管道来提高工作者的管理。当所有工作者都在处理任务时,工作者的获取会阻塞自动有工作者可用为止。
`Worker`对应工作者实现具体任务由后台一个固定的Goroutine完成和外界通过专有的管道通信工作者的私有管道也可以选择带有一定的缓存具体实现如下
```go
type Worker struct {
job chan interface{}
quit chan bool
wg sync.WaitGroup
}
// 构造工作者
func NewWorker(maxJobs int) *Worker {
return &Worker{
job: make(chan interface{}, maxJobs),
quit: make(chan bool),
}
}
// 启动任务
func (w *Worker) Start() {
p.wg.Add(1)
go func() {
defer p.wg.Done()
for {
// 接收任务
// 此时工作中已经从工作者池中取出
select {
case job := <-p.job:
// 处理任务
case <-w.quit:
return
}
}
}()
}
// 关闭任务
func (p *Worker) Stop() {
p.quit <- true
p.wg.Wait()
}
// 提交任务
func (p *Worker) AddJob(job interface{}) {
p.job <- job
}
```
任务的分发系统在`Service`对象中完成:
```go
type Service struct {
workers *WorkerPool
jobs chan interface{}
maxJobs int
wg sync.WaitGroup
}
func NewService(maxWorkers, maxJobs int) *Service {
return &Service {
workers: NewWorkerPool(maxWorkers),
jobs: make(chan interface{}, maxJobs),
}
}
func (p *Service) Start() {
p.jobs = make(chan interface{}, maxJobs)
p.wg.Add(1)
p.workers.Start()
go func() {
defer p.wg.Done()
for job := range p.jobs:
go func(job Job) {
// 从工作者池取一个工作者
worker := p.workers.Get()
// 完成任务后返回给工作者池
defer p.workers.Put(worker)
// 提交任务处理(异步)
worker.AddJob(job)
}(job)
}
}()
}
func (p *Service) Stop() {
p.workers.Stop()
close(p.jobs)
p.wg.Wait()
}
// 提交任务
// 任务管道带较大的缓存, 延缓阻塞的时间
func (p *Service) AddJob(job interface{}) {
p.jobs <- job
}
```
主程序可以是一个web服务器
```go
var (
MaxWorker = os.Getenv("MAX_WORKERS")
MaxQueue = os.Getenv("MAX_QUEUE")
)
func main() {
service := NewService(MaxWorker, MaxQueue)
service.Start()
defer service.Stop()
// 处理海量的任务
http.HandleFunc("/jobs", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Job以JSON格式提交
var jobs []Job
err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(&jobs)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusBadRequest)
return
}
// 处理任务
for _, job := range jobs {
service.AddJob(job)
}
// OK
w.WriteHeader(http.StatusOK)
})
// 启动web服务
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
基于Go语言特有的管道和Goroutine特性我们以非常简单的方式设计了一个针对海量请求的处理系统结构。在实际的系统中用户可以根据任务的具体类型和特性将管道定义为具体类型以避免接口等动态特性导致的开销。
## 更多
在Go1.7发布时,标准库增加了一个`context`用来简化对于处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作官方有博文对此做了专门介绍。我们可以用`context`包来重新实现前面的线程安全退出或超时的控制: 在Go1.7发布时,标准库增加了一个`context`用来简化对于处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作官方有博文对此做了专门介绍。我们可以用`context`包来重新实现前面的线程安全退出或超时的控制:
@ -934,7 +693,7 @@ func main() {
fmt.Printf("%v: %v\n", i+1, prime) fmt.Printf("%v: %v\n", i+1, prime)
ch = PrimeFilter(ctx, ch, prime) // 基于新素数构造的过滤器 ch = PrimeFilter(ctx, ch, prime) // 基于新素数构造的过滤器
} }
cancel() cancel()
} }
``` ```

View File

@ -1,3 +1,4 @@
## 1.8. 扩展阅读 ## 1.8. 补充说明
本书定位是Go语言进阶图书因此读者需要有一定的Go语言基础。如果对Go语言不太了解作者推荐通过以下资料开始学习Go语言。首先是安装Go语言环境然后通过`go tool tour`命令打开“A Tour of Go”教程学习。在学习“A Tour of Go”教程的同时可以阅读Go语言官方团队出版的[《The Go Programming Language》](http://www.gopl.io/)教程。[《The Go Programming Language》](http://www.gopl.io/)在国内Go语言社区被称为Go语言圣经它将带你系统地学习Go语言。在学习的同时可以尝试用Go语言解决一些小问题如果遇到要差异API的适合可以通过godoc命令打开自带的文档查询。Go语言本身不仅仅包含了所有的文档也包含了所有标准库的实现代码这是第一手的最权威的Go语言资料。我们此时你应该已经可以熟练使用Go语言了。
TODO

View File

@ -1,4 +1,4 @@
# 2.11. 编译和链接参数 # 2.10. 编译和链接参数
编译和链接参数是每一个C/C++程序员需要经常面对的问题。构建每一个C/C++应用均需要经过编译和链接两个步骤CGO也是如此。 编译和链接参数是每一个C/C++程序员需要经常面对的问题。构建每一个C/C++应用均需要经过编译和链接两个步骤CGO也是如此。
本节我们将简要讨论CGO中经常用到的编译和链接参数的用法。 本节我们将简要讨论CGO中经常用到的编译和链接参数的用法。

View File

@ -1,6 +1,6 @@
# 2.12. 补充说明 ## 2.11. 补充说明
CGO是C语言和Go语言混合编程的技术因此要想熟练地使用CGO需要了解这两门语言。C语言推荐两本书第一本是C语言之父编写的《C程序设计语言》第二本是讲述C语言模块化编程的《C语言接口与实现:创建可重用软件的技术》。Go语言推荐官方出版的《The Go Programming Language》和Go语言自带的全部文档和全部代码。
为何要话费巨大的精力学习CGO是一个问题。任何技术和语言都有它自身的优点和不足Go语言不是银弹它无法解决全部问题。而通过CGO可以继承C/C++将近半个世纪的软件遗产通过CGO可以用Go给其它系统写C接口的共享库通过CGO技术可以让Go语言编写的代码可以很好地融入现有的软件生态——而现在的软件正式建立在C/C++语言之上的。因此说CGO是一个保底的后备技术它是Go的一个重量级的替补技术值得任何一个严肃的Go语言开发人员学习。 为何要话费巨大的精力学习CGO是一个问题。任何技术和语言都有它自身的优点和不足Go语言不是银弹它无法解决全部问题。而通过CGO可以继承C/C++将近半个世纪的软件遗产通过CGO可以用Go给其它系统写C接口的共享库通过CGO技术可以让Go语言编写的代码可以很好地融入现有的软件生态——而现在的软件正式建立在C/C++语言之上的。因此说CGO是一个保底的后备技术它是Go的一个重量级的替补技术值得任何一个严肃的Go语言开发人员学习。
本章讨论了CGO的一些常见用法并给出相关的例子。关于CGO有几点补充如果有纯Go的解决方法就不要使用CGOCGO中涉及的C和C++构建问题非常繁琐CGO有一定的限制无法实现解决全部的问题不要试图越过CGO的一些限制。而且CGO只是一种官方提供并推荐的Go语言和C/C++交互的方法。如果是使用的gccgo的版本可以通过gccgo的方式实现Go和C/C++的交互。同时SWIG也是一种选择并对C++诸多特性提供了支持。

View File

@ -1,3 +0,0 @@
## 2.12. 扩展阅读
TODO

View File

@ -11,7 +11,7 @@
![](../images/ch3-func-stack-frame-layout-01.ditaa.png) ![](../images/ch3-func-stack-frame-layout-01.ditaa.png)
首先是调用函数前,转变的输入参数和返回值空间。然后CALL指令将首先触发返回地址入栈操作。在进入到被调用函数内之后汇编器自动插入了BP寄存器相关的指令因此BP寄存器和返回地址是紧挨着的。再下面就是当前函数的局部变量的空间包含再次调用其它函数需要准备的调用参数空间。被调用的函数执行RET返回指令时先从栈恢复BP和SP寄存器接着取出的返回地址跳转到对应的指令执行。 首先是调用函数前准备的输入参数和返回值空间。然后CALL指令将首先触发返回地址入栈操作。在进入到被调用函数内之后汇编器自动插入了BP寄存器相关的指令因此BP寄存器和返回地址是紧挨着的。再下面就是当前函数的局部变量的空间包含再次调用其它函数需要准备的调用参数空间。被调用的函数执行RET返回指令时先从栈恢复BP和SP寄存器接着取出的返回地址跳转到对应的指令执行。
## 高级汇编语言 ## 高级汇编语言

View File

@ -1,3 +1,10 @@
## 3.9. 扩展阅读 ## 3.9. 补充说明
如果是纯粹学习汇编语言则可以从《深入理解程序设计使用Linux汇编语言》开始该书讲述了如何以C语言的思维变现汇编程序。如果是学习X86汇编则可以从《汇编语言基于x86处理器》一开始然后再结合《现代x86汇编语言程序设计》学习AVX等高级汇编指令的使用。
Go汇编语言的官方文档非常匮乏。其中“A Quick Guide to Go's Assembler”是唯一的一篇系统讲述Go汇编语言的官方文章该文章中又引入了另外两篇Plan9的文档A Manual for the Plan 9 assembler 和 Plan 9 C Compilers。Plan9的两篇文档分别讲述了汇编语言以及和汇编有关联的C语言编译器的细节。看过这几篇文档之后会对Go汇编语言有了一些模糊的概念剩下的就是在实战中通过代码学习了。
Go语言的编译器和汇编器都带了一个`-S`参数可以查看生成的最终目标代码。通过对比目标代码和原始的Go语言或Go汇编语言代码的差异可以加深对底层实现的理解。同时Go语言连接器的实现代码也包含了很多相关的信息。Go汇编语言是依托Go语言的语言因此理解Go语言的工作原理是也是必要的。比较重要的部分是Go语言runtime和reflect包的实现原理。如果读者了解CGO技术那么对Go汇编语言的学习也是一个巨大的帮助。最后是要了解syscall包是如何实现系统调用的。
得益于Go语言的设计Go汇编语言的优势也非常明显跨操作系统、不同CPU之间的用法也非常相似、支持C语言预处理器、支持模块。同时Go汇编语言也存在很多不足它不是一个独立的语言底层需要依赖Go语言甚至操作系统很多高级特性很难通过手工汇编完成。虽然Go语言官方尽量保持Go汇编语言简单但是汇编语言是一个比较大的话题大到足以写一本Go汇编语言的教程。本章的目的是让大家对Go汇编语言简单入门在看到底层汇编代码的时候不会一头雾水在某些遇到性能受限制的场合能够通过Go汇编突破限制。
TODO

View File

@ -1,14 +0,0 @@
# 3.9. 补充说明
得益于Go语言的设计Go汇编语言的优势也非常明显跨操作系统、不同CPU之间的用法也非常相似、支持C语言预处理器、支持模块。同时Go汇编语言也存在很多不足它不是一个独立的语言底层需要依赖Go语言甚至操作系统很多高级特性很难通过手工汇编完成。虽然Go语言官方尽量保持Go汇编语言简单但是汇编语言是一个比较大的话题大到足以写一本Go汇编语言的教程。本章的目的是让大家对Go汇编语言简单入门在看到底层汇编代码的时候不会一头雾水在某些遇到性能受限制的场合能够通过Go汇编突破限制。这只是一个开始后续版本会继续完善。
<!--
未解的问题:
defer/go 工作原理
闭包掉工作原理
接口结构和原理
通过接口调用方法
AVX512指令集
JIT动态生成代码
直接调用C函数(假设栈足够)
-->

View File

@ -1,3 +1,5 @@
## 4.8. 扩展阅读 ## 4.8. 补充说明
目前专门讲述RPC的图书比较少。目前Protobuf和GRPC的官网都提供了详细的参考资料和例子。本章重点讲述了Go标准库的RPC和基于Protobuf衍生的GRPC框架同时也简单展示了如何自己定制一个RPC框架。之所以聚焦在这几个有限的主题是因为这几个技术都是Go语言团队官方在进行维护和Go语言契合也最为默契。不过RPC依然是一个庞大的主题足以单独成书。目前开源世界也有很多富有特色的RPC框架还有针对分布式系统进行深度定制的RPC系统用户可以根据自己实际需求选择合适的工具。
TODO

View File

@ -1,5 +0,0 @@
# 4.8. 补充说明
本章重点讲述了Go标准库的RPC和基于Protobuf衍生的GRPC框架同时也简单展示了如何自己定制一个RPC框架。之所以聚焦在这几个有限的主题是因为这几个技术都是Go语言团队官方在进行维护和Go语言契合也最为默契。不过RPC依然是一个庞大的主题足以单独成书。目前开源世界也有很多富有特色的RPC框架还有针对分布式系统进行深度定制的RPC系统用户可以根据自己实际需求选择合适的工具。
<!-- Nginx 补充说明 -->