Простая блокировка чтения и записи

#c #concurrency #locking #readwritelock

#c #параллелизм #блокировка #readwritelock

Вопрос:

Я нахожу, что многие реализации блокировки вращения чтения записи через Интернет излишне сложны. Я написал простую блокировку чтения и записи на c .

Кто-нибудь может мне сказать, не упускаю ли я чего-нибудь?

 int r = 0;
int w = 0;

read_lock(void) 
{
     atomic_inc(r); //increment value atomically
     while( w != 0);    
}

read_unlock(void)
{
   atomic_dec(r); // Decrement value atomically
}
write_lock(void)
{
 while( (r != 0) amp;amp; 
            ( w != 0))
  atomic_inc(w); //increment value atomically
}

write_unlock(void)
{
    atomic_dec(w); //Decrement value atomically
}
  

Использование будет таким, как показано ниже.

 read_lock()
// Critical Section
read_unlock();

write_lock()
// Critical Section
write_unlock();
  

Редактировать:

Спасибо за ответы. Теперь я изменил ответ на атомарный эквивалент

Комментарии:

1. Это недопустимый C , и, похоже, предполагается, что такие вещи, как w , являются атомарными.

2. У вас нет синхронизации для ваших счетчиков. Это не потокобезопасно.

3. Продолжайте работать deadlockempire.github.io/#menu

4. Спасибо за ссылку . Я пройду по ссылке

5. В настоящее время это недопустимый C (не имеет возвращаемого типа в функциях). Но, что еще хуже, это не идиоматический C . Это вызовы функций C, которые ДОЛЖНЫ выполняться парами, чтобы работать. Для этого есть шаблон. Также этот код не является безопасным для исключений. В дополнение к сомнительным и неопределенным atomic_*() функциям, похоже, ошибка в write_lock()

Ответ №1:

Если потоки обращаются к r и w одновременно, у них возникает гонка данных. Если в программе C происходит скачок данных, поведение программы не определено.

int стандарт C не гарантирует, что он будет атомарным. Даже если мы предположим, что система, в которой доступ к int является атомарным, operator вероятно, не будет атомарной операцией даже в таких системах. Таким образом, одновременные приращения могут «исчезнуть».

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


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

Комментарии:

1. Спасибо eerorika за ответы. Вы правы, это реализация для spin lock. Я отредактировал сообщение. Блокировки вращения могут быть полезны в операционных системах, например, в контексте прерываний, когда человек не может спать.

2. Если мы используем систему, в которой все манипуляции со словами являются атомарными, то r , w могут быть атомарными, верно?

3. в @sysinit x86 есть инструкции по атомарному приращению, поэтому приращение может быть атомарным. Но он также имеет неатомное приращение, что более эффективно. Нет причин, по которым компилятор предпочел бы использовать атомарную операцию с int .

Ответ №2:

Простейшей реализацией было бы использование единственного целого значения. -1 показывает текущее состояние записи, 0 означает, что оно не считывается, а положительное значение указывает, что оно считывается таким-то количеством потоков.

Используйте atomic_int и compare_exchange_weak (или должно быть достаточно сильного, но слабого)

 std::atomic_int l=0;

void write_lock() {
    int v = 0;
    while( !l.compare_exchange_weak( v, -1 ) ) 
       v = 0; // it will set it to what it currently held
}

void write_unlock() {
    l = 0; // no need to compare_exchange
}

void read_lock() {
    int v = l.load();
    while( v < 0 || !l.compare_exchange_weak(v, v 1) )
       v = l.load();
}

void read_unlock() {
    --l; // no need to do anything else
}
  

Я думаю, что это должно сработать и иметь объекты RAII, т. Е. Создать автоматический объект, который блокируется при построении и разблокируется при уничтожении для каждого типа.

это можно было бы сделать следующим образом:

 class AtomicWriteSpinScopedLock
{
   private:
      atomic_intamp; l_;

   public:
      // handle copy/assign/move issues

      explicit AtomicWriteSpinScopedLock( atomic_intamp; l ) :
         l_(l)
       {
           int v = 0;   
           while( !l.compare_exchange_weak( v, -1 ) ) 
             v = 0; // it will set it to what it currently held
      }

      ~AtomicWriteSpinScopedLock()
       {
           l_ = 0;
       }
 };
       
class AtomicReadSpinScopedLock
{
   private:
      atomic_intamp; l_;

   public:
      // handle copy/assign/move issues

      explicit AtomicReadSpinScopedLock( atomic_intamp; l ) :
         l_(l)
       {
          int v = l.load();
          while( v < 0 || !l.compare_exchange_weak(v, v 1) )
               v = l.load();          }
       }

      ~AtomicReadSpinScopedLock()
       {
           --l_;
       }
 };
  

При блокировке для записи значение должно быть 0, и вы должны поменять его на -1, так что просто продолжайте пытаться это делать.

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

compare_exchange_weak устанавливает в первый параметр, что он фактически содержал, если обмен не удался, а второй параметр — это то, на что вы пытаетесь его изменить. Он возвращает true, если он поменялся местами, и false, если этого не произошло.

Насколько эффективно? Это спин-блокировка. Во время ожидания он будет использовать циклы процессора, поэтому лучше, чтобы он был доступен очень скоро: обновление или чтение данных должно быть быстрым.