美文网首页
golang中的互斥锁与读写锁

golang中的互斥锁与读写锁

作者: 护念 | 来源:发表于2023-11-11 20:08 被阅读0次

    在多线程执行情况下,会存在对同一变量同时修改的情况;不能保证资源的修改后数据的一致(和我们期望)我们称为线程不安全。

    这种情况,在编程语言中我们可以通过加锁解决。

    我们先来看在不加锁的情况下,会产生的问题。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var w sync.WaitGroup
    
    type Account struct {
        name   string
        amount int
    }
    
    // 取钱
    func (a *Account) WithDraw(num int) {
        a.amount -= num
    }
    
    // 存钱
    func (a *Account) Deposit(num int) {
        a.amount += num
    }
    
    func main() {
        a := Account{
            name:   "zhangsan",
            amount: 0, // 初始化金额为0
        }
    
        w.Add(2)
        // 开一个gorounine 存钱
        go func() {
            defer w.Done()
            for i := 0; i < 1000; i++ {
                a.Deposit(100) // 每次存100
            }
        }()
    
        // 开一个gorountine 取钱
        go func() {
            defer w.Done()
            for i := 0; i < 1000; i++ {
                a.WithDraw(50) // 每次取50
            }
        }()
    
        w.Wait()              // 等待所有gorountine执行完
        fmt.Println(a.amount) // 最终金额
    }
    

    多次执行,我们会发现每次执行后打印的值都不一样,因为,并发情况下对amount的修改是非线程安全的。

    下面我们开始修复它。

    1. sync.Mutext互斥锁

    在修改amount金额时,先加锁;修改完释放锁;
    代码如下:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        lock sync.Mutex
        w    sync.WaitGroup
    )
    
    type Account struct {
        name   string
        amount int
    }
    
    // 取钱
    func (a *Account) WithDraw(num int) {
        lock.Lock() // 加锁
        a.amount -= num
        lock.Unlock() //释放锁
    }
    
    // 存钱
    func (a *Account) Deposit(num int) {
        lock.Lock() // 加锁
        a.amount += num
        lock.Unlock() //释放锁
    }
    
    func main() {
        a := Account{
            name:   "zhangsan",
            amount: 0, // 初始化金额为0
        }
    
        w.Add(2)
        // 开一个gorounine 存钱
        go func() {
            defer w.Done()
            for i := 0; i < 1000; i++ {
                a.Deposit(100) // 每次存100
            }
        }()
    
        // 开一个gorountine 取钱
        go func() {
            defer w.Done()
            for i := 0; i < 1000; i++ {
                a.WithDraw(50) // 每次取50
            }
        }()
    
        w.Wait()              // 等待所有gorountine执行完
        fmt.Println(a.amount) // 最终金额
    }
    

    现在执行代码每次输出都是50000,数据正确啦~

    2. sync.RWMutex 读写锁

    sync.Mutex可以解决问题,但是在许多场景下,都是读多写少;在这种情况下,使用读写锁更好。

    什么事读写锁呢?

    1. 在一个线程的对读加锁的情况,其它线程也可以读
    2. 只有在线程写的情况下,其它线程不能读和写

    总结下就是,只有写的时候会造成阻塞,其它时候不会阻塞;因此非常时候读多写入。

    其写法和互斥差不多,只是在读的时候,写法不同多了R

    • lock.RLock() 加读锁
    • lock.RUnlock() 释放读锁

    我们来看看代码:

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    var (
        x    = 0
        lock sync.RWMutex // 读写锁
        w    sync.WaitGroup
    )
    
    // 读
    func read() {
        lock.RLock()                     // 读上锁
        time.Sleep(5 * time.Millisecond) // 模拟耗时操作
        lock.RUnlock()                   //读释放锁
    }
    
    // 写
    func write() {
        lock.Lock() // 写加锁 (和互斥锁写法一样)
        x += 1
        time.Sleep(50 * time.Millisecond) // 模拟耗时间操作
        lock.Unlock()
    }
    
    func main() {
        w.Add(2)
        // 开一个gorounine 读(多)
        go func() {
            defer w.Done()
            for i := 0; i < 1000; i++ {
                read()
            }
        }()
    
        // 开一个gorountine 写
        go func() {
            defer w.Done()
            for i := 0; i < 10; i++ { // 写的少
                write()
            }
        }()
    
        w.Wait()       // 等待所有gorountine执行完
        fmt.Println(x) // 打印x值
    }
    

    相关文章

      网友评论

          本文标题:golang中的互斥锁与读写锁

          本文链接:https://www.haomeiwen.com/subject/zeclwdtx.html