当您构建 Golang 程序时,您几乎总会发现 Goroutines 的用途。
Goroutines 功能强大且通常易于使用,但是,如果您需要修改 Goroutines 之间共享的某些数据,那么您可能会遇到数据完整性方面的问题。
在本文中,我们将了解什么是“互斥体”以及如何使用它。
什么是互斥体?
在Golang中;Goroutine 本质上是一个放在后台队列中并在资源可用时并发执行的函数。
package main import ( "fmt" "sync" ) var NUM_PROCESSED = 0 func countNumProcessed(wg *sync.WaitGroup) { defer wg.Done() NUM_PROCESSED++ } func main() { var wg sync.WaitGroup for i := 0; i < 500; i++ { wg.Add(1) go countNumProcessed(&wg) } wg.Wait() fmt.Println(NUM_PROCESSED) }
如果我们删除“go”关键字,则对“countNumProcessed”的每个函数调用都会阻塞循环并等待该函数完成后再继续循环。
当您使用“go”关键字时,这些函数将同时运行。这使得 2 个或更多函数可以同时修改变量。
如果您运行此代码几次 - 您会注意到总计数会波动。
这是因为每个 Goroutine 在几纳秒内复制内存中的“NUM_PROCESSED”值以递增它,然后更新变量。
如果两个(或更多)Goroutine 大约在同一时间复制该值,它们将不会意识到其他 Goroutine 所做的更新。
例如:假设值为“200”;两个 Goroutines 中的每一个都会添加“1”,即“201”。因此,“NUM_PROCESSED”的值将变为“201”而不是“202”。
这就是互斥体派上用场的地方。互斥锁会在进程中创建一把“锁”——这样一次只有一个 Goroutine 可以更新“NUM_PROCESSED”。
然后其他 Goroutines 将暂停,直到锁被释放。这与队列非常相似 - 当每个 Goroutine 释放锁时,下一个 Goroutine 会获取新锁,并且该过程将继续,直到所有排队的 Goroutine 完成更新“NUM_PROCESSED”。
互斥体示例
我们可以修改上面的代码来引入一个Mutex,如下所示:
package main import ( "fmt" "sync" ) var NUM_PROCESSED = 0 var MUTEX sync.Mutex func countNumProcessed(wg *sync.WaitGroup) { defer wg.Done() MUTEX.Lock() NUM_PROCESSED++ MUTEX.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 500; i++ { wg.Add(1) go countNumProcessed(&wg) } wg.Wait() fmt.Println(NUM_PROCESSED) }
您会注意到上面的代码,无论您运行此函数多少次,它总是会打印“500”。这与 for 循环中创建的 goroutine 数量完全相同。