ReadWriteSerializer


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

Для этого, я разработал читать-писать сериализатор структура, чья работа заключается в том, чтобы позволить читателям, чтобы одновременно получать доступ к данным. Стоит отметить, что любой писатель будет получить монопольный контроль над данными и сдерживать любые попытки чтения, пока все ожидающие записи являются полными. Это позволит программное обеспечение, чтобы получить полные данные и избежать состояний гонки - в случаях, когда операция записи не завершена, в связи с блокировкой перед читателем пришел.

Я хочу знать, насколько эффективна эта реализация и, если какие-либо улучшения могут быть сделаны. Спасибо заранее.

Примечание: Spinlock есть примитивный замок, который используется SpinLock и SpinUnlock функции. Он используется в коде.

struct ReadWriteSerializer
{
    unsigned long onlineReaders;
    Spinlock criticalSection;

    void enterAsReader()
    {
        SpinLock(&criticalSection);
        ++(onlineReaders);
        SpinUnlock(&criticalSection);
    }

    void enterAsWriter()
    {
        SpinLock(&criticalSection);
        while(onlineReaders)
            asm volatile("nop");
    }

    void exitAsReader()
    {
        --(onlineReaders);
        __mfence // macro declared otherwhere - asm volatile("mfence");
    }

    void exitAsWriter()
    {
        SpinUnlock(&criticalSection);
    }
};

Объяснение того, как этот замок работает:

Когда программа пытается читать/писать, он будет доступ ReadWriteSerializer::enterAsReader или ReadWriteSerializer::enterAsWriter. На входе, как читателя, код будет немедленно увеличить количество одновременных читателей (onlineReaders), а затем разблокируйте сериализатор. Это означает, что другое программное обеспечение для чтения/записи может прийти и открыть сериализатор.

Но когда писатель входит, он говорит НОП :-), нет больше читателей войти, пока я не закончу свою работу. Он ждет, пока все остальные читатели покинуть замок (путем уменьшения onlineReaders). Затем он делает свою работу. На выходе, это освободит замок за другим.



Комментарии
1 ответ

Спинлоки следует проводить только на очень короткое время

Блокировки стоят дорого; они вызывают поток, который пытается установить блокировку, чтобы использовать 100% процессорного времени. Поэтому, когда у вас есть замок, вы должны как можно быстрее освободить. Когда вы звоните enterAsReader()вы не снимите блокировку. Вы показали себя, что ты делаешь как писатель, может быть, манипулирует "огромный" структуры данных, так что может занять много времени, в ходе которого читателям будет вращаться.

onlineReaders это не атомные

Вы не используете блокировку в exitAsReader() поэтому нет никаких гарантий, что пре-декремент является атомарным. Таким образом, если два потока вызывают exitAsReader() в то же время, они будут считывать текущее значение, декремента, и сохранять результат обратно. Это может привести к onlineReaders эффективно только уменьшается, а не два раза.

Если вы защитите exitAsReader() С замком, то вы, конечно, сразу же столкнулись с проблемой, потому что enterAsWriter() возьмем замок, и заблокировать любой другой поток от призыва exitAsReader(). Так наоборот, он должен выглядеть так:

void enterAsWriter() {
while(true) {
SpinLock(&criticalSection);
if(!onlineReaders)
break;
SpinUnlock(&criticalSection);
}
}

Писатель голода

После ремонта, ваш код страдает от классической проблемы: писатель голода. Чтобы решить эту проблему, вам нужно иметь две переменные; - количество читателей, которые держат замок и другой счетчик, который показывает, насколько многие авторы ждут, чтобы взять замок. Писатель шагом второй счетчик сначала, затем пытается захватить замок. Когда другие читатели пытаются взять блокировку на чтение, они должны ждать, пока счетчик писателей равна нулю.

5
ответ дан 18 марта 2018 в 02:03 Источник Поделиться