cai_go
TOC TOC
- Introduction
- 编写第一个Go程序
- 变量&常量
- 数据类型
- 运算符
- 条件和循环
- 数组和切片
- map
- Map与工厂模式
- 字符串
- go里的函数
- 行为的定义和实现
- Go语言的相关接口
- 扩展与复用
- 不一样的接口类型,一样的多态
- 空接口
- Go语言错误机制
- panic and recover
- package/module
- init方法
- package
- 依赖管理
- 协程机制
- 共享内存并发控制
- CSP并发机制
- 多路选择和超时控制
- channel的关闭
- 任务的取消
- Context与任务取消
- 单例可以使用 sync.once
- 仅需任意任务完成
- 等待所有任务with csp/channel
- sync.Pool
- 单元测试
- benchmark
Introduction
it's a course from geektime about golang, provided by a guru named 蔡超
编写第一个Go程序
环境变量GOPATH
- 1.8版本前必须设置这个环境变量
- 1.8(含)后有默认值:
*nix
默认为$HOME/go
Windows
默认为%USERPROFILE%/go
GOPATH
也是 go get
命令下载存储的地方,所以默认也是 $HOME/go
go version
应用程序入口
- 必须是 main 包:
package main
- 必须是 main 方法:
func main()
- 文件名不一定是
main.go
退出返回值
- Go中
main
不支持任何返回值 - 通过
os.Exit
来返回状态
获取命令行参数
main
函数不支持传入参数- 通过
os.Args
获取 第0项是二进制文件名,和c系语言一致
变量&常量
编写测试程序 [0/1]
源码文件以
_test
结尾:xxx_test.go
测试方法名以 Test 开头:
func TestXXX(t *testing.T) {...}
(大写的函数名表示包外可访问,虽然测试例实际无此需求)[ ] package的目录名和package名一致应该是convention,就像java里一样 这块等到 package 再看一下
$ tree . ├── go_learning.iml └── src ├── ch01 │ └── main │ └── hello_world.go └── ch2 ├── fib │ └── fib_test.go └── test └── try_test.go
hello_world.go : package main
fib_test.go : package fib
try_test.go : package try_test
变量
var a int
a := 2
常量
快速常量赋值
const (
Monday = iota + 1
Tuesday
)
留意下 iota
就好
数据类型
基本数据类型
bool
string
int int8 int16 int32 int64
unit uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32, represents a Unicode code point
float32 float64
complex64 complex128
类型转换
- 不允许隐式类型转换
- 别名和原有类型也不允许隐式类型转换
类型的预定义值
math.MaxInt64
math.MaxFloat64
math.MaxUint32
指针类型
- 不支持指针运算
string
是值类型,其默认的初始化值为空字符串,而不是nil
- 个人看法:其实就是可为空的引用
运算符
算术运算符
- 没有前置
++ --
== 比较数组
- 可以比较,严格相等才为
true
位运算符
- &^ 按位置零
右边如果是1,则无论左边是啥都置位0; 否则就是左边的值。
就是普通的1 &^ 0 -- 1 1 &^ 1 -- 0 0 &^ 1 -- 0 0 &^ 0 -- 0
a & (^b)
条件和循环
只有
for
关键字if
支持变量赋值if var declaration; condition { // }
switch
条件表达式不限制为常数或者整数
单个
case
当中,可以出现多个结果选项,使用逗号分隔不需要
break
可以不提供 switch 所需的表达式,这时候其实就是
if else if else
switch { case 0 <= Num && Num <= 3: fmt.Printf("0-3") case 4 <= Num && Num <= 6: fmt.Printf("4-6") case 7 <= Num && Num <= 9: fmt.Printf("7-9") }
数组和切片
数组的声明
package array_test
import "testing"
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1,2,3,4}
arr3 := [...]int{1,2,3,5}
arr[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 3, 4, 5}
for i := 0; i < len(arr3); i++ {
t.Log(arr3[i])
}
for _, e := range arr3 {
t.Log(e)
}
}
这里 ...
也是给数组用的,slice方括号里没有任何东西
数组截取
和python的语法一样的
切片内部结构
就是 c++ 里的vector,java里的arraylist,没啥好说的
map
声明
m1 := map[int]int{1:1,2:2,3:9}
t.Log(m1[2])
t.Logf("len m1=%d", len(m1))
m2 := map[int]int{}
m2[4] = 16
t.Logf("len m2=%d", len(m2))
m3 := make(map[int]int, 10)
t.Logf("len m3=%d", len(m3))
// cannot use map
// t.Logf("len m3=%d", cap(m3))
缺失值
当缺失值的时候,会返回默认值,因此需要主动判断是否存在
m1 := map[int]int{} t.Log(m1[1]) m1[2] = 0 t.Log(m1[2]) if v, ok := m1[3]; ok { t.Logf("Key 3's value is %d", v) } else { t.Log("Key 3 is not existing") }
遍历
还是用range
Map与工厂模式
- map的value可以是一个方法
- 用于实现set :: 没有默认的set,可以用 map 搞下
map[type]bool
- 删除可以用delete
字符串
- string 是数据(值)类型,不是引用或者指针
- string 是只读的byte slice, len可以获取其中的所有byte数量 这里应该是有字符编码的问题的?
- string 的 byte array 可以存放任何数据 所以就是c++里的string一样,就是一个 byte array?
- len 方法求出来的是byte array的长度
- 所以对于实际上的长度使用loop然后求出来?for loop 会自动转rune
- 字符串方法:
- split
- join
- strconv package里的方法等等
go里的函数
- 可以有多个返回值
- 所有参数都是值传递
- 函数可以作为变量的值
- 函数可以作为参数和返回值
可变长参数
...int
defer函数
行为的定义和实现
结构体定义
形如
type Employee struct {
Id string
Name string
Age int
}
实例创建及初始化
e := Employee {"0", "Bob", 20}
e1 := Employee {Name: "Mike", Age: 30}
e2 := new(Employee) // 此处返回的是指针
e2.Id = "2"
e2.Age = 22
行为(方法)定义
receiver 是指针时无需拷贝
Go语言的相关接口
Duck Type式接口实现
接口定义
type Programmer interface { WriteHelloWold() Code }
接口实现
type GoProgrammer struct { } func (p *GoProgrammer) WriteHelloWold() Code { return "fmt.Println(\"Hello World!\")" }
example:
package _interface import "testing" type Programmer interface { WriteHelloWorld() string } type GoProgrammer struct { } func (o *GoProgrammer) WriteHelloWorld() string { return "fmt.Println(\"Hello World\")" } func TestClient(t *testing.T) { // not *Programmer var p Programmer p = new(GoProgrammer) t.Log(p.WriteHelloWorld()) }
接口变量
扩展与复用
type Dog struct {
Pet
}
<- 匿名成员,所有方法自动委托 https://www.google.com/search?q=go+anonymous+field
不一样的接口类型,一样的多态
example:
package poly
import (
"fmt"
"testing"
)
type Code string
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
return "System.out.Println(\"Hello World!\")"
}
func writeFirstProgram(p Programmer) {
fmt.Println("%T %v\n", p, p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T) {
goProg := new(GoProgrammer)
javaProg := new(JavaProgrammer)
writeFirstProgram(goProg)
writeFirstProgram(javaProg)
}
空接口
interface{}
- 空接口可以表示任何类型
- 通过断言来将空接口转换为指定类型
v, ok := p.(int)
Go语言错误机制
没有异常机制
error 类型实现了 error 接口
type error interface { Error() string }
可以通过 errors.New 来快速创建错误实例
errors.New("blablabla")
panic and recover
panic vs. os.Exit
- os.Exit 退出不会调用
defer
- os.Exit 退出不输出当前调用栈信息
- os.Exit 退出不会调用
recover code like:
defer func() { if err := recover(); err != nil { fmt.Println("recovered from ", err) } }() fmt.Println("Start") panic(errors.New("Something wrong"))
Start recovered from Something wrong
package/module
- 首字母大写表示可被包外访问
- 代码的
package
可以和所在目录不一致 - 同个目录的 Go 代码的
package
需要保持一致
for current go learning, setup GOPATH
export GOPATH=$HOME/go_learning:$GOPATH
beware we have a src subdir
$ tree go_learning
go_learning
├── go_learning.iml
└── src
── ch01
└── main
└── hello_world.go
for idea, we can setup via
Settings / Languages & Frameworks / Go / GOPATH
导入路径从 src 后开始写,例如:
package client
import (
"ch15/series"
"testing"
)
func TestPackage(t *testing.T) {
t.Log(series.GetFibonacci(5))
}
init方法
- 在 main 被执行前, 所有依赖的 package 的 init 方法都会执行
- 不同包的 init 函数按照包导入的依赖关系决定执行顺序 是否会有循环导入的问题?
- 每个包可以有多个 init 函数
包的每个源文件也可以有多个 init 函数
package
- 直接提交src后的路径,即不要含
src
依赖管理
Go未解决的依赖问题
- 同一环境下,不同项目使用同一包的不同版本
- 无法管理对包的特定版本的依赖
- 寻找路径:
GOPATH -> GOROOT
常用的依赖管理工具
- godep
- https://github.com/tools/godep 早期
- glide
- https://github.com/Masterminds/glide
- dep
- https://github.com/golang/dep 比较新
Vendor路径
resolve 顺序如下:
- 当前包下的vendor目录
- 向上级目录查找,知道找到 src 下的vendor目录
- 在GOPATH下面查找依赖包
- 在GOROOT目录下查找依赖包
glide使用实例
安装可以参考repo说明,windwos 也阔以 go get github.com/Masterminds/glide
logs example:
# glide init
[INFO] Generating a YAML configuration file and guessing the dependencies
[INFO] Attempting to import from other package managers (use --skip-import to skip)
[INFO] Scanning code to look for dependencies
[INFO] --> Found test reference to github.com\easierway\concurrent_map
[INFO] Writing configuration file (glide.yaml)
[INFO] Would you like Glide to help you find ways to improve your glide.yaml configuration?
[INFO] If you want to revisit this step you can use the config-wizard command at any time.
[INFO] Yes (Y) or No (N)?
Y
[INFO] Looking for dependencies to make suggestions on
[INFO] --> Scanning for dependencies not using version ranges
[INFO] --> Scanning for dependencies using commit ids
[INFO] Gathering information on each dependency
[INFO] --> This may take a moment. Especially on a codebase with many dependencies
[INFO] --> Gathering release information for dependencies
[INFO] --> Looking for dependency imports where versions are commit ids
[INFO] Here are some suggestions...
[INFO] The package github.com/easierway/concurrent_map appears to have Semantic Version releases (http://semver.org).
[INFO] The latest release is v1.0.0. You are currently not using a release. Would you like
[INFO] to use this release? Yes (Y) or No (N)
Y
[INFO] Would you like to remember the previous decision and apply it to future
[INFO] dependencies? Yes (Y) or No (N)
N
[INFO] Updating github.com/easierway/concurrent_map to use the release v1.0.0 instead of no release
[INFO] The package github.com/easierway/concurrent_map appears to use semantic versions (http://semver.org).
[INFO] Would you like to track the latest minor or patch releases (major.minor.patch)?
[INFO] The choices are:
[INFO] - Tracking minor version releases would use '>= 1.0.0, < 2.0.0' ('^1.0.0')
[INFO] - Tracking patch version releases would use '>= 1.0.0, < 1.1.0' ('~1.0.0')
[INFO] - Skip using ranges
[INFO] For more information on Glide versions and ranges see https://glide.sh/docs/versions
[INFO] Minor (M), Patch (P), or Skip Ranges (S)?
S
[INFO] Would you like to remember the previous decision and apply it to future
[INFO] dependencies? Yes (Y) or No (N)
N
[INFO] Configuration changes have been made. Would you like to write these
[INFO] changes to your configuration file? Yes (Y) or No (N)
Y
[INFO] Writing updates to configuration file (glide.yaml)
[INFO] You can now edit the glide.yaml file.:
[INFO] --> For more information on versions and ranges see https://glide.sh/docs/versions/
[INFO] --> For details on additional metadata see https://glide.sh/docs/glide.yaml/
然后 glide install
后会有 vendor 路径
nevermind,intellij idea/goland 里面集成的是 dep,这块先这么过去就好了
协程机制
Thread vs. Goroutine
- 和 kse (Kernel Space Entity)的对应关系
- Java Thread 是 1:1
- Goroutine 是 M:N
- 创建时默认的栈大小
- JDK5 以后 Java Thread Stack 默认为1MB
- Goroutine 的 Stack 大小初始化为 2KB
共享内存并发控制
sync
package:Mutex
RWLock
- WaitGroup
Example:
package share_mem
import (
"sync"
"testing"
)
func TestCounter(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
couter := 0
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
couter ++
wg.Done()
}()
}
// time.Sleep(1 * time.Second)
wg.Wait()
t.Logf("Counter = %d", couter)
}
CSP并发机制
- Communicating sequenctial processing
- channel 也有 Sync (和java的synchronousqueue一样) 和 Buffer的两种方式 just serach it https://www.google.com/search?q=go%20csp%20sync%20and%20buffer
多路选择和超时控制
select
多渠道的选择
select { case ret := <- retCh1 t.Logf("result is %s", ret) case ret := <- retCh2 t.Logf("result is %s", ret) default: t.Error("No one returned") }
超时控制
select { case ret := <- retCh: t.Logf("result is %s", ret) case ret <- time.After(time.Second * 1): t.Error("time out") }
select没有保证顺序
channel的关闭
close
函数<-
可以返回状态- 向关闭的channel发送数据,会导致panic
- v, ok <- ch; ok 为 bool 值,true表示正常接受, false表示通道关闭
- 所有的channel接收者会在channel关闭时,立刻从阻塞等待中返回且ok值为false。
- 这个广播机制常用于向订阅者同时发送信号(如退出信号)
任务的取消
通过关闭 channel 来做任务取消是阔以的:
select {
case <- ch:
return true
default:
return false
}
当 close(ch)
的时候,所有receiver都会拿到一个message
ps 除开 interface{}
也有 struct{}
,实例化方式是 struct{}{}
Context与任务取消
- 根context
- 通过 context.Background() 创建
- 子context
context.WithCancel(parentContext) 创建
ctx, cancel := context.WithCancel(context.Background())
- 当前 Context 被取消时,所有基于它的子 context 都会被取消
- 接收取消通知
<- ctx.Done()
具体再看下context的文档
单例可以使用 sync.once
仅需任意任务完成
maybe goroutine leak use chan with buffer can avoid this, but it should be someway to do it with sync chan too
等待所有任务with csp/channel
sync.Pool
sync.Pool对象获取
尝试从私有对象获取
如果似有对象不存在,尝试从当前Processor的共享池中获取
如果当前Processor共享池也没有,尝试从其他Processor的共享池中获取
如果所有子池都是空的,就用用户指定的 New 函数产生一个新的对象
私有对象 :: 协程安全
共享对象 :: 协程不安全
TODO 这个逻辑保证是单例么? :: 并不会,get会把对象拿出来
使用sync.Pool
pool := &sync.Pool {
New: func() interface{} {
return 0
},
}
arr := pool.Get().(int)
pool.Put(10)
sync.Pool对象生命周期
- GC 会清除 sync.pool 缓存的对象
- 对象的缓存有效期为下一次GC之前
sync.Pool 总结
- 适合通过复用,降低复杂对象的创建和GC代价
- 协程安全,会有锁的开销
- 生命周期受GC影响,不适合做链接池等。
单元测试
内置单元测试框架
- Fail, Error: 该测试失败,该测试继续,其他测试继续
- FailNow, Fatal: 该测试失败,该测试终止,其他测试继续
代码覆盖率
go test -v -cover
断言
https://github.com/stretchr/testify
benchmark
函数以 Benchmark 开头 示例:
func BenchmarkConcatStringByBytesBuffer(b *testing.B) {
elems := []string{"1", "2", "3", "4", "5"}
b.StartTimer()
for i := 0; i < b.N; i++ {
var buf bytes.Buffer
for _, elem := range elems {
buf.WriteString(elem)
}
}
b.StopTimer()
}
func BenchmarkConcatStringByAdd(b *testing.B) {
elems := []string{"1", "2", "3", "4", "5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
ret := ""
for _, elem := range elems {
ret += elem
}
}
b.StopTimer()
}
- 留意需要自行使用
for i := 0; i < b.N; i++
来控制迭代次数 - 在 windows 下面用
-bench='.'
选项