Go语言编程实践
综述
并发与分布式
- 类“Erlang风格的并发模型”的编程范式:goroutine & channel
- 传统的“共享内存模型”仍然被保留,允许适度地使用:Mutex
软件工程
- 代码风格 如强制{}风格
- 错误处理 函数多返回值最后一个为error类型 defer
- 包 package import
- 接口 interface 非侵入式
- 单元测试 go test
- 编译 go run, go build
编程哲学
大道至简
- go对面向对象持保守态度有限吸收 支持类struct, 类成员方法、 类组合, 反对继承、函数和运算符重载、虚函数及重载、放弃构造和析构。继承采用组合的文法。提供了非侵入式接口、可为类型添加方法的类型系统
- 吸收函数式编程,支持匿名函数和闭包
- 语言交互性 cgo
- go内存管理?
Go语言包的实现代码非常精致耐读,是学习Go语言编程的最佳示例。可以达到事半功倍的效果。
类型系统
典型类型系统:
- 基础类型,如byte、int、bool、float等;
- 复合类型,如数组、结构体、指针等;
- 可以指向任意对象的类型(Any类型);
- 值语义和引用语义;
- 面向对象,即所有具备面向对象特征(比如成员方法)的类型;
- 接口。
为类型添加方法
在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法。
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
结构体
Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性。
接口
Go语言的主要设计者之一罗布·派克(Rob Pike)曾经说过,如果只能选择一个Go语言的特性移植到其他语言中,他会选择接口。
非侵入式接口
在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。
类型查询
在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型。利用反射也可以进行类型查询,详情可参阅reflect.TypeOf()方法的相关文档。
并发编程
每个进程有一个执行上下文,即一个调用栈,一个堆。并发意味着程序执行多个上下文,对应多个调用栈。
并发主流实现模式:
- 多进程
- 多线程 “共享内存系统”
- 基于回调的非阻塞异步IO
-
协程 “消息传递系统”
- Akka/Erlang的Actor模型
- golang的goroutine与channel对应的CSP
- python和ruby GIL greenlet
- nodejs Reactor callback
人的思维模式可认为是串行的,串行的事务具有确定性。线程通信采用共享内存方式,需加锁。实践证明,我们很难面面俱到,往往会在工程中遇到各种奇怪的故障和问题。“不要通过共享内存来通信,而应该通过通信来共享内存。”
协程
多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能也并不完整,比如仅仅提供轻量级线程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程,从而无法真正达到轻量级线程本身期望达到的目标。
进程、线程、协程的关系和区别:
- 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
- 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
- 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
堆和栈的区别请参看:http://www.cnblogs.com/ghj1976/p/3623037.html
协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任。
执行协程只需要极少的栈内存(大概是4~5KB),默认情况下,线程栈的大小为1MB。
goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。
- boost boost::context、boost::coroutine。仅跨平台提供协程基础功能。
- libco 腾讯的协程库。明显的阉割版放出。没有提供上层封装使用层代码
- libgo 魅族的协程库。和goroutine使用功能上无限接近。
- libtask goroutine前身。goroutine一出,大家惊呆了,原来封装的好,也可以这么好用。
goroutine、libgo、libco 比较
开源库 HOOK情况 协程大小 功能完成度(goroutine用法为参照) 代码可读性
goroutine 全部hook 8K开始,动态增加,分段栈实现 100% go代码太庞大,还没找到…
libco socket系列API 可制定大小,共享栈实现 最基本的协程功能 代码量小,比较难看,没看
libgo socket系列API 可制定大小,没有特殊实现 接近100% 代码量小,可读性很高,基本看懂
并发模式外延
多路复用 future
c实现:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *count();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
main()
{
int rc1, rc2;
pthread_t thread1, thread2;
/* 创建线程,每个线程独立执行函数functionC */
if((rc1 = pthread_create(&thread1, NULL, &add, NULL)))
{
printf("Thread creation failed: %d\n", rc1);
}
if((rc2 = pthread_create(&thread2, NULL, &add, NULL)))
{
printf("Thread creation failed: %d\n", rc2);
}
/* 等待所有线程执行完毕 */
pthread_join( thread1, NULL);
pthread_join( thread2, NULL);
exit(0);
}
void *count()
{
pthread_mutex_lock( &mutex1 );
counter++;
printf("Counter value: %d\n",counter);
pthread_mutex_unlock( &mutex1 );
}
go实现:
package main
import "fmt"
func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range(chs) {
<-ch
}
}
协程池
channel
不带缓冲channel阻塞
var ch chan int
ch := make(chan int)
ch <- value
value := <-ch
带缓冲channel
在缓冲区填满前不会阻塞
var ch chan int
ch := make(chan int, 1024)
ch <- value
value := <-ch
select关键字,用于处理异步IO问题
select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作。select的特点是只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的情况。
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
超时机制
基于select特性,为channel实现超时机制:
// 首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // 等待1秒钟
timeout<- true
}()
// 然后我们把timeout这个channel利用起来
select {
case <-ch:
// 从ch中读取到数据
case <-timeout:
// 一直没有从ch中读取到数据,但从timeout中读取到了数据
}
定时器
package main
import (
"fmt"
"time"
)
func testTimer1() {
go func() {
fmt.Println("test timer1")
}()
}
func timer1() {
timer1 := time.NewTicker(1 * time.Second)
for {
select {
case <-timer1.C:
testTimer1()
}
}
}
func main() {
go timer1()
time.Sleep(5 * time.Second)
}
channel的传递
单向channel
单向channel也是起到这样的一种契约作用。
单向channel变量的声明非常简单,如下:
var ch1 chan int // ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据
单向channel和双向channel之间进行转换。示例如下:
ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一个单向的读取channel
ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel
同步锁
Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。
全局唯一性操作
对于从全局的角度只需要运行一次的代码,比如全局初始化操作,Go语言提供了一个sync.Once类型来保证全局的唯一性操作。
为了更好地控制并行中的原子性操作,sync包中还包含一个atomic子包,它提供了对于一些基础数据类型的原子操作函数,比如下面这个函数: func CompareAndSwapUint64(val *uint64, old, new uint64) (swapped bool)就提供了比较和交换两个uint64类型数据的操作。这让开发者无需再为这样的操作专门添加 Lock操作。
软件工程
常用包
网络编程
Socket编程
Socket编程步骤:socket(), bind(), listen(), connect(), accept(), receive(), send()。
Go标准库对此进行了抽象和封装:net.Dial(),Dial()函数是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装。
Dial()函数支持如下几种网络协议:”tcp”、”tcp4”(仅限IPv4)、”tcp6”(仅限 IPv6)、”udp”、”udp4”(仅限IPv4)、”udp6”(仅限IPv6)、”ip”、”ip4”(仅限IPv4)和”ip6”(仅限IPv6)。
在成功建立连接后,我们就可以进行数据的发送和接收。发送数据时,使用conn的Write()成员方法,接收数据时使用Read()方法。
网络字节序
数据在tcp网络传输协议中传输的字节序是大端模式的,c语言的htonl函数会将数据字节序转换成大端模式,在网络上面传输,接收端想解出原始数据只需要认为发送来的数据是大端模式,按照大端模式表示的数据解析便可。golang并没有类似ntohl()、htonl()等函数, 但是提供了binary.BigEndian binary.LittleEndian等模式。
head_buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, head)
buf := bytes.NewReader(buffer)
err = binary.Read(buf, binary.BigEndian, &num)
HTTP编程
Go标准库提供net/http包。
http.Get(), http.Post(), http.PostForm(), http.Head(). http.Get()和http.PostForm() 就可以满足需求,但是如果我们发起的 HTTP 请求需要更多的定制信息,我们希望设定一些自定义的 Http Header 字段, 可以使用net/http包http.Client对象的Do()方法来实现。
高级封装:除了之前介绍的基本HTTP操作,Go语言标准库也暴露了比较底层的HTTP相关库,让开发 者可以基于这些库灵活定制HTTP服务器和使用HTTP服务。
RPC编程
在Go中,标准库提供的net/rpc 包实现了 RPC 协议需要的相关细节,开发者可以很方便地 使用该包编写 RPC 的服务端和客户端程序,这使得用 Go 语言开发的多个进程之间的通信变得非 常简单。
标准库
程序框架
- errors error 包实现了用于错误处理的函数.
- flag flag 包实现命令行标签解析.
- os os包提供了操作系统函数的不依赖平台的接口.
- exec exec包执行外部命令.
- signal signal包实现了对输入信号的访问.
- user user包允许通过名称或ID查询用户帐户.
- log log包实现了简单的日志服务.
- syslog syslog包提供一个简单的系统日志服务的接口.
输入输出 (Input/Output)
IO 操作封装在了如下几个包中:
- io 为 IO 原语(I/O primitives)提供基本的接口
- io/ioutil 封装一些实用的 I/O 函数
- fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf
-
bufio 实现带缓冲I/O
- path path实现了对斜杠分隔的路径的实用操作函数.
- filepath filepath包实现了兼容各操作系统的文件路径的实用操作函数.
字节流和字符串
- strings 字符串操作
- bytes byte slice 便利操作
- strconv 字符串和基本数据类型之间转换
- regexp 正则表达式
- unicode Unicode码点、UTF-8/16编码
容器包 container
- heap heap包提供了对任意类型(实现了heap.Interface接口)的堆操作.
- list list包实现了双向链表.
- ring ring实现了环形链表的操作.
网络包 net
- http http包提供了HTTP客户端和服务端的实现.
- cgi cgi 包实现了RFC3875协议描述的CGI(公共网关接口).
- cookiejar cookiejar包实现了保管在内存中的符合RFC 6265标准的http.CookieJar接口.
- fcgi fcgi 包实现了FastCGI协议.
- httptest httptest 包提供HTTP测试的单元工具.
- httptrace Package httptrace provides mechanisms to trace the events within HTTP client requests.
- httputil httputil包提供了HTTP公用函数,是对net/http包的更常见函数的补充.
- pprof pprof 包通过提供HTTP服务返回runtime的统计数据,这个数据是以pprof可视化工具规定的返回格式返回的.
- mail mail 包实现了解析邮件消息的功能.
- rpc rpc 包提供了一个方法来通过网络或者其他的I/O连接进入对象的外部方法.
- sonrpc jsonrpc 包使用了rpc的包实现了一个JSON-RPC的客户端解码器和服务端的解码器.
- smtp smtp包实现了简单邮件传输协议(SMTP),参见RFC 5321.
- textproto textproto实现了对基于文本的请求/回复协议的一般性支持,包括HTTP、NNTP和SMTP.
- url url包解析URL并实现了查询的逸码,参见RFC 3986.
反射包 reflect
reflect包实现了运行时反射,允许程序操作任意类型的对象.
反射三定律:
1.反射可以将“接口类型变量”转换为“反射类型对象”。
2.反射可以将“反射类型对象”转换为“接口类型变量”。
3.如果要修改“反射类型对象”,其值必须是“可写的”(settable)。
sync
- sync sync 包提供了互斥锁这类的基本的同步原语.
- atomic atomic 包提供了底层的原子性内存原语,这对于同步算法的实现很有用.
database
- database
- sql sql 包提供了通用的SQL(或类SQL)数据库接口.
- driver driver包定义了应被数据库驱动实现的接口,这些接口会被sql包使用.
使用感受
语言好用点
- 大道至简 符合思维逻辑 工程语言
- interface 组合
- goroutine channel
- 丰富的标准库 net io string bytes container reflect sync
- package 工程组织 分层 高内聚
- :=声明变量并赋值 var
- 函数多返回值 “_”屏蔽不关心返回值
- 编译快
- 反射
- 不定参数
语言难受点
- 编译未使用变量、未使用import报错
第三方库
- protobuf
- db gorm
重点
Golang 优化之路——临时对象池
并发与分布式
2016 年十大 Golang 开发者必读好文
Go语言TCP Socket编程
各家观点
Go语言,Docker和新技术 - 2017-10-26 陈皓
我为什么放弃Go语言-2014年3月
Golang适合高并发场景的原因分析
说说Golang的使用心得
Go-简洁的并发
Go coding in go way
版本
- 2007年,谷歌工程师Rob Pike, Ken Thompson和Robert Griesemer开始设计一门全新的语言,这是Go语言的最初原型。[1]
- 2009年11月10日,Go语言以开放源代码的方式向全球发布。[1]
- 2011年3月16日,Go语言的第一个稳定(stable)版本r56发布。[2]
- 2012年3月28日,Go语言的第一个正式版本Go1发布。[2]
- 2013年4月04日,Go语言的第一个Go 1.1beta1测试版发布。[3]
- 2013年4月08日,Go语言的第二个Go 1.1beta2测试版发布。[3]
- 2013年5月02日,Go语言Go 1.1RC1版发布。[4]
- 2013年5月07日,Go语言Go 1.1RC2版发布。[5]
- 2013年5月09日,Go语言Go 1.1RC3版发布。 [6]
- 2013年5月13日,Go语言Go 1.1正式版发布。
- 2013年9月20日,Go语言Go 1.2RC1版发布。[7]
- 2013年12月1日,Go语言Go 1.2正式版发布。[8]
- 2014年6月18日,Go语言Go 1.3版发布。[9]
- 2014年12月10日,Go语言Go 1.4版发布。[10]
- 2015年8月19日,Go语言Go 1.5版发布,本次更新中移除了”最后残余的C代码”。[11]
- 2016年2月17日,Go语言Go 1.6版发布。[12]
- 2016年8月15日,Go语言Go 1.7版发布。[13]
- 2017年2月17日,Go语言Go 1.8版发布。[14]
- 2017年8月24日,Go语言Go 1.9版发布。[15]
社区
golang.org
studygolang
golangtc
wonderfogo
tonibai
企业应用
golang-in-china
GoUsers
今日头条Go建千亿级微服务的实践
- 今日头条当前后端服务超过80%的流量是跑在 Go 构建的服务上。微服务数量超过100个,高峰 QPS 超过700万,日处理请求量超过3000亿,是业内最大规模的 Go 应用。
对于复杂的服务间调用,我们抽象出五元组的概念:(From, FromCluster, To, ToCluster, Method)。每一个五元组唯一定义了一类的RPC调用。以五元组为单元,我们构建了一整套微服务架构。
我们使用 Go 语言研发了内部的微服务框架 kite,协议上完全兼容 Thrift。以五元组为基础单元,我们在 kite 框架上集成了服务注册和发现,分布式负载均衡,超时和熔断管理,服务降级,Method 级别的指标监控,分布式调用链追踪等功能。目前统一使用 kite 框架开发内部 Go 语言的服务,整体架构支持无限制水平扩展。
-
360消息推送的数据: 16台机器,标配:24个硬件线程,64GB内存 Linux Kernel 2.6.32 x86_64 单机80万并发连接,load 0.2~0.4,CPU 总使用率 7%~10%,内存占用20GB (res) 目前接入的产品约1280万在线用户 2分钟一次GC,停顿2秒 (1.0.3 的 GC 不给力,直接升级到 tip,再次吃螃蟹) 15亿个心跳包/天,占大多数。
-
京东云消息推送系统 (团队人数:4) 单机并发tcp连接数峰值118w 内存占用23G(Res) Load 0.7左右 心跳包 4k/s gc时间2-3.x s
-
美团后台流量支撑程序 产品网址:http://meituan.com 应用范围:支撑主站后台流量(排序,推荐,搜索等),提供负载均衡,cache,容错,按条件分流,统计运行指标(qps,latency)等功能, 上线时间(或预计上线时间):2013-8-1 Go代码行数占比:100% 代码行数:约2k 日 PV:保密
-
weico 3.0 产品网址:http://weico.com 上线时间(或预计上线时间):2013-9-1 应用范围: 服务端所有代码 Go代码行数占比:100% 日 PV:保密 代码行数:3万行量级(最近没有统计过)
-
金山微看 网址:www.weikan.cn 上线时间:2012年9月 应用范围:服务接口,后台流程服务,消息系统,图片系统 Go代码行数:62838 日PV:保密
-
搜狗推送系统 网址:内部网址 上线时间:2013年10月至今 应用范围:Push系统中用于维持与客户端连接的部分 Go代码行数:1W+ 日PV:保密
-
七牛云存储 产品网址:http://qiniu.com/ 上线时间(或预计上线时间):2011-9-1 应用范围:整个产品(包括基础服务、Web端、统计平台、各类小工具等等) Go代码行数占比:99.9% 日 PV:保密 代码行数:近50万行
开源项目
docker 无人不知的虚拟华平台,开源的应用容器引擎,借助该引擎,开发者可以打包他们的应用,移植到任何平台上。 https://github.com/docker/docker
golang go本身,也是用go语言实现的,包括他的编译器,要研究go源代码的可以看此项目录 https://github.com/golang/go
lantern 蓝灯,一款P2P的过墙软件,他和SS不一样的是,他是分布式的,P2P的,通过蓝灯,你可以和自由上网的用户共享网络,对方可以自由上网,你也就自由了。 https://github.com/getlantern/lantern
kubernetes Google出品,用于调度和管理Docker的开源容器管理系统,利用他,可以方便的管理你的docker实例,哪怕非常多,也是目前最流行的docker管理系统。 https://github.com/kubernetes/kubernetes
awesome-go 这不是一个go项目,他是一个学习go的资料网站,属于著名的awesome系列,里面关于go的资源非常详细。 https://github.com/avelino/awesome-go
gogs 一款基于Git的代码托管系统,类似于github和gitlab,不过其小巧易用,功能强大,部署方便,也有不少用户在使用。 https://github.com/gogits/gogs
syncthing 开源的文件同步系统,它使用了其独有的对等自由块交换协议,速度很快,据说可以替换BitTorrent Sync。 https://github.com/syncthing/syncthing
hugo 一款极速的静态页面生成器,让你可以很快的搭建个人网站,提供了多套主题可供使用,并且可以自己定制,和NodeJS的Hexo是一样的。 https://github.com/spf13/hugo
grafana 一款开源监控度量的看板系统,可以接Graphite,Elasticsearch,InfluxDB等数据源,定制化很高。 https://github.com/grafana/grafana
etcd 一款分布式的,可靠的K-V存储系统,使用简单,速度快,又安全。 https://github.com/coreos/etcd
hub 一款更便捷使用github的工具,包装并且扩展了git,提供了很多特性和功能,使用和git差不多。 https://github.com/github/hub
influxdb 可伸缩的数据库,使用场景主要用来存储测量数据,事件点击以及其他等实时分析数据,用来做监控性能很不错。 https://github.com/influxdata/influxdb
caddy 快速的,跨平台的HTTP/2 Web服务器。 https://github.com/mholt/caddy
beego 国产开源的高性能Web框架,让你快速的开发Go Web应用服务,谢大主笔。 https://github.com/astaxie/beego
martini 也是一款不错的Web框架。 https://github.com/go-martini/martini
cayley Google开源的图数据库,这是一个NoSql数据库,适合处理复杂的,但是结构化低的数据,适用于社交网络,推荐系统等。 https://github.com/cayleygraph/cayley
nsq 一款开源的实时的,分布式的消息中间件系统。 https://github.com/nsqio/nsq
codis Codis是一个分布式Redis解决方案,其实就是一个数据库代理,让你在使用Redis集群的时候,就像使用单机版的Redis是一样的,对开发者透明。 https://github.com/CodisLabs/codis
delve 这个Go开发者都知道,一款go应用开发的调试工具。 https://github.com/derekparker/delve
cobra cobra是一个命令行go库,可以让你创建非常强大的,现代的CLI命令行应用。 https://github.com/spf13/cobra
shadowsocks-go go版本的shadowsocks,大家都懂的。 https://github.com/shadowsocks/shadowsocks-go
pholcus Pholcus(幽灵蛛)是一款纯Go语言编写的支持分布式的高并发、重量级爬虫软件,定位于互联网数据采集,为具备一定Go或JS编程基础的人提供一个只需关注规则定制的功能强大的爬虫工具。 https://github.com/henrylee2cn/pholcus
参考文献:
- 《Go编程语言》许世伟 吕桂华
- 《Go语言程序设计》Mask Summerfield