并发

golang channle学习

TT
学习 golang channel 源码的记录,都注释在源码中了。 文章参考: 微信公众号-Golang梦工厂 极客时间专栏-Go并发编程实战课 channel 的数据结构 type hchan struct { qcount uint // 循环队列中元素的数量,就是还没被取走的 dataqsiz uint // 循环队列的大小 buf unsafe.Pointer // 存放元素的循环队列,大小总是elemsize的整数倍 elemsize uint16 // chan 中元素的大小 closed uint32 // chan 是否关闭 elemtype *_type // chan 中元素的类型 sendx uint // 处理发送数据的指针在 buf 中的位置。一旦接收了新的数据,指针就会加上 elemsize,移向下一个位置 recvx uint // 处理接收请求时的指针在 buf 中的位置。一旦取出数据,此指针会移动到下一个位置 sendq waitq // 如果生产者因为 buf 满了而阻塞,会被加入到 sendq 队列中 recvq waitq // chan 是多生产者多消费者的模式,如果消费者因为没有数据可读而被阻塞了,就会被加入到 recvq 队列中 lock mutex // 保护所有字段 } channel 的创建 使用 make 创建 channel 时,编译后对应 runtime.

Go sync.Map 学习

TT
一些结论 sync.Map 适用场景 只会增长的缓存系统中,一个 key 只写入一次而被读很多次; 多个 goroutine 为不相交的键集读、写和重写键值对。 官方叮嘱:建议你针对自己的场景做性能评测,如果确实能够显著提高性能,再使用 sync.Map。 sync.Map 源码中的一些思想 空间换时间。通过冗余的两个数据结构(只读的 read 字段、可写的 dirty),来减少加锁对性能的影响。对只读字段(read)的操作不需要加锁。 优先从 read 字段读取、更新、删除,因为对 read 字段的读取不需要锁。 动态调整。miss 次数多了之后,将 dirty 数据提升为 read,避免总是从 dirty 中加锁读取。 double-checking。加锁之后先还要再检查 read 字段,确定真的不存在才操作 dirty 字段。 延迟删除。删除一个键值只是打标记,只有在提升 dirty 字段为 read 字段的时候才清理删除的数据。 sync.Map 结构图 疑问 1. 为什么说 read 是并发读写安全的? 2. read 为什么可以更新 key 对应的 value?dirty 中会同步更新吗? 3. map 的 misses 是什么?干嘛用的? 4. 什么时候 misses 会变化? 5. readOnly 的 amended 是什么? 6. 什么时候会改变 amended? 7.

Go Cond 学习

TT
为等待 / 通知场景下的并发问题提供支持。Cond 通常应用于等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine 或者所有的 goroutine 都会被唤醒执行。 说点人话吧…… cond 分析 我们来看一下 Cond 提供的方法 func NewCond(l Locker) *Cond {} // 创建一个 cond func (c *Cond) Wait() {} // 阻塞,等待唤醒 func (c *Cond) Signal() {} // 唤醒一个等待者 func (c *Cond) Broadcast() {} // 唤醒所有等待者 第一步:创建一个 cond c := sync.NewCond(&sync.Mutex{}) 第二步:将 goroutine 阻塞在 c 上 // 这里会有坑,下文再讨论 c.Wait() 第三步:唤醒 // 唤醒所有等待者 c.Broadcast() // 唤醒一个等待者 c.Signal() 这里再回过头去看 cond 的作用,应该清晰了不少,我们再结合一个例子来看一下。

Go sync.Once 学习

TT
Once 常常用来初始化单例资源,或者并发访问只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源。 如何初始化单例 1. 定义 package 级别的变量 package test var pi = 3.1415926 2. 在 init() 函数中初始化 package test var pi float64 func init() { pi = 3.1415926 } 3. 在 main 函数中执行初始化逻辑 package test var pi float64 func initApp() { pi = 3.1415926 } func main() { initApp() } 以上三种方法都是线程安全的,后两种方法甚至可以提供定制化的初始化服务,但是它们有一个共同的缺点:不能实现延迟初始化。 延迟初始化 // 使用互斥锁保证线程(goroutine)安全 var mu sync.Mutex var pi float64 func getPI() float64 { mu.Lock() defer mu.Unlock() if pi != 0 { return pi } pi = 3.

Go Mutex 源码浅析 & Mutex 演变历程

TT
文章的内容参考了极客时间专栏《Go 并发编程实战课》,结合自己的理解写了一篇总结。 作者将 Mutex 的演进划分成了 4 个阶段: 初版 使用一个 key 字段标记是否持有锁,以及等待该锁的 goroutine 数量 源码下载地址 部分代码片段如下: package sync import "runtime" // CAS操作,当时还没有抽象出atomic包 func cas(val *uint32, old, new uint32) bool // 互斥锁的结构,包含两个字段 type Mutex struct { key uint32; // 锁是否被持有的标识,0:锁未被持有;1:锁被持有,且没有其它等待者;n:锁被持有,同时还有 n-1 个竞争者 sema uint32; //信号量专用,用以阻塞/唤醒goroutine } // 保证成功在val上增加delta的值 func xadd(val *uint32, delta int32) (new uint32) { for { v := *val; nv := v+uint32(delta); if cas(val, v, nv) { return nv; } } panic("unreached"); } // 请求锁 func (m *Mutex) Lock() { if xadd(&m.

Go go:linkname 是个啥

TT
问题发现 今天阅读 golang 源码时,看到一个函数 runtime_canSpin 大概意思是判断能否进行自旋,想查看它的实现,idea 跳转进去以后在 sync/runtime.go 文件里只看到了函数的定义 // Active spinning runtime support. // runtime_canSpin reports whether spinning makes sense at the moment. func runtime_canSpin(i int) bool 并没有函数的实现,同目录下也没有找到汇编代码的实现,那函数的具体实现在哪里? 找答案 clone 了 golang 源码,全局搜索 runtime_canSpin,在 runtime/proc.go 文件找到了如下的代码块: // Active spinning for sync.Mutex. //go:linkname sync_runtime_canSpin sync.runtime_canSpin //go:nosplit func sync_runtime_canSpin(i int) bool { // sync.Mutex is cooperative, so we are conservative with spinning. // Spin only few times and only if running on a multicore machine and // GOMAXPROCS>1 and there is at least one other running P and local runq is empty.

Go Context

TT
文中例子参考:https://www.flysnow.org/2017/05/12/go-in-action-go-context.html 1. 使用 chan + select 控制 goroutine 停止 package main import ( "fmt" "time" ) func main() { stop := make(chan bool) go func() { for { select { case value1, ok1:= <-stop: fmt.Println(value1, ok1) fmt.Println("监控退出,停止了...1") return default: fmt.Println("goroutine监控中...1") time.Sleep(2 * time.Second) } } }() go func() { for { select { case value2, ok2:= <-stop: fmt.Println(value2, ok2) fmt.Println("监控退出,停止了...2") return default: fmt.Println("goroutine监控中...2") time.Sleep(2 * time.Second) } } }() time.

Go 防缓存击穿利器 - singleflight

TT
1. 需求描述 热点数据保存在缓存中,缓存过期的瞬间,避免大量的请求直接打到数据库服务器上。 2. singleflight 使用 demo package main import ( "errors" "fmt" "golang.org/x/sync/singleflight" "sync" "sync/atomic" ) var ErrNotFund = errors.New("not fund") func main() { var clt = NewCacheClient() wg := sync.WaitGroup{} wg.Add(10) for i := 0; i < 10; i++ { //模拟10个并发请求从缓存获取数据 go func() { defer wg.Done() clt.Get("a") }() } wg.Wait() } type CacheClient struct { keys atomic.Value //实际存储的是 map[string]string group singleflight.Group } func NewCacheClient() *CacheClient { clt := new(CacheClient) m := make(map[string]string) clt.

什么是 CAS

TT
什么是 CAS CAS 的全称是 Compare And Swap. 该指令会将给定的值和一个内存地址中的值进行比较,如果它们是同一个值,就使用新值替换内存地址中的值,整个操作过程是原子性的。 那啥是原子性呢? 原子性我们只需要记住一点,就是这个操作总是基于最新的值进行计算,如果同时有其它线程已经修改了这个值,那么这次操作不成功。 听起来还是很绕?贴个 golang 源码片段(还带汇编的哦) // func cas(val *int32, old, new int32) bool // Atomically: // if *val == old { // *val = new; // return true; // }else // return false; TEXT sync·cas(SB), 7, $0 MOVQ 8(SP), BX MOVL 16(SP), AX MOVL 20(SP), CX LOCK CMPXCHGL CX, 0(BX) JZ ok MOVL $0, 24(SP) RET ok: MOVL $1, 24(SP) RET 再贴一段早期的 Mutex 源码片段 源码地址