Go

作者: 谭英智 | 来源:发表于2022-09-03 14:55 被阅读0次

数据结构

chan

channel是golang进程内协程间通讯的管道

例子

package main

import (
    "fmt"
    "time"
)

func addNumberToChan(chanName chan int) {
    for {
        chanName <- 1
        time.Sleep(1 * time.Second)
    }
}

func main() {
    var chan1 = make(chan int, 10)
    var chan2 = make(chan int, 10)

    go addNumberToChan(chan1)
    go addNumberToChan(chan2)

    for {
        select {
        case e := <- chan1 :
            fmt.Printf("Get element from chan1: %d\n", e)
        case e := <- chan2 :
            fmt.Printf("Get element from chan2: %d\n", e)
        default:
            fmt.Printf("No element in chan1 and chan2.\n")
            time.Sleep(1 * time.Second)
        }
    }
}

输出:

D:\SourceCode\GoExpert\src>go run main.go
Get element from chan1: 1
Get element from chan2: 1
No element in chan1 and chan2.
Get element from chan2: 1
Get element from chan1: 1
No element in chan1 and chan2.
Get element from chan2: 1
Get element from chan1: 1
No element in chan1 and chan2.

性质

  • 从chan读,如果chan缓冲为空,当前goroutine会被阻塞
  • 向chan写,如果chan缓冲已满,当前goroutine会被阻塞
  • 因读阻塞的协程会被写入chan的协程唤醒
  • 因写阻塞的协程会被读chan的协程唤醒
  • 关闭channel,会把因读阻塞的协程用nil唤醒,把因写阻塞的协程用panic唤醒

slice

slice是动态数组

  • 创建slice
go-slice-make
  • 使用数组创建slice
go-slice-from-array
  • 扩容
go-slice-expand

如果cap小于1024,则扩容2倍

如果大于,则扩容1.25倍

扩容后,地址发生了变化

例子

    sliceA := make([]int, 5, 10)  //length = 5; capacity = 10
    sliceB := sliceA[0:5]         //length = 5; capacity = 10
    sliceC := sliceA[0:5:5]       //length = 5; capacity = 5

map

map是一个hash表

go-map

使用渐进式扩容

ito

iota代表了const声明块的行索引(下标从0开始 )

const (
    bit0, mask0 = 1 << iota, 1<<iota - 1   //const声明第0行,即iota==0
    bit1, mask1                            //const声明第1行,即iota==1, 表达式继承上面的语句
    _, _                                   //const声明第2行,即iota==2
    bit3, mask3                            //const声明第3行,即iota==3
)s

string

  • string可以为空,但不会为nil
  • string对象不可修改
  • []byte与string转换一般需要拷贝,但对于某些临时变量则不需要
  • 字符串拼接,目标字符串会一次性分配足够的内存,防止多次扩容

控制结构

defer

defer语句用于延迟函数的调用,每次defer,会先把函数压入栈,函数返回前,再把栈内的函数弹出来执行

  • 延迟函数的参数再defer语句时就已经确定
  • 延迟函数后进先出
  • return执行过程:保存返回值->执行defer->执行ret跳转

select

select用于多个channel的多路复用

package main

import (
    "fmt"
    "time"
)

func main() {
    chan1 := make(chan int)
    chan2 := make(chan int)

    go func() {
        chan1 <- 1
        time.Sleep(5 * time.Second)
    }()

    go func() {
        chan2 <- 1
        time.Sleep(5 * time.Second)
    }()

    select {
    case <-chan1:
        fmt.Println("chan1 ready.")
    case <-chan2:
        fmt.Println("chan2 ready.")
    default:
        fmt.Println("default")
    }

    fmt.Println("main exit.")
}
  • 除default外,每个case操作一个channel,要么读要么写
  • 除default外,个case是随机执行
  • 如果没有default,会阻塞任一case
  • channel读操作需要判断是否成功读取,关闭channel会返回nil

mutex

用于协程间并发控制

加锁时先自旋,如果自选4次无法获得锁,则陷入睡眠,等待唤醒

自旋条件:

  • 最多自旋4次
  • CPU核数大于1
  • 协程调度的Process大于1
  • 可运行队列为空(闲时)

重复解锁会panic

normal模式

先自旋再阻塞

starvation模式

饥饿模式不会自旋,直接进入阻塞状态,保证长时间等待的协程可以有机会被唤醒

woken状态

同一时刻一个在加锁,一个在解锁,不需要发送信号量

协程

  • G (Goroutine) : 协程
  • M (Machine):线程
  • P (Processor):处理器
go-goroutine

每个线程都维持着协程队列

线程在得到P执行器时,调度协程执行

全局有一个协程队列,用于负载均衡

线程间可以窃取其他线程的协程

内存

内存分配

内存分配类似tcmalloc

GC

最新版STW 0.5ms

