综述

并发与分布式

  • 类“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
    }
}

协程池

fasthttp中的协程池实现

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 语言开发的多个进程之间的通信变得非 常简单。

标准库

golang

标准库

studygolang

程序框架

  • 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万行

开源项目

golang-open-source-projects

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