5

我需要在 golang 中阅读首选 RW互斥锁。golang 中是否有一个包可以满足我的需求。我试过sync.RWMutex,但它似乎是写首选锁。这是我区分 Go 的 RWMutex 的尝试,

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {

y := &resource{x: 10}

go func() {
    defer fmt.Println("done first read")
    y.RLock()
    defer y.RUnlock()
    go func() {
        defer fmt.Println("done first write")
        fmt.Println("first write req")
        y.Lock()
        fmt.Println("after first write granted")
        defer y.Unlock()
    }()
    time.Sleep(time.Second)
    go func() {
        defer fmt.Println("done second read")
        fmt.Println("second read req")
        y.RLock()
        fmt.Println("after second read granted")
        defer y.RUnlock()
    }()

    time.Sleep(10 * time.Second)
}()

time.Sleep(time.Minute)

}

type resource struct {
    sync.RWMutex
    x int
}

输出:

first write req
second read req
done first read
after first write granted
done first write
after second read granted
done second read

第二个读者一直等待直到作者释放锁。

4

2 回答 2

8

sync.RWMutex实现写优先和读优先锁定。这完全取决于您如何使用它来获得写入优先或读取优先。

以您的维基百科链接伪代码作为 Lock-For-Read 的示例(在首选阅读情况下):

* Input: mutex m, condition variable c, integer r (number of readers waiting), flag w (writer waiting).
* Lock m (blocking).
* While w:
* wait c, m[a]
* Increment r.
* Unlock m.

只要您遵循上面的 Lock-For-Reads 模式,Lock-For-Write 模式就会出现在优先读取的情况下:

* Lock m (blocking).
* While (w or r > 0):
* wait c, m
* Set w to true.
* Unlock m.

您可以在实现的过程中看到这种机制RWMutex。请记住,Go 框架只是 Go 代码 - 查看代码以了解它是如何实现的:

https://golang.org/src/sync/rwmutex.go?s=879:905#L20

29  // RLock locks rw for reading.
30  func (rw *RWMutex) RLock() {
31      if race.Enabled {
32          _ = rw.w.state
33          race.Disable()
34      }
35      if atomic.AddInt32(&rw.readerCount, 1) < 0 {
36          // A writer is pending, wait for it.
37          runtime_Semacquire(&rw.readerSem)
38      }
39      if race.Enabled {
40          race.Enable()
41          race.Acquire(unsafe.Pointer(&rw.readerSem))
42      }
43  }

需要注意的一个关键是rw.readerSem上面的代码中的代码,它为您integer r提供了维基百科示例模式,哪些语言(如 Go 和其他语言)称为信号量:

http://www.golangpatterns.info/concurrency/semaphores

真正的等待在第 37 行,用于runtime_Semaquire()

https://golang.org/src/sync/runtime.go

11  // Semacquire waits until *s > 0 and then atomically decrements it.
12  // It is intended as a simple sleep primitive for use by the synchronization
13  // library and should not be used directly.
14  func runtime_Semacquire(s *uint32)

知道这一点,并查看 a 如何RWMutex.RLock()增加读取该数字,您可以相应地重构您的代码。

看看如何RWMutex.RUnlock减少,但最重要的是如何RWMutex.Lock()迫使所有活跃的读者等待:

71  // Lock locks rw for writing.
72  // If the lock is already locked for reading or writing,
73  // Lock blocks until the lock is available.
74  // To ensure that the lock eventually becomes available,
75  // a blocked Lock call excludes new readers from acquiring
76  // the lock.
77  func (rw *RWMutex) Lock() {
78      if race.Enabled {
79          _ = rw.w.state
80          race.Disable()
81      }
82      // First, resolve competition with other writers.
83      rw.w.Lock()
84      // Announce to readers there is a pending writer.
85      r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
86      // Wait for active readers.
87      if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
88          runtime_Semacquire(&rw.writerSem)
89      }
90      if race.Enabled {
91          race.Enable()
92          race.Acquire(unsafe.Pointer(&rw.readerSem))
93          race.Acquire(unsafe.Pointer(&rw.writerSem))
94      }
95  }

这很可能是您看到第二位读者在等待的原因。

请记住,信号量不仅在RWMutex您创建的实例之间共享,而且在整个运行时共享以围绕其他 goroutine 和其他锁进行调度。因此,为什么在应用程序中尝试强制模式可能弊大于利。

我的建议是退后一步,考虑一下为什么要在架构中使用读优先锁定。您是否真的处于 CPU 上下文切换会减慢您的高频应用程序的性能水平?我想说的是可以采取一种更系统的方法,而不是仅仅因为它听起来很酷并且听起来可以解决您所有的问题而尝试实现“读取首选锁定”模式。你的基准数字是多少?输入数据的大小是多少,以及跨越多少个并发进程?一定要分享吗?它是否在 X GB 的内存消耗下,你可以切换到将东西放在堆栈上(例如通道,没有互斥锁)?堆栈上的读取数据并保持写入集以进行锁定呢?GC 清理堆栈与将东西保留在堆上需要多长时间?等等等等。

于 2016-04-11T17:59:54.063 回答
2

例如,您似乎可以使用sync.WaitGroup 同步原语实现所需的行为

var wg sync.WaitGroup
go func() {
            defer fmt.Println("done second read")
            fmt.Println("second read req")
            y.RLock()   //wait writer
            wg.Add(1)   //report busy
            fmt.Println("after second read granted")
            defer wg.Done() //report done
            defer y.RUnlock()
        }()
//forcing writer to wait all readers
go func() {
            defer fmt.Println("done first write")
            fmt.Println("first write req")
            wg.Wait()  //wait all readers
            y.Lock()
            fmt.Println("after first write granted")
            defer y.Unlock()
        }()

你可以试试https://play.golang.org/p/y831xIrglj

于 2016-04-11T19:38:19.220 回答