golang中的互斥锁与读写锁

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

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

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

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值
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容