Читать, предпочитая блокировку мьютекса RW в Golang

Мне нужен мьютекс read, предпочитающий RW мьютекс в golang. Есть ли пакет в golang, который удовлетворит мои потребности. Я попробовал sync.RWMutex, но, похоже, запись предпочитает блокировку. Вот моя попытка отличить RWMutex от Go,

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

Второй читатель ждет, пока писатель не снимет блокировку.


person IhtkaS    schedule 11.04.2016    source источник


Ответы (2)


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 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)

Зная это и видя, как 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, но и во время выполнения для планирования других горутин и других блокировок. Следовательно, попытка принудительного применения шаблона может принести больше вреда, чем пользы в приложении.

Я бы посоветовал сделать шаг назад и подумать, зачем вообще нужна блокировка чтения в вашей архитектуре. Вы действительно на том уровне производительности, при котором переключение контекста ЦП замедляет работу вашего высокочастотного приложения? Я бы сказал, что есть более систематический подход, который можно было бы использовать вместо того, чтобы пытаться реализовать шаблон блокировки для чтения только потому, что он звучит круто и звучит так, как будто он решает все ваши проблемы. Каковы ваши контрольные цифры? Каков размер входных данных и сколько параллельных процессов? Нужно ли делиться? Это меньше X ГБ потребления памяти и можете ли вы переключиться на размещение вещей в стеке (например, каналы, без блокировки мьютекса)? Как насчет чтения данных в стеке и сохранения набора для записи для блокировки? Как долго сборщик мусора не очистит стек, по сравнению с тем, чтобы хранить вещи в куче? и т.д.

person eduncan911    schedule 11.04.2016

Кажется, вы можете добиться желаемого поведения, например, с помощью 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

person Uvelichitel    schedule 11.04.2016
comment
Хороший. Я не часто использую мьютексы и просто рассказал о том, как их исследовать. - person eduncan911; 11.04.2016