并发
goroutine 不是os线程、不是绿色线程(由语言运行时管理的线程),是协程。协程是一种非抢占式的简单并发子goroutine(函数、闭包或方法),也就是说,它们不能被中断。取而代之的是,协程有多个点,允许暂停或重新进入 —Go语言并发之道
goroutinechannelsgoroutine
基础知识
goroutine
goroutine是go
channel 通道
通道单向通道无缓冲的通道带缓冲的通道同步信号(shutdown/close/finish)消息传递(queue/stream)互斥(mutex)
//go中对chan的定义 /runtime/chan.go
type hchan struct {
qcount uint // total data in the queue 数据总量
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32 //标记channel已经关闭
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters 和ring buffer相关
sendq waitq // list of send waiters 和ring buffer相关
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex //保护所有数据结构,但在某些quick path场景下不必加锁
}
type waitq struct {
first *sudog
last *sudog //sudog dequeue中等待的goroutine以及他的数据存储
}
- 任何时间只能有一个goroutine访问chan
- chan 里面的数据满足FIFO规则
//声明通道
var 通道变量 chan 通道类型
//创建通道
通道实例 := make(chan 通道类型)
//给通道发送数据
通道变量 <-值
//读取通道数据
data, ok := <-通道变量
defer关键字
defer
go调度模型
goroutine系统线程Processor调度实体
实现方式
syn包
进行多个任务的同步,主要操作有Add()、Done()、Wait()等操作
- 互斥锁和读写锁
sync.Mutexsync.RWMutex
使用注意事项:
1、配套使用Lock、UnLock
2、运行时离开当前逻辑就释放锁
3、锁的粒度越小越好,加锁后尽快释放锁
4、没有特殊原因,尽量不要defer释放锁
5、RWMutex的读锁不要嵌套使用
- List item
- cond
sync.CondBroadcast()Signal()Wait()L LockBroadcast()Signal()Wait()
- once
sync.Once
func main() {
var count int
increment := func() {
count++
}
var once sync.Once
var increments sync.WaitGroup
increments.Add(100)
for i:=0;i<100;i++{
go func() {
defer increments.Done()
once.Do(increment)
}()
}
increments.Wait()
fmt.Printf("count is %d\n", count)
}
//最终输出为 1
- 池
池(pool)是Pool模式的并发安全实现,需要参照pool设计模式
channel
select 语句
GOMAXPROCS控制
runtime.GOMAXPROCS(runtime.NumCPU()) //充分利用cpu资源
Go语言的并发通过
channelgoroutineCSP(Communicating Sequential Process)
经典例子分析
case1
func handler(){
ch := make(chan string)
go func() {
time.Sleep(3*time.Second)
ch <- "job result"
}()
select {
case result := <- ch:
fmt.Println(result)
case <-time.After(time.Second):
return
}
}
上述代码会造成goroutine泄漏,原因在于channel没有缓存
可能造成goroutine泄漏的原因有:
1、channel没有缓存
2、select命中timeout逻辑
3、导致channel没有消费者
4、最终导致anonymous goroutine泄漏
case2
var mu sync.RWMutex
func main() {
go A()
time.Sleep(2*time.Second)
fmt.Println("main call lock")
mu.Lock()
defer mu.Unlock()
}
func A() {
fmt.Println("A call rlock")
mu.RLock()
fmt.Println("A rlocked")
defer mu.RUnlock()
B()
}
func B(){
time.Sleep(5*time.Second)
C()
}
func C() {
fmt.Println("C call rlock")
mu.RLock()
fmt.Println("C rlocked")
mu.RUnlock()
}
运行结果如下:
谨慎使用锁的递归调用,上面的还需再看一看
case3
map
gomap
- hash算法:AES
- 冲突解决:链地址法(和java类似) Python采用开放定址法
- range go每次rangemap的时候顺序都会不同,因为go故意实现了random 【需要代码论据】
- 装填因子 6.5
- rehash 渐进式rehash (和redis类似)