go并发编程sync.Cond使用场景及实现原理
来源:脚本之家
时间:2023-01-16 13:39:27 195浏览 收藏
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《go并发编程sync.Cond使用场景及实现原理》,主要介绍了go并发编程、sync.Cond,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!
使用场景
sync.Cond是go标准库提供的一个条件变量,用于控制一组goroutine在满足特定条件下被唤醒。
sync.Cond常用于一组goroutine等待,一个goroutine通知(事件发生)的场景。如果只有一个goroutine等待,一个goroutine通知(事件发生),使用Mutex或者Channel就可以实现。
可以用一个全局变量标志特定条件condition,每个sync.Cond都必须要关联一个互斥锁(Mutex或者RWMutex),当condition发生变更或者调用Wait时,都必须加锁,保证多个goroutine安全地访问condition。
下面是go标准库http中关于pipe的部分实现,我们可以看到,pipe使用sync.Cond来控制管道中字节流的写入和读取,在pipe中数据可用并且字节流复制到pipe的缓冲区之前,所有的需要读取该管道数据的goroutine都必须等待,直到数据准备完成。
type pipe struct { mu sync.Mutex c sync.Cond // c.L lazily initialized to &p.mu b pipeBuffer // nil when done reading ... }
// Read waits until data is available and copies bytes // from the buffer into p. func (p *pipe) Read(d []byte) (n int, err error) { p.mu.Lock() defer p.mu.Unlock() if p.c.L == nil { p.c.L = &p.mu } for { ... if p.b != nil && p.b.Len() > 0 { return p.b.Read(d) } ... p.c.Wait() // write未完成前调用Wait进入等待 } }
// Write copies bytes from p into the buffer and wakes a reader. // It is an error to write more data than the buffer can hold. func (p *pipe) Write(d []byte) (n int, err error) { p.mu.Lock() defer p.mu.Unlock() if p.c.L == nil { p.c.L = &p.mu } defer p.c.Signal() // 唤醒所有等待的goroutine if p.err != nil { return 0, errClosedPipeWrite } if p.breakErr != nil { p.unread += len(d) return len(d), nil // discard when there is no reader } return p.b.Write(d) }
实现原理
type Cond struct { noCopy noCopy // 用来保证结构体无法在编译期间拷贝 // L is held while observing or changing the condition L Locker // 用来保证condition变更安全 notify notifyList // 待通知的goutine列表 checker copyChecker // 用于禁止运行期间发生的拷贝 }
type notifyList struct { wait uint32 // 正在等待的goroutine的ticket notify uint32 // 已经通知到的goroutine的ticket lock uintptr // key field of the mutex head unsafe.Pointer // 链表头部 tail unsafe.Pointer // 链表尾部 }
copyChecker
copyChecker是一个指针类型,在创建时,它的值指向自身地址,用于检测该对象是否发生了拷贝。如果发生了拷贝,则直接panic。
// copyChecker holds back pointer to itself to detect object copying. type copyChecker uintptr func (c *copyChecker) check() { if uintptr(*c) != uintptr(unsafe.Pointer(c)) && !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) && uintptr(*c) != uintptr(unsafe.Pointer(c)) { panic("sync.Cond is copied") } }
Wait
调用 Wait 会自动释放锁 c.L,并挂起调用者所在的 goroutine,因此当前协程会阻塞在 Wait 方法调用的地方。如果其他协程调用了 Signal 或 Broadcast 唤醒了该协程,那么 Wait 方法在结束阻塞时,会重新给 c.L 加锁,并且继续执行 Wait 后面的代码。
对条件的检查,使用了 for !condition()
而非 if
,是因为当前协程被唤醒时,条件不一定符合要求,需要再次 Wait 等待下次被唤醒。为了保险起见,使用 for
能够确保条件符合要求后,再执行后续的代码。
func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() }
- 检查Cond是否被复制,如果被复制,直接panic;
- 调用runtime_notifyListAdd调用者添加到通知列表并解锁,以便可以接收到通知,然后将返回的ticket传入到runtime_notifyListWait来等待通知。
- 当前goroutine会阻塞在wait调用的地方,直到其他goroutine调用Signal或Broadcast唤醒该协程。
func notifyListAdd(l *notifyList) uint32 { return atomic.Xadd(&l.wait, 1) - 1 }
notifyListWait会将当前goroutine追加到链表的尾端,同时调用goparkunlock让当前goroutine陷入休眠,该方法会直接让出当前处理器的使用权并等待调度器的唤醒。
func notifyListWait(l *notifyList, t uint32) { s := acquireSudog() s.g = getg() s.ticket = t if l.tail == nil { l.head = s } else { l.tail.next = s } l.tail = s goparkunlock(&l.lock, waitReasonSyncCondWait, traceEvGoBlockCond, 3) releaseSudog(s) }
Signal
Signal会唤醒队列最前面的Goroutine。
func (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) }
func notifyListNotifyOne(l *notifyList) { t := l.notify atomic.Store(&l.notify, t+1) for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next { if s.ticket == t { n := s.next if p != nil { p.next = n } else { l.head = n } if n == nil { l.tail = p } s.next = nil readyWithTime(s, 4) return } } }
Broadcast
Broadcast会唤醒队列中全部的goroutine。
func (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
func notifyListNotifyAll(l *notifyList) { s := l.head l.head = nil l.tail = nil atomic.Store(&l.notify, atomic.Load(&l.wait)) for s != nil { next := s.next s.next = nil readyWithTime(s, 4) s = next } }
以上就是《go并发编程sync.Cond使用场景及实现原理》的详细内容,更多关于golang的资料请关注golang学习网公众号!
-
185 收藏
-
459 收藏
-
491 收藏
-
153 收藏
-
488 收藏
-
165 收藏
-
473 收藏
-
377 收藏
-
384 收藏
-
246 收藏
-
110 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习