Русский
Русский
English
Статистика
Реклама

Из песочницы А давайте в Go сделаем TryLock(context.Context)

Привет!


Вэтой статье я хотел бы расcказать, как можно было бы сделать свой RWMutex, но с возможностью по таймауту или по срабатыванию контекста пропустить блокировку. То есть реализовать TryLock(context.Context) и RTryLock(context.Context), но уже для своего Mutex.


image


На картинке изображено, как нужно наливать воду в очень узкое горлышко.


Для начала следует уточнить, что для 99% задач такие методы вообще не нужны. Нужны они становятся тогда, когда блокируемый ресурс может очень долго не отпускаться. Хочу отметить, что если блокируемый ресурс долгое время остаётся занятым, стоит в начале попробовать оптимизировать логику таким образом, что бы минимизировать время блокировки.


Подробнее об этом написано в посте Танцы с мьютексами в Go в примере 2.


Но если всё же нам приходится иметь долгое удержание одним потоком ресурсов, то как мне кажется без TryLock будет сложно обойтись.


Когда мы работаем так, что у нас нет параллельных потоков, мы будем просто менять переменную методами из пакета atomic, записывая факт блокировки или разблокировки мьютекса. Но вот если наш запрос пришел в заблокированнй мьютекс, то мы пойдём по длинному сценарию. В этом случае нам нужно будет разблокировать ожидающие потоки по завершении блокировки, и для этого, можно воспользоваться каналом. А точнее свойством канала, что если мы его закрываем, то все читающие из него потоки разблокируются.


Создадим Mutex:


// RWTMutex - Read Write and Try Mutextype RWTMutex struct {    state int32    mx    sync.Mutex    ch    chan struct{}}

state состояние mutex, мы будем работать с ним через atomic.AddInt32, atomic.LoadInt32 и atomic.CompareAndSwapInt32


ch канал, который будет разблокировать потоки.


mx мьютекс, который используется для того, что бы разблокировка потоков и создания каналов не происходили параллельно.


А теперь можно перейти к реализации:


// TryLock - try locks mutex with contextfunc (m *RWTMutex) TryLock(ctx context.Context) bool {    if atomic.CompareAndSwapInt32(&m.state, 0, -1) {        return true    }    // Slow way    return m.lockST(ctx)}// RTryLock - try read locks mutex with contextfunc (m *RWTMutex) RTryLock(ctx context.Context) bool {    k := atomic.LoadInt32(&m.state)    if k >= 0 && atomic.CompareAndSwapInt32(&m.state, k, k+1) {        return true    }    // Slow way    return m.rlockST(ctx)}

Как можно увидеть, если Мьютекс не залочен, то его можно просто заблокировать, но вот если нет, то мы перейдём к более сложной схеме.


В начале получим канал, и переходим в бесконечный цикл, если получилось залочить, выходим с успехом, а если нет, то мы начинаем ждать одного из 2х событий, или что канал разблокируется, или что разблокирует поток ctx.Done():


func (m *RWTMutex) chGet() chan struct{} {    m.mx.Lock()    if m.ch == nil {        m.ch = make(chan struct{}, 1)    }    r := m.ch    m.mx.Unlock()    return r}func (m *RWTMutex) lockST(ctx context.Context) bool {    ch := m.chGet()    for {        if atomic.CompareAndSwapInt32(&m.state, 0, -1) {            return true        }        if ctx == nil {            return false        }        select {        case <-ch:            ch = m.chGet()        case <-ctx.Done():            return false        }    }}func (m *RWTMutex) rlockST(ctx context.Context) bool {    ch := m.chGet()    var k int32    for {        k = atomic.LoadInt32(&m.state)        if k >= 0 && atomic.CompareAndSwapInt32(&m.state, k, k+1) {            return true        }        if ctx == nil {            return false        }        select {        case <-ch:            ch = m.chGet()        case <-ctx.Done():            return false        }    }}

Давайте раблокируем мьютекс.


Нам потребуется поменять состояние и при необходимости разблокировать канал.


Как я уже писал выше, если канал закрыть, то case <-ch пропустит поток выполнения дальше.


func (m *RWTMutex) chClose() {    if m.ch == nil {        return    }    var o chan struct{}    m.mx.Lock()    if m.ch != nil {        o = m.ch        m.ch = nil    }    m.mx.Unlock()    if o != nil {        close(o)    }}// Unlock - unlocks mutexfunc (m *RWTMutex) Unlock() {    if atomic.CompareAndSwapInt32(&m.state, -1, 0) {        m.chClose()        return    }    panic("RWTMutex: Unlock fail")}// RUnlock - unlocks mutexfunc (m *RWTMutex) RUnlock() {    i := atomic.AddInt32(&m.state, -1)    if i > 0 {        return    } else if i == 0 {        m.chClose()        return    }    panic("RWTMutex: RUnlock fail")}

Собственно мьютекс готов, нужно к нему написать пару тестов и стандартных методов типа Lock() и RLock()


Бенчмарки на моей машине показали вот такие скорости


Описанные выше методыBenchmarkRWTMutexTryLockUnlock-8        92154297                12.8 ns/op             0 B/op          0 allocs/opBenchmarkRWTMutexTryRLockRUnlock-8      64337136                18.4 ns/op             0 B/op          0 allocs/opСтандартный RWMutexBenchmarkRWMutexLockUnlock-8            44187962                25.8 ns/op             0 B/op          0 allocs/opBenchmarkRWMutexRLockRUnlock-8          94655520                12.6 ns/op             0 B/op          0 allocs/opСтандартный MutexBenchmarkMutexLockUnlock-8              94345815                12.7 ns/op             0 B/op          0 allocs/op

То есть скорость работы сопоставима с обычным RWMutex и Mutex.


Код на github
Источник: habr.com
К списку статей
Опубликовано: 10.11.2020 22:10:33
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Go

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru