数据结构
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
- 使用数组创建slice
- 扩容
如果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表
使用渐进式扩容
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):处理器
每个线程都维持着协程队列
线程在得到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)
网友评论