app自助建站,西安seo培训学校,html制作表格,东莞外贸推广在学习 Go 编程语言时#xff0c;您可能会遇到这句著名的格言#xff1a;“不要通过共享内存来进行通信#xff1b;相反#xff0c;通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础#xff0c;其中通道#xff08;channels#xff09;作为协程之间的主要通信… 在学习 Go 编程语言时您可能会遇到这句著名的格言“不要通过共享内存来进行通信相反通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础其中通道channels作为协程之间的主要通信工具。然而虽然通道是管理并发的多功能工具但错误地假设我们应该始终用通道替换传统的锁定机制如 Mutex是一个错误的观念。在某些情况下使用 Mutex 不仅恰当而且比通道更有效。 在我的 Go 并发可视化系列中今天我将通过视觉方式来解释 sync.Mutex。 Golang 基础 场景 想象一下有四位 Gopher 自行车手每天骑车上班。他们都需要在到达办公室后洗个澡但办公室只有一个浴室。为了防止混乱他们确保一次只能有一个人使用浴室。这种独占式访问的概念正是 Go Mutex互斥锁的核心。 每天早上在办公室洗澡对自行车手和跑步者来说是一个小小的竞争。 普通模式 今天最早到达的是 Stringer。当他来的时候没有人在使用浴室因此他可以立即使用浴室。 对一个未加锁的 Mutex 调用 Lock() 会立即成功。 片刻后Partier 到了。Partier 发现有人在使用浴室但他不知道是谁也不知道什么时候会结束使用。此时他有两个选择站在浴室前面主动等待或者离开并稍后再回来被动等待。按 Go 的术语前者被称为“自旋”spinning。自旋的协程会占用 CPU 资源增加了在锁定可用时获取 Mutex 的机会而无需进行昂贵的上下文切换。然而如果 Mutex 不太可能很快可用继续占用 CPU 资源会降低其他协程获取 CPU 时间的机会。 从版本 1.21 开始Golang 允许到达的协程自旋一段时间。如果在指定时间内无法获取 Mutex它将进入休眠状态以便其他协程有机会运行。 到达的协程首先自旋然后休眠。 Candier 到了。就像 Partier 一样她试图获取浴室。 因为她刚到如果 Stringer 很快释放浴室她就有很大的机会在被动等待之前获取它。这被称为普通模式。 普通模式的性能要好得多因为协程可以连续多次获取 Mutex即使有阻塞的等待者。 1*GJ7OW0_8z_8QjXPa2cFxPw.png go/src/sync/mutex.go at go1.21.0 · golang/go · GitHub[1] 新到达的协程在争夺所有权时具有优势 饥饿模式 Partier 回来了。由于他等待的时间很长超过 1 毫秒他将尝试以饥饿模式获取浴室。当 Swimmer 来时他注意到有人饿了他不会尝试获取浴室也不会自旋。相反他会排队在等待队列的尾部。 在这种饥饿模式下当 Candier 结束时她会直接把浴室交给 Partier。此时没有竞争。 饥饿模式是防止尾延迟的病理情况的重要措施。 Partier 完成了他的回合并释放了浴室。此时只有 Swimmer 在等待因此他将立即拥有它。Swimmer 如果发现自己是最后一个等待的人他会将 Mutex 设置回普通模式。如果他发现自己的等待时间少于 1 毫秒也会这样做。 最后Swimmer 在使用浴室后释放了它。请注意Mutex 不会将所有者从“已锁定由 Goroutine A 锁定”状态更改为“已锁定由 Goroutine B 锁定”状态。它始终会在“已锁定”到“未锁定”然后再到“已锁定”的状态之间切换。出于简洁起见上面的图像中省略了中间状态。 展示代码 Mutex 的实现随时间而变化实际上要完全理解它的实现并不容易。幸运的是我们不必完全理解其实现就能高效使用它。如果从这篇博客中只能记住一件事那一定是早到的人不一定赢得比赛。相反新到达的协程通常具有更高的机会因为它们仍在 CPU 上运行。Golang 还尝试避免通过实现饥饿模式来使等待者被饿死。 package mainimport (fmtsynctime
)func main() {wg : sync.WaitGroup{}wg.Add(4)bathroom : sync.Mutex{}takeAShower : func(name string) {defer wg.Done()fmt.Printf(%s: I want to take a shower. Im trying to acquire the bathroom\n, name)bathroom.Lock()fmt.Printf(%s: I have the bathroom now, taking a shower\n, name)time.Sleep(500 * time.Microsecond)fmt.Printf(%s: Im done, Im unlocking the bathroom\n, name)bathroom.Unlock()}go takeAShower(Partier)go takeAShower(Candier)go takeAShower(Stringer)go takeAShower(Swimmer)wg.Wait()fmt.Println(main: Everyone is Done. Shutting down...)
} 正如您可能猜到的并发代码的结果几乎总是非确定性的。 第一次 Swimmer: I want to take a shower. Im trying to acquire the bathroom Partier: I want to take a shower. Im trying to acquire the bathroom Candier: I want to take a shower. Im trying to acquire the bathroom Stringer: I want to take a shower. Im trying to acquire the bathroom Swimmer: I have the bathroom now, taking a shower Swimmer: Im done, Im unlocking the bathroom Partier: I have the bathroom now, taking a shower Partier: Im done, Im unlocking the bathroom Candier: I have the bathroom now, taking a shower Candier: Im done, Im unlocking the bathroom Stringer: I have the bathroom now, taking a shower Stringer: Im done, Im unlocking the bathroom main: Everyone is Done. Shutting down... 第二次 Swimmer: I want to take a shower. Im trying to acquire the bathroom Swimmer: I have the bathroom now, taking a shower Partier: I want to take a shower. Im trying to acquire the bathroom Stringer: I want to take a shower. Im trying to acquire the bathroom Candier: I want to take a shower. Im trying to acquire the bathroom Swimmer: Im done, Im unlocking the bathroom Partier: I have the bathroom now, taking a shower Partier: Im done, Im unlocking the bathroom Stringer: I have the bathroom now, taking a shower Stringer: Im done, Im unlocking the bathroom Candier: I have the bathroom now, taking a shower Candier: Im done, Im unlocking the bathroom main: Everyone is Done. Shutting down... 自己实现 Mutex 实现 sync.Mutex 是困难的但使用具有缓冲的通道来实现 Mutex 却相当容易。 type MyMutex struct {ch chan bool
}func NewMyMutex() *MyMutex {return MyMutex{// 缓冲大小必须为 1ch: make(chan bool, 1),}
}// Lock 锁定 m。
// 如果锁已被使用调用的协程将被阻塞直到 Mutex 可用。
func (m *MyMutex) Lock() {[m.ch](http://m.ch) - true
}// Unlock 解锁 m。
func (m *MyMutex) Unlock() {-m.ch
} 这篇文章通过生动的场景和可视化效果很好地解释了 Go 语言中 sync.Mutex 的工作原理以及如何使用互斥锁来管理并发 相关系列文章 使用通信顺序进程CSP模型的 Go 语言通道 Go并发可视化解释 – select语句 以可视化方式解释 Go 并发 - 通道