go_Mutex(互斥锁)

当一个程序并发运行时,修改共享资源的部分代码不应该被多个Goroutines同时访问
修改共享资源的这段代码被称为临界区

使用互斥锁,限定临界区只能同时由一个线程持有

互斥锁

Mutex用于提供一种锁定机制,以确保在任何时间点只有一个Goroutine正在运行代码的关键部分,以防止竞态条件

互斥锁在同步中可用。Lock和Unlock

mutex.Lock()
x = x+1
mutex.Unlock()

在上面的代码中,x = x + 1在任何时间点都只会由一个 Goroutine 执行,从而防止竞争条件。

具有竞争条件的程序
package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup) {  
    x = x + 1
    wg.Done()
}
func main() {  
    var w sync.WaitGroup
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

在上面的程序中,increment函数在第7行将x增加1。然后调用WaitGroup的Done()来通知其完成。

我们从第 10 行生成了 1000 个增量 Goroutine。这些 Goroutines 中的每一个都是并发运行的,并且当尝试增加 x 时会出现竞争条件。当多个 Goroutine 试图同时访问 x 的值时,在第8行尝试增加 x 时发生竞争条件。

请在您本机运行此程序,因为playground是确定性的,并且竞争条件不会发生在playground上。在你的本地机器上多次运行这个程序,你可以看到每次的输出都会因为竞争条件而不同。

使用互斥锁解决竞争条件
package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup, m *sync.Mutex) {  
    m.Lock()
    x = x + 1
    m.Unlock()
    wg.Done()   
}
func main() {  
    var w sync.WaitGroup
    var m sync.Mutex
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w, &m)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

Mutexm是一种结构类型,我们在第 15 行创建了一个类型为Mutex的零值的变量m。 在上面的程序中,我们改变了increment函数,使 x = x + 1增加 x 的代码在m.Lock()和m.Unlock()之间。现在这段代码没有任何竞争条件,因为在任何时间点只允许一个 Goroutine 执行这段代码。

现在如果这个程序运行,它会输出:final value of x 1000

在第 18 行中传递互斥体的地址很重要。 如果互斥量是按值传递而不是传递地址,每个 Goroutine 都会有自己的互斥量副本,仍然会出现竞态条件。

使用channel解决竞争条件
package main  
import (  
    "fmt"
    "sync"
    )
var x  = 0  
func increment(wg *sync.WaitGroup, ch chan bool) {  
    ch <- true
    x = x + 1
    <- ch
    wg.Done()   
}
func main() {  
    var w sync.WaitGroup
    ch := make(chan bool, 1)
    for i := 0; i < 1000; i++ {
        w.Add(1)        
        go increment(&w, ch)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}

在上面的程序中,我们创建了一个缓冲 channel 并指定容量为 1,并将其传递给了18行中的 increment Goroutine。这个缓冲 channel 用于确保只有一个 Goroutine 访问增加 x 的代码的关键部分。这是通过在 x 增加之前将 true 传递给缓冲 chennel 来实现的,由于缓冲 channel 的容量为 1,所以所有的其他试图写入这个 channel 的 Goroutine 都会被阻塞。第10行直到将 x 增加之后从 channel 读取该值。这实际上只允许一个 Goroutine 访问临界区。

该程序打印:final value of x 1000

互斥与通道

我们已经使用互斥锁和通道解决了竞争条件问题。那么我们如何决定什么时候使用呢?答案在于您要解决的问题。如果您要解决的问题更适合互斥锁,那么请继续使用互斥锁。如果需要,请不要犹豫使用互斥锁。如果问题似乎更适合渠道,请使用它:)。

大多数 Go 新手尝试使用通道来解决每个并发问题,因为它是该语言的一个很酷的特性。这是错误的。该语言为我们提供了使用 Mutex 或 Channel 的选项,选择其中任何一个都没有错。

通常当 Goroutine 需要相互通信时使用通道,当只有一个 Goroutine 应该访问代码的关键部分时使用互斥锁。

对于我们上面解决的问题,我更喜欢使用互斥锁,因为这个问题不需要 goroutine 之间的任何通信。因此,互斥锁将是自然而然的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值