标记-清除算法

  • 灰色:对象在标记队列中
  • 黑色:对象已被标记(代表不会在GC中清理)
  • 白色:对象未被标记(代表需要在GC中清理)

从内存根节点开始层次遍历

被引用的对象会被标记会灰色

灰色对象的孩子会被标记会灰色

灰色对象会弹出队列,并标记为黑色

为了减少STW,引入了写屏障

对被在标记期间,引用减少的对象,被标记会灰色

新增的对象,被标记为灰色

内存逃逸

  • 函数外部没有引用,则优先放在栈
  • 函数外部存在引用,则放在堆中
  • 栈内存不够,则放在堆中
  • 传递指针会把内存放在堆中
  • 栈的效率高于堆

并发控制

  • channel:个数固定,简单
  • WaitGroup:个数动态
  • Contex:对派生的子协程控制

chanel

package main

import (
    "time"
    "fmt"
)

func Process(ch chan int) {
    //Do some work...
    time.Sleep(time.Second)

    ch <- 1 //管道中写入一个元素表示当前协程已结束
}

func main() {
    channels := make([]chan int, 10) //创建一个10个元素的切片,元素类型为channel

    for i:= 0; i < 10; i++ {
        channels[i] = make(chan int) //切片中放入一个channel
        go Process(channels[i])      //启动协程,传一个管道用于通信
    }

    for i, ch := range channels {  //遍历切片,等待子协程结束
        <-ch
        fmt.Println("Routine ", i, " quit!")
    }
}

WaitGroup

package main

import (
    "fmt"
    "time"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(2) //设置计数器,数值即为goroutine的个数
    go func() {
        //Do some work
        time.Sleep(1*time.Second)

        fmt.Println("Goroutine 1 finished!")
        wg.Done() //goroutine执行结束后将计数器减1
    }()

    go func() {
        //Do some work
        time.Sleep(2*time.Second)

        fmt.Println("Goroutine 2 finished!")
        wg.Done() //goroutine执行结束后将计数器减1
    }()

    wg.Wait() //主goroutine阻塞等待计数器变为0
    fmt.Printf("All Goroutine finished!")
}

Contex

package main

import (
    "fmt"
    "time"
    "context"
)

func HandelRequest(ctx context.Context) {
    go WriteRedis(ctx)
    go WriteDatabase(ctx)
    for {
        select {
        case <-ctx.Done():
            fmt.Println("HandelRequest Done.")
            return
        default:
            fmt.Println("HandelRequest running")
            time.Sleep(2 * time.Second)
        }
    }
}

func WriteRedis(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("WriteRedis Done.")
            return
        default:
            fmt.Println("WriteRedis running")
            time.Sleep(2 * time.Second)
        }
    }
}

func WriteDatabase(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("WriteDatabase Done.")
            return
        default:
            fmt.Println("WriteDatabase running")
            time.Sleep(2 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go HandelRequest(ctx)

    time.Sleep(5 * time.Second)
    fmt.Println("It's time to stop all sub goroutines!")
    cancel()

    //Just for test whether sub goroutines exit or not
    time.Sleep(5 * time.Second)
}

反射

反射式是检查自身结构的能力

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)  //t is reflect.Type
    fmt.Println("type:", t)

    v := reflect.ValueOf(x) //v is reflect.Value
    fmt.Println("value:", v)
type: float64
value: 3.4

定时器

Timer

通过chan在往后一定时间,触发某一段程序

Ticker

通过chan周期性的触发某一段程序

timer

通过四叉堆来管理Timer和Ticker

工具

gc

pprof

package main

import (
    "log"
    "runtime"
    "time"
    "net/http"
    _ "net/http/pprof"
)

func readMemStats() {

    var ms runtime.MemStats

    runtime.ReadMemStats(&ms)

    log.Printf(" ===> Alloc:%d(bytes) HeapIdle:%d(bytes) HeapReleased:%d(bytes)", ms.Alloc, ms.HeapIdle, ms.HeapReleased)
}

func test() {
    //slice 会动态扩容,用slice来做堆内存申请
    container := make([]int, 8)

    log.Println(" ===> loop begin.")
    for i := 0; i < 32*1000*1000; i++ {
        container = append(container, i)
        if ( i == 16*1000*1000) {
            readMemStats()
        }
    }

    log.Println(" ===> loop end.")
}

func main() {


    //启动pprof
    go func() {
        log.Println(http.ListenAndServe("0.0.0.0:10000", nil))
    }()

    log.Println(" ===> [Start].")

    readMemStats()
    test()
    readMemStats()

    log.Println(" ===> [force gc].")
    runtime.GC() //强制调用gc回收

    log.Println(" ===> [Done].")
    readMemStats()

    go func() {
        for {
            readMemStats()
            time.Sleep(10 * time.Second)
        }
    }()

    time.Sleep(3600 * time.Second) //睡眠,保持程序不退出
}

