程序问答   发布时间:2022-06-02  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了如何使用 RWMutex?大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

如何解决如何使用 RWMutex??@H_607_1@ 开发过程中遇到如何使用 RWMutex?的问题如何解决?下面主要结合日常开发的经验,给出你关于如何使用 RWMutex?的解决方法建议,希望对你解决如何使用 RWMutex?有所启发或帮助;

当多个线程需要改变同一个值时,就需要一种锁定机制来同步访问。@R_772_9086@,两个或多个线程可能会同时写入相同的值,从而导致内存损坏,通常会导致崩溃。

该原子包提供了一个快速简便的方法来同步访问的原始值。对于计数器来说,它是最快的同步方法。它具有定义明确的用例的方法,例如递增、递减、交换等。

的同步包提供了一种同步访问更复杂的值,诸如地图,切片,阵列或组的值。您可以将此用于未在atomic 中定义的用例。

在任何一种情况下,只有在写入时才需要锁定。多个线程*可以在没有锁定机制的情况下安全地读取相同的值。

让我们看看您提供的代码。

type Stat struct {
    counters     map[String]*int64
    countersLock sync.RWMutex
    averages     map[String]*int64
    averagesLock sync.RWMutex
}

func (s *Stat) Count(name String) {
    s.countersLock.RLock()
    counter := s.counters[name]
    s.countersLock.RUnlock()
    if counter != nil {
        atomic.AddInt64(counter, int64(1))
        return
    }
}

这里缺少的是地图本身是如何初始化的。到目前为止,这些地图还没有发生变异。如果计数器名称是预先确定的并且以后无法添加,则不需要RWMutex。该代码可能如下所示:

type Stat struct {
    counters map[String]*int64
}

func InitStat(names... String) Stat {
    counters := make(map[String]*int64)
    for _, name := range names {
        counter := int64(0)
        counters[name] = &counter
    }
    return Stat{Counters}
}

func (s *Stat) Count(name String) int64 {
    counter := s.counters[name]
    if counter == nil {
        return -1 // (int64, error) instead?
    }
    return atomic.AddInt64(counter, 1)
}

(注意:我删除了平均值,因为它没有在原始示例中使用。)

现在,假设您不希望您的计数器被预先确定。在这种情况下,您需要一个互斥锁来同步访问。

让我们只用一个Mutex试试吧。这很简单,因为一次只有一个线程可以持有Lock。如果第二个线程试图锁定第一个版本之前,他们与解锁,它等待(或块)**到那个时候。

type Stat struct {
    counters map[String]*int64
    mutex    sync.Mutex
}

func InitStat() Stat {
    return Stat{Counters: make(map[String]*int64)}
}

func (s *Stat) Count(name String) int64 {
    s.mutex.Lock()
    counter := s.counters[name]
    if counter == nil {
        value := int64(0)
        counter = &value
        s.counters[name] = counter
    }
    s.mutex.Unlock()
    return atomic.AddInt64(counter, 1)
}

上面的代码可以正常工作。但是有两个问题。

  1. 如果 Lock() 和 Unlock() 之间出现恐慌,互斥将被永远锁定,即使您要从恐慌中恢复。这段代码可能不会恐慌,但一般来说,假设它可能会更好。
  2. 获取计数器时获取排他锁。一次只有一个线程*可以从计数器中读取。

问题#1 很容易解决。使用延迟:

func (s *Stat) Count(name String) int64 {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    counter := s.counters[name]
    if counter == nil {
        value := int64(0)
        counter = &value
        s.counters[name] = counter
    }
    return atomic.AddInt64(counter, 1)
}

这可确保始终调用 Unlock()。如果由于某种原因你有不止一个返回,你只需要在函数的开头指定 Unlock() 一次。

问题#2 可以用RWMutex解决。它究竟是如何工作的,为什么有用?

RWMutex是Mutex的扩展,增加了两个方法:RLock和RUnlock。关于RWMutex有几点需要注意

  • RLock是共享读锁。当一个锁被它拿走时,其他线程也可以用RLock拿走他们自己的锁。这意味着多个线程可以同时读取。它是半排他性的。
  • 如果互斥锁被读取锁定,则对Lock的调用将被阻止**。如果一个或多个读者持有一个锁,你就不能写
  • 如果互斥锁被写锁定(使用Lock),RLock将阻塞**

一个很好的思方式是RWMutex是一个带有读取器计数器的互斥锁。RLock增加计数器,而RUnlock减少它。只要该计数器 > 0,对Lock的调用就会阻塞。

