golang并发安全及读写互斥锁的示例分析

 更新时间:2022年04月14日 13:29:09   作者:Jeff的技术栈  
这篇文章主要为大家介绍了golang并发安全及读写互斥锁的示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪

并发安全和锁

有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。类比现实生活中的例子有十字路口被各个方向的的汽车竞争;还有火车上的卫生间被车厢里的人竞争。

举个例子:

var x int64
var wg sync.WaitGroup
func add() {
    for i := 0; i < 5000; i++ {
        x = x + 1
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
} 

上面的代码中我们开启了两个goroutine去累加变量x的值,这两个goroutine在访问和修改x变量的时候就会存在数据竞争,导致最后的结果与期待的不符。

互斥锁

import (
	"fmt"
	"sync"
)
var lock sync.Mutex
lock.Lock() // 加锁
lock.Unlock() // 解锁

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。 使用互斥锁来修复上面代码的问题:

var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
    for i := 0; i < 5000; i++ {
        lock.Lock() // 加锁
        x = x + 1
        lock.Unlock() // 解锁
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
} 

使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

读写互斥锁

import (
	"fmt"
	"sync"
)
var rwlock sync.RWMutex
rwlock.Lock()   // 加写锁
rwlock.Unlock() // 解写锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

读写锁示例:

var (
    x      int64
    wg     sync.WaitGroup
    lock   sync.Mutex
    rwlock sync.RWMutex
)
func write() {
    // lock.Lock()   // 加互斥锁
    rwlock.Lock() // 加写锁
    x = x + 1
    time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
    rwlock.Unlock()                   // 解写锁
    // lock.Unlock()                     // 解互斥锁
    wg.Done()
}
func read() {
    // lock.Lock()                  // 加互斥锁
    rwlock.RLock()               // 加读锁
    time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
    rwlock.RUnlock()             // 解读锁
    // lock.Unlock()                // 解互斥锁
    wg.Done()
}
func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }
    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}

需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。

以上就是golang并发安全及读写互斥锁的示例分析的详细内容,更多关于golang并发安全及读写互斥锁的资料请关注脚本之家其它相关文章!

相关文章

  • go build和go install的区别介绍

    go build和go install的区别介绍

    这篇文章主要介绍了go build和go install的区别介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang实现EasyCache缓存库实例探究

    Golang实现EasyCache缓存库实例探究

    这篇文章主要为大家介绍了Golang实现EasyCache缓存库实例探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • goland设置颜色和字体的操作

    goland设置颜色和字体的操作

    这篇文章主要介绍了goland设置颜色和字体的操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Golang等多种语言转数组成字符串举例详解

    Golang等多种语言转数组成字符串举例详解

    今天写代码遇到数组转换成字符串操作,下面这篇文章主要给大家介绍了关于Golang等多种语言转数组成字符串的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • Go 泛型和非泛型代码详解

    Go 泛型和非泛型代码详解

    Go 在 1.17 中支持泛型,但是默认未开启;1.18 中会正式支持泛型,下面文章内容小编将给大家讲解Go 语言中的泛型和非泛型并且附上代码详解,刚兴趣的小伙伴请参考下面文章的具体内容
    2021-10-10
  • Golang中定时器的陷阱详解

    Golang中定时器的陷阱详解

    这篇文章主要给大家介绍了关于Golang中定时器陷阱的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用golang具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • Golang两行代码实现发送钉钉机器人消息

    Golang两行代码实现发送钉钉机器人消息

    创建一个钉钉机器人必须使用加签,本文通过Golang两行代码实现发送钉钉机器人消息,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • golang常用库之配置文件解析库-viper使用详解

    golang常用库之配置文件解析库-viper使用详解

    viper 配置管理解析库,是由大神 Steve Francia 开发,他在google领导着 golang 的产品开发,他也是 gohugo.io 的创始人之一,命令行解析库 cobra 开发者,这篇文章主要介绍了golang常用库之配置文件解析库-viper使用详解,需要的朋友可以参考下
    2020-10-10
  • Go设计模式之备忘录模式讲解和代码示例

    Go设计模式之备忘录模式讲解和代码示例

    备忘录是一种行为设计模式, 允许生成对象状态的快照并在以后将其还原,本文就通过代码示例给大家讲讲Go备忘录模式,感兴趣的小伙伴跟着小编一起来看看吧
    2023-08-08
  • golang如何让string转int64

    golang如何让string转int64

    这篇文章主要介绍了golang如何让string转int64问题,
    2024-02-02

最新评论