输入地址:http://127.0.0.1:10000/debug/pprof/heap?debug=1

# ...

# runtime.MemStats
# Alloc = 228248
# TotalAlloc = 1293696976
# Sys = 834967896
# Lookups = 0
# Mallocs = 2018
# Frees = 671
# HeapAlloc = 228248
# HeapSys = 804913152
# HeapIdle = 804102144
# HeapInuse = 811008
# HeapReleased = 108552192
# HeapObjects = 1347
# Stack = 360448 / 360448
# MSpan = 28288 / 32768
# MCache = 3472 / 16384
# BuckHashSys = 1449617
# GCSys = 27418976
# OtherSys = 776551
# NextGC = 4194304
# LastGC = 1583203571137891390

# ...

CPU

package main

import (
    "bytes"
    "math/rand"
    "time"
    "log"
    "net/http"
    _ "net/http/pprof"
)


func test() {

    log.Println(" ===> loop begin.")
    for i := 0; i < 1000; i++ {
        log.Println(genSomeBytes())
    }

    log.Println(" ===> loop end.")
}

//生成一个随机字符串
func genSomeBytes() *bytes.Buffer {

    var buff bytes.Buffer

    for i := 1; i < 20000; i++ {
        buff.Write([]byte{'0' + byte(rand.Intn(10))})
    }

    return &buff
}

func main() {

    go func() {
        for {
            test()
            time.Sleep(time.Second * 1)
        }
    }()

    //启动pprof
    http.ListenAndServe("0.0.0.0:10000", nil)

}
$ go tool pprof ./demo4 profile

File: demo4
Type: cpu
Time: Mar 3, 2020 at 11:18pm (CST)
Duration: 30.13s, Total samples = 6.27s (20.81%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
(pprof) top
Showing nodes accounting for 5090ms, 81.18% of 6270ms total
Dropped 80 nodes (cum <= 31.35ms)
Showing top 10 nodes out of 60
      flat  flat%   sum%        cum   cum%
    1060ms 16.91% 16.91%     2170ms 34.61%  math/rand.(*lockedSource).Int63
     850ms 13.56% 30.46%      850ms 13.56%  sync.(*Mutex).Unlock (inline)
     710ms 11.32% 41.79%     2950ms 47.05%  math/rand.(*Rand).Int31n
     570ms  9.09% 50.88%      990ms 15.79%  bytes.(*Buffer).Write
     530ms  8.45% 59.33%      540ms  8.61%  syscall.Syscall
     370ms  5.90% 65.23%      370ms  5.90%  runtime.procyield
     270ms  4.31% 69.54%     4490ms 71.61%  main.genSomeBytes
     250ms  3.99% 73.52%     3200ms 51.04%  math/rand.(*Rand).Intn
     250ms  3.99% 77.51%      250ms  3.99%  runtime.memmove
     230ms  3.67% 81.18%      690ms 11.00%  runtime.suspendG
(pprof)s
./demo4
$ go tool pprof http://localhost:10000/debug/pprof/profile?seconds=60
Fetching profile over HTTP from http://localhost:10000/debug/pprof/profile?seconds=60
Saved profile in /home/itheima/pprof/pprof.demo4.samples.cpu.005.pb.gz
File: demo4
Type: cpu
Time: Mar 3, 2020 at 11:59pm (CST)
Duration: 1mins, Total samples = 12.13s (20.22%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 9940ms, 81.95% of 12130ms total
Dropped 110 nodes (cum <= 60.65ms)
Showing top 10 nodes out of 56
      flat  flat%   sum%        cum   cum%
    2350ms 19.37% 19.37%     4690ms 38.66%  math/rand.(*lockedSource).Int63
    1770ms 14.59% 33.97%     1770ms 14.59%  sync.(*Mutex).Unlock (inline)
    1290ms 10.63% 44.60%     6040ms 49.79%  math/rand.(*Rand).Int31n
    1110ms  9.15% 53.75%     1130ms  9.32%  syscall.Syscall
     810ms  6.68% 60.43%     1860ms 15.33%  bytes.(*Buffer).Write
     620ms  5.11% 65.54%     6660ms 54.91%  math/rand.(*Rand).Intn
     570ms  4.70% 70.24%      570ms  4.70%  runtime.procyield
     500ms  4.12% 74.36%     9170ms 75.60%  main.genSomeBytes
     480ms  3.96% 78.32%      480ms  3.96%  runtime.memmove
     440ms  3.63% 81.95%      440ms  3.63%  math/rand.(*rngSource).Uint64
(pprof)

ref:https://www.topgoer.cn/docs/gozhuanjia/gogfjhk

相关文章

网友评论

      本文标题:Go

      本文链接:https://www.haomeiwen.com/subject/shvunrtx.html