golang核心编程
2015 年 4 月 12 日
golang核心笔记
Go的编译命令
- GOROOT: go当前安装目录 - GOPATH: 工作区的集合,多个用:分隔.工作区是放置 Go 源码文件的目录.三个目录:**src 目录,pkg 目录,bin 目录**。 - GO111MODULE: mod包管理开启 - GOPROXY: go代理,配合GO111MODULE使用 - go env -w GO111MODULE=on - eport GO111MODULE=auto - go mod init [project_name] # 初始化一个mod管理项目 - go env # 查看更多命令 - go build xx.go -o xx -i [build flags] - go run xx.go - go clean # 删除目标文件和缓存文件 - go test -v # 测试项目目标的xxx_test.go文件 - go test -test.bench=".*" # 测试总个目录的 - go test xxx_test.go -test.bench=".*" # 测试单个文件 - go test xxx_test.go -benchmem -test.bench=".*" # 显示内存 - gofmt -w xx.go # 格式化某个文件或目录
变量
const name = 'ok' //隐式类型定义 const name string = "ok" //显式类型定义 var dp = [3][5]int{[5]int{1, 2, 3, 4, 5}, [5]int{4, 5, 6}, [5]int{7, 8, 9}} //多维数组的初始化,不足补0 s := []string{"abc", "ABC"} //切片初始化 slice1 := s[1:2] //[开始:结束] 开始和结束不能大于数组实际长度 var slice2 []type = make([]type, len) s1 := []int{1, 2} //切片增加元素 s1 = append(s1, 3) s2 := []int{4, 5} s3 := append(s1, s2...) //合并两个切片 //************************************************************************** res := struct { //匿名结构体 Name string Age int }{Name:"lily", Age:18} jsons, err := json.Marshall(res) //json序列化(注:jsons的类型是[]type) errs = json.Unmarshal(jsons, &res2) //反序列化 //************************************************************************** person := map[int]string{ //map集合是无序的 key-value 数据结构 1 : "Tom", 2 : "Aaron", 3 : "John", } delete(person, 2) //删除 person[2] = "Jack" //增加 person[3] = "Kevin" //修改
slice切片:是对数组一个连续片段的引用,是一种引用类型
- 切片提供了一个与指向数组的动态窗口
- Go 中数组赋值和函数传参都是值复制的,而切片是引用传递的
- 使用make创建切片:实现运行期动态指定数组长度,绕开编译期常量的限制
- 切片扩容策略:小于1024个元素,翻倍扩容。超过1024后1.25倍扩容。
// 切片定义 type slice struct { array unsafe.Pointer len int cap int // cap一定要比len大 } // 从 slice 中得到一块内存地址 s := make([]byte, 200) ptr := unsafe.Pointer(s[0]) // 从go的内存地址中构造一个slice var ptr unsafe.Pointer var s1 = struct { //初始化一个结构体 addr unitptr len int cap int }{ptr, length, length} s := *(*[]byte)(unsafe.Pointer(s1)) // ptr 转 slice
流程控制
import ( "crypto/md5" "encoding/hex" "fmt" "time" ) func MD5(str string) string { s := md5.New() s.Write([]byte(str)) return hex.EncodeToString(s.Sum(nil)) } func main() { switch var { case val1: fmt.Println(time.Now().Format(`2006-01-02 15:04:05`)) //获取系统当前时间 case val2: MD5("md5sum") default: time.Sleep(1*time.Second) } } for-range //迭代语法:可用于array、slice、map、chan
interface{}与类型断言
interface{} 只定义方法
package main type interfaceName interface { }
- 接口实现:接口的方法与实现接口的类型方法格式一致(方法名、参数类型、返回值类型一致)。所有方法都要被实现。
- 接口赋值:接口本质上是一个指针类型。实现了该接口的struct和子接口,可以赋值给接口。
- 接口类型做为参数:如果一个函数有个接口作为参数。那么实现了该接口的struct都可以做为此参数。
- 空接口: Go也能像其它动态语言一样,在数据结构中存储任意类型的数据。
package main import "fmt" func main() { any := make([]interface{}, 5) any[0] = 11 any[1] = "hello world" any[2] = []int{11, 22, 33, 44} for _, value := range any { fmt.Println(value) } }
- 接口嵌套:内部属性属于外部属性-
类型断言
- 如果不清楚当前struct是什么类型,可以采用类型断言,运行时判断。
package main func main() { switch t := areaIntf.(type) { case *Rectangle: // do something case *Triangle: // do something default: // do something } }
多态
- 1、多个类型(结构体)可以实现同一个接口。
- 2、一个类型(结构体)可以实现多个接口。
- 3、实现接口的类(结构体)可以赋值给接口。
package main import "fmt" type Shaper interface { Area() float64 } type Rectangle struct { length float64 width float64 } func (r *Rectangle) Area() float64 { return r.length * r.width // 实现 Shaper 接口中的方法 } func (r *Rectangle) Set(l float64, w float64) { r.length = l //Set 是属于 Rectangle 自己的方法 r.width = w } type Triangle struct { // ==== Triangle ==== bottom float64 hight float64 } func (t *Triangle) Area() float64 { return t.bottom * t.hight / 2 } func (t *Triangle) Set(b float64, h float64) { t.bottom = b t.hight = h } func main() { rect := new(Rectangle) rect.Set(2, 3) areaIntf := Shaper(rect) //这种方法只能将指针类型的类示例赋值给接口 fmt.Printf("The rect has area: %f\n", areaIntf.Area()) triangle := new(Triangle) triangle.Set(2, 3) areaIntf = Shaper(triangle) //这种方法只能将指针类型的类示例赋值给接口 fmt.Printf("The triangle has area: %f\n", areaIntf.Area()) }
GO配置文件读取
- 内置flag包获取配置
- config.json文件、ini文件、yaml文件、toml文件
- 万能的viper
package main //json文件 import "encoding/json" buf, _ := ioutil.ReadFile(path) myConfig := &MyConfig{} //对应json中的k-v项 _ = json.Unmarshal(buf, myConfig) // ini文件 import "github.com/go-ini/ini" myConfig := &MyConfig{} //对应ini中的k-v项 err := ini.MapTo(myConfig, "config.ini") // 另一种常用来读ini文件: import "gopkg.in/ini.v1" cfg,_ := ini.Load(path) cfg.Section("").Key("app_mode").String() //read cfg.Section("section_name").Key("port").SetValue("8086") //write cfg.SaveTo(path) // yaml文件 import "gopkg.in/yaml.v2" myConfig := &MyConfig{} //对应ini中的k-v项 file, err := ioutil.ReadFile("config.yaml") err = yaml.Unmarshal(file, myConfig) // toml文件 import "github.com/BurntSushi/toml" myConfig := &MyConfig{} //对应yaml中的k-v项 toml.DecodeFile("config.toml", myConfig)
GO并发
通信来共享内存
Goroutine启动一个协程:(如果main协程退出,case协程自动退出)
package main func case() { fmt.Println("case Goroutine!") } func main() { go case() // 启动另外一个goroutine去执行case函数 fmt.Println("main协程 done!") }
使用sync.WaitGroup启动多个协程
package main import "sync" import "fmt" var wg sync.WaitGroup func hello(i int) { defer wg.Done() // goroutine结束就登记-1 fmt.Println("Hello Goroutine!", i) } func main() { for i := 0; i < 10; i++ { wg.Add(1) // 启动一个goroutine就登记+1 go hello(i) } wg.Wait() // 等待所有登记的goroutine都结束 }
Goroutineg与线程
- OS线程一般都有固定的栈内存(一般2MB)
- goroutine(可增长的栈)的栈不是固定的,他可以按需增大和缩小(典型大小2KB, 限制可达1GB)
runtime
- runtime.Gosched() 让出CPU时间片,重新等待调度。
- runtime.Goexit() 退出当前协程
- runtime.GOMAXPROCS(int) (最大256)
调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。
默认值是机器上的CPU核心数。
例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。
- goroutine与os线程是M:N的关系
Channel
- CSP并发模型(Communicating Sequential Processes),提倡 通过通信共享内存 而不是 通过共享内存而实现通信 。
- 带缓存的channel 和 不带缓存的channel
- 创建 不带缓存 的 ch := make(chan interface{})
- 创建 带缓存 的 ch := make(chan interface{}, num int)
- 只读的 ch := make(<-chan interface{})
- 只写的 ch := make(chan<- interface{})
- 发送 ch <- 10
- 接收 x := <- ch // 从ch中接收值并赋值给变量x
<- ch // 从ch中接收值,忽略结果 - 关闭 close(ch) // 管道不存取时一定要关闭
- 长度 len(ch) // 求 缓冲通道 中元素的数量
- 容量 cap(ch) // 求 缓冲通道 的容量
两者的区别
不带缓冲的通道, 发送 和 接收 操作都会 阻塞当前协程
带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞
管道会出现panic地场景: 1. close以后的管道,再写入。 2. 重复close
只读的chan不能close, close以后还可以读取数据
for-range管道,遍历完后,如果chan是关闭的,遍历完数据,正常退出。
for-range管道,遍历完后,如果chan不是关闭的,遍历完数据,程序会行等待,直到出现死锁。
eg1:采用无缓冲通道进行通信
package main import "fmt" func recv(c chan int) { ret := <-c fmt.Println("接收成功", ret) } func main() { ch := make(chan int) go recv(ch) // 启用goroutine从通道接收值 ch <- 10 fmt.Println("发送成功") }
无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。
相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致 发送和接收的goroutine同步化 。因此, 无缓冲通道也被称为同步通道 。
eg2:按顺序实现输入输出
package main import "fmt" func main() { ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i fmt.Println("write num is :", i) <- ch //这里必须,否则实现交替输出后,main程无法退出 } close(ch) }() for { //主协程只负责读取chan中的数据 if data, ok := <- ch; ok { fmt.Println("read num is :", data) } else { fmt.Println("no data") break } } }
eg3:交替打印数字和字母
package main func PrintNums(f chan int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 5; i++ { fmt.Println("num", i) f <- 1 <- f } } func PrintChars(f chan int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 5; i++ { fmt.Println("char", string('a' + i)) <- f f <- 1 } } func main() { runtime.GOMAXPROCS(8) flag := make(chan int) var wg sync.WaitGroup wg.Add(2) go PrintNums(flag, &wg) go PrintChars(flag, &wg) wg.Wait() }
Time包的定时器功能
package main import ( "fmt" "time" ) func main() { // 1.获取ticker对象 ticker := time.NewTicker(1 * time.Second) i := 0 // 子协程 go func() { for { //<-ticker.C i++ fmt.Println(<-ticker.C) if i == 5 { //停止 ticker.Stop() } } }() time.Sleep(6 * time.Second) }
GMP模型
- Goroutine:相当于OS的进程控制块(Process Control Block);它包含:函数执行的指令和参数,任务对象,线程上下文切换,字段保护,和字段的寄存器。
- M:对应物理线程。
- P:golang的协程调度器。P的数量可以通过GOMAXPROCS设置。
调度器设计策略
1.复用线程:
- work stealing机制 :当本线程无可运行的Goroutine时,尝试从其他线程绑定的 P队列 偷取G,而不是消毁线程。
- hand off机制 :当本线程因为Goroutine进行系统调用阻塞时,线程释放绑定的P,把P转移给其它空闲的线程执行。
2.抢占:在goroutine中要等待一个协程主动让CPU才执行下一个协程;在GO中,一个goroutine最多占用CPU 10ms, 防止其他goroutine被锁死。
3.利用并行:利用GOMAXPROCS设置P数量,最多有GPMAXPROCS个线程分布在多个CPU上同时执行。
4.全局G队列:当M执行work stealing从其它P的本地队列中偷不到G时,它可以从全局列队获取G.
有疑问加站长微信联系(非本文作者)