Как отслеживать изменения с помощью Condvar и Mutex

У меня есть общий Vec<CacheChange>. Всякий раз, когда пишется новый CacheChange, я хочу разбудить читателей. Я помню, что Condvar хорош для сигнализации о том, что предикат/ситуация готова, а именно, когда Vec изменен.

Поэтому я потратил некоторое время на создание абстракции Monitor, чтобы владеть Vec и обеспечивать семантику wait и lock.

Теперь проблема в том, что я не знаю, когда сбросить Condvar. Каков хороший способ дать читателям разумное количество времени, чтобы нажать на предикат и проложить себе путь к удержанию блокировки? перед закрытием кондвара? Я неправильно подхожу к Condvars?

Это код Rust, но это больше вопрос основ для точного одновременного доступа/уведомления между несколькими читателями.

pub struct Monitor<T>(
    sync::Arc<MonitorInner<T>>
);

struct MonitorInner<T> {
    data: sync::Mutex<T>,
    predicate: (sync::Mutex<bool>, sync::Condvar)
}

impl<T> Monitor<T> {   
    pub fn wait(&self) -> Result<(),sync::PoisonError<sync::MutexGuard<bool>>> {
        let mut open = try!(self.0.predicate.0.lock());
        while !*open {
            open = try!(self.0.predicate.1.wait(open));
        }
        Ok(())
    }

    pub fn lock(&self) -> Result<sync::MutexGuard<T>, sync::PoisonError<sync::MutexGuard<T>>> {
        self.0.data.lock()
    }

    pub fn reset(&mut self) -> Result<(),sync::PoisonError<sync::MutexGuard<bool>>> {
        let mut open = try!(self.0.predicate.0.lock());
        *open = false;
        Ok(())
    }

    pub fn wakeup_all(&mut self) -> Result<(),sync::PoisonError<sync::MutexGuard<bool>>>  {
        let mut open = try!(self.0.predicate.0.lock());
        *open = true;
        self.0.predicate.1.notify_all();
        Ok(())
    }
}

После первого пробуждения мои читатели могут пропустить чтение. Вероятно, потому что они все еще удерживают блокировку данных, пока предикат снова переключается. Я видел это в своем тестовом коде только с одним читателем и одним писателем.

Затем возникает сложность, когда сбрасывать Monitor, в идеале он должен быть заблокирован после того, как все читатели имели возможность просмотреть данные. Это может вызвать взаимоблокировку, если читатель будет игнорировать свои мониторы (нет гарантии, что они будут обслуживать каждый вызов пробуждения).

Нужно ли мне использовать какую-то систему отслеживания чтения с тайм-аутами и отслеживать, когда приходят новые данные, в то время как чтение монитора все еще обслуживается? Существует ли существующая парадигма, о которой мне следует знать?


person xrl    schedule 26.10.2016    source источник


Ответы (1)


Самое простое решение — использовать счетчик вместо логического значения.

struct MonitorInner<T> {
    data: sync::Mutex<T>,
    signal: sync::Condvar,
    counter: sync::AtomicUsize,
}

Затем каждый раз, когда выполняется обновление, счетчик увеличивается. Он никогда не сбрасывается, поэтому не возникает вопроса о том, когда сбрасывать.

Конечно, это означает, что читатели должны помнить значение счетчика, когда они в последний раз просыпались.

person Matthieu M.    schedule 26.10.2016
comment
Он никогда не сбрасывается, ну, каждые 2^32 или 2^64 инкремента. АКА выпуск time_t 2038 года ^_^ - person Shepmaster; 26.10.2016
comment
@Shepmaster: Да, я бы хотел, чтобы AtomicU64 был стабильным, поскольку 64-битный счетчик не может физически переполниться из-за 1-приращения (для склонных к математике требуется ~ 600 лет, чтобы переполнить его с 1 инкрементом / нс, или ~ 120 лет). на частоте 5 ГГц). - person Matthieu M.; 26.10.2016
comment
Я мог бы поместить атомарную загрузку в wait(), и поток проснется, когда число перезагрузок будет выше. - person xrl; 26.10.2016