golang核心编程

voidFan · · 621 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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 奉行通过通信来共享内存(CSP并发模型),而不是共享内存来通信

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

  1. CSP并发模型(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信
  2. 带缓存的channel不带缓存的channel<阻塞通道>
  3. 创建不带缓存的 ch := make(chan interface{})
  4. 创建带缓存的 ch := make(chan interface{}, num int)
  5. 只读的 ch := make(<-chan interface{})
  6. 只写的 ch := make(chan<- interface{})
  7. 发送 ch <- 10
  8. 接收 x := <- ch // 从ch中接收值并赋值给变量x
    <- ch // 从ch中接收值,忽略结果
  9. 关闭 close(ch) // 管道不存取时一定要关闭
  10. 长度 len(ch) // 求缓冲通道中元素的数量
  11. 容量 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.


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:voidFan

查看原文:golang核心编程

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

621 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传