您可能会想:如果我的应用程序被大量读取,是否意味着写入器可能会被无限期阻塞?不。RWMutex还有一个有用的属性:

  • 如果读者计数器 > 0 并且Lock被调用,以后对RLock 的调用也将阻塞,直到现有读者释放他们的锁,作者获得了他的锁并稍后释放它。

把它想象成杂货店收银台上方的灯,上面显示收银员是否营业。排队的人可以留在那里,他们会得到帮助,但新人不能排队。一旦最后剩下的顾客得到帮助,收银员就会休息,并且该收银机要么保持关闭,直到他们回来,要么被另一个收银员取代。

让我们用RWMutex修改前面的例子:

type Stat struct {
    counters map[String]*int64
    mutex    sync.RWMutex
}

func InitStat() Stat {
    return Stat{Counters: make(map[String]*int64)}
}

func (s *Stat) Count(name String) int64 {
    var counter *int64
    if counter = getCounter(Name); counter == nil {
        counter = initCounter(Name);
    }
    return atomic.AddInt64(counter, 1)
}

func (s *Stat) getCounter(name String) *int64 {
    s.mutex.RLock()
    defer s.mutex.RUnlock()
    return s.counters[name]
}

func (s *Stat) initCounter(name String) *int64 {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    counter := s.counters[name]
    if counter == nil {
        value := int64(0)
        counter = &value
        s.counters[name] = counter    
    }
    return counter
}

使用上面的代码,我将逻辑分离为getCounterinitCounter函数:

  • 保持代码简单易懂。在同一个函数中使用 RLock() 和 Lock() 会很困难。
  • 使用 defer 时尽可能早地释放锁。

与Mutex示例不同,上面的代码允许您同时增加不同的计数器。

我想指出的另一件事是在上面的所有示例中,映射@H_449_85@map[String]*int64包含指向计数器的指针,而不是计数器本身。如果要将计数器存储在地图中@H_449_85@map[String]int64,则需要使用不带atomic 的Mutex。该代码如下所示:

type Stat struct {
    counters map[String]int64
    mutex    sync.Mutex
}

func InitStat() Stat {
    return Stat{Counters: make(map[String]int64)}
}

func (s *Stat) Count(name String) int64 {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    s.counters[name]++
    return s.counters[name]
}

您可能希望这样做以减少垃圾收集 - 但这仅在您有数千个计数器时才重要 - 即便如此,计数器本身也不会占用大量空间(与字节缓冲区之类的东西相比)。

*当我说线程时,我的意思是 go-routIne。其他语言中的线程是一种同时运行一组或多组代码的机制。创建和拆除线程的成本很高。go-routIne 是建立在线程之上的,但会重用它们。当一个 go-routIne 休眠时,底层线程可以被另一个 go-routIne 使用。当一个 go-routIne 醒来时,它可能在不同的线程上。Go 在幕后处理所有这些。– 但出于所有意图和目的,当涉及到内存访问时,您会将 go-routIne 视为线程。但是,在使用 go-routInes 时不必像使用线程那样保守。

**当 go-routIne 被LockRLock、通道或Sleep阻塞时,底层线程可能会被重用。该 go-routIne 不使用 cpu - 将其视为排队等候。像其他语言一样,无限循环for {}会阻塞,同时保持 cpu 和 go-routIne 忙碌

解决方法@H_607_1@
type Stat struct {
    counters     map[String]*int64
    countersLock sync.RWMutex
    averages     map[String]*int64
    averagesLock sync.RWMutex
}

它在下面被称为

func (s *Stat) Count(name String) {
    s.countersLock.RLock()
    counter := s.counters[name]
    s.countersLock.RUnlock()
    if counter != nil {
        atomic.AddInt64(counter,int64(1))
        return
    }
}

我的理解是,我们首先锁定接收者 s(这是一个 Stat 类型),然后如果计数器确实存在,我们就添加它。

问题:

Q1:为什么需要加锁?RWMutex甚至是什么意思?

Q2:s.countersLock.RLock()- 这会锁定整个接收者还是仅锁定 Stat 类型的 counters 字段?

Q3:s.countersLock.RLock()- 这会锁定平均值字段吗?

Q4:为什么要使用RWMutex?我认为通道是在 Golang 中处理并发的首选方式?

Q5:这是什么atomic.AddInt64。为什么在这种情况下我们需要原子?

Q6:为什么我们要在添加之前立即解锁?

大佬总结

以上是大佬教程为你收集整理的如何使用 RWMutex?全部内容,希望文章能够帮你解决如何使用 RWMutex?所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。