1、channel是一个指针
slice和map
makemap 和 makeslice 的区别,带来一个不同点:当 map 和 slice 作为函数参数时,在函数参数内部对 map 的操作会影响 map 自身;而对 slice 却不会
主要原因:一个是指针( *hmap),一个是结构体( slice)。Go 语言中的函数传参都是值传递,在函数内部,参数会被 copy 到本地。*hmap指针 copy 完之后,仍然指向同一个 map,因此函数内部对 map 的操作会影响实参。而 slice 被 copy 后,会成为一个新的 slice,对它进行的操作不会影响到实参。
但是操作slice中的数组会影响数据,因为新的slice仍然是指向同个数组
GMP
- 如果g进入了系统调用状态,那么与其绑定的M也将进入系统调用状态
struct 比较
相同类型的struct:
含有map、slice等不可比较的属性 则 struct不可以比较,==编译通不过
若所有属性都可以比较,那么可以比较
不同类型的struct:
若符合↑,强转后可以比较
reflect.DeepEqual ptr1==ptr2 || 所有的变量相等
defer
延迟执行语句,在所属的函数退出之前,按defer的逆序进行执行
(close关闭一个无缓冲的通道,会让其由阻塞变为不断读出0值的通道?)
context包
谷歌官方开发的,用于对一个请求衍生出来的各个goroutine施加约束。比如context传递给多个goroutine使用时,执行一次cancel就可以取消所有的goroutine。
协程同步
select +chan
waitGroup
context
什么是goroutine chan
goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。
goroutine 是 Go语言程序的并发体的话,那么 channels 就是它们之间的通信机制。它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。
chan:
- 关闭的chan进行写操作会panic,读操作会返回类型零值和false
- 单方向通道不可以隐式转换为普通通道,反之可以
- 通道数据结构hchan是数组和下标指针模拟环形队列、读写等待协程链表
- chan写入:
1、读等待协程链表有协程那么直接获取第一个等待读数据的协程,并把数据复制到那个协程并唤醒他
2、没有等待的读协程,环形缓冲队列还有位置
3、缓冲队列没有位置那么将当前协程加入写等待协程链表
select
- select和switch类似,但是每个case都是一个通道读写
- 多个case的通道准备好数据时,随机选择一个
- 只有一个case的时候,编译器会优化去掉select
- 两轮循环,第一轮循环检查是否有准备好的case,或者default,没有的话第二轮循环创建一个sudog结构体,放入所有case中的读写通道的读写等待协程链表后面,并阻塞当前线程。当任意一个case不再阻塞,唤醒当前协程,并取消当前协程的sudog挂在其他case中的通道的等待链表
race是通过谷歌开发的Threadsantinizer检测的,本来是检测C++的数据争用问题,go中通过GOC调用
自旋锁
获取到锁才操作,没有获取到就一直循环等待锁
mutex互斥锁
结构体 m{state int32 , sema uint32}
高29位是当前互斥锁上等待的goroutine个数
饥饿状态位,唤醒状态位,锁定状态位 低三位
加锁过程
原子CAS操作加锁,如果发现锁定状态位为1,那么进入慢加锁过程
- 如果是非饥饿模式,且满足(多核多P,当前P本地队列无其他协程,自旋次数小于4)则先自旋获取锁,30次pause
- 如果长时间未获取到锁那么开始用信号量同步
。。。
读写锁
读写锁复用了互斥锁,写锁只能有一个,需要互斥获取
type RWMutex struct{
w Mutex
writerSem uint32
readerSem uint32
readerCount int32
readerWait int32
}
写锁加锁
获取互斥锁,减去一个很大的数,表示有写锁
获取互斥锁之后,将readerCount 减去1<<30 ,为了告诉reader在尝试加写锁或者已经有写锁,因为一般的readerCount在增加读者时,也就几个几十个,释放读锁时,readerCount原子操作减一,最小为0。
r表示原来的readerCount,将其赋值给readerWait表示阻塞的写锁等待这么多的读者退出
然后对writerSem信号量p操作阻塞住
读锁加锁
读锁可以加很多把,根据上面的写锁,当有写锁在准备加或者已经有写锁时,阻塞当前协程,在readerSem上进行p操作
读锁释放
readerCount原子操作减一,若结果是负数,说明有写锁等待或者有写锁,那么尝试对readerWait(加写锁时还有的读者数量)减一,恰好是最后一个读者,那么对writerSem进行V操作,唤醒欲加写锁的协程。
写锁释放
将readerCount+那个很大的数,结果为r,在写锁期间,有r个协程阻塞,对readerSem 进行r次V操作,依次唤醒。