zoukankan      html  css  js  c++  java
  • Go语言基础之并发安全和锁

    Go语言基础之并发安全和锁

    有时候在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) // 6164
    }
    

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

    image-20211107111035964

    互斥锁

    互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个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) // 10000
    }
    

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

    读写互斥锁

    互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在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))
    }
    

    互斥锁消耗时间:15.8940874s

    读写锁消耗时间:171.33ms

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

  • 相关阅读:
    Android中实现定时器的三种方法 分类: Android 2015-07-14 18:04 11人阅读 评论(0) 收藏
    java构造器内部多态方法
    java继承方法覆盖
    java对象实例化 静态块,对象块,构造函数执行顺序
    Linux 的系统运行级别
    Jmeter启动jmeter-server.bat 报java.io.FileNotFoundException:rmi_keystore.jks 解决方法
    jmeter中JSON Extractors使用
    CentOS6.5下安装jenkins
    day12接口自动化测试框架
    day10 python接口开发、mock接口、网络编程
  • 原文地址:https://www.cnblogs.com/randysun/p/15520063.html
Copyright © 2011-2022 走看看