Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 05.04.10 12:19
Оценка:
Имеется несколько процессов, общие настройки между ними лежат POD-структуре в MMF. Один из процессов может читать и менять настройки, остальные — только читать.

Чтобы обойтись без лишних блокировок, и исходя из того, что настройки меняются крайне редко, в MMF также есть номер текущей версии настроек чтение и запись настроек проиходит следующим образом:

// Запись
  Settings new_settings = {/*...*/};
  InterlockedIncrement(&shared_data->version);
  memcpy( &shared_data->settings, &new_settings , sizeof(Settings));
  InterlockedIncrement(&shared_data->version);


// Чтение
  LONG before, after;
  Settings local_settings;
ReadSerrings:
  before = InterlockedAdd(&shared_data->version, 0);
  memcpy(&local_settings, &shared_data->settings, sizeof(Settings));
  after = InterlockedAdd(&shared_data->version, 0);
  if (before != after) {
    SwitchToThread();
    goto ReadSettings;
  }


Вопросы:
1. Это вообще работоспособно?
2. Как-то можно сделать, чтобы SwitchToThread с большей вероятностью делал что нужно? (т.е. активировал сохраняющий настройки процесс)
3. Что ещё можете посоветовать?
Русский военный корабль идёт ко дну!
Re: Межпроцессное разделение данных без блокировки
От: rus blood Россия  
Дата: 05.04.10 12:59
Оценка: 10 (1)
Здравствуйте, Alexander G, Вы писали:

AG>Вопросы:

AG>1. Это вообще работоспособно?

В таком виде нет.
Reader может прочитать оба значения "before" и "after" между инкрементами, которые делает writer.
В этом случае прочитанные значения будут равны, и reader может получить "битые" данные.

AG>2. Как-то можно сделать, чтобы SwitchToThread с большей вероятностью делал что нужно? (т.е. активировал сохраняющий настройки процесс)


Нет.

AG>3. Что ещё можете посоветовать?


Можно в MMF хранить две структуры, и флаг, который показывает, какая из структур активная.
Reader-ы читают из текущей активной структуры.
Writer пишет в неактивную структуру, и меняет флаг.

Надо обеспечить, чтобы все reader-ы закончили читать из структуры, которую собирается обновлять writer.
Если это происходит достаточно редко, можно на это забить.
В противном случае, жестко можно решить interlocked-счетчиком reader-ов.
Имею скафандр — готов путешествовать!
Re[2]: Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 05.04.10 14:44
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Здравствуйте, Alexander G, Вы писали:


AG>>Вопросы:

AG>>1. Это вообще работоспособно?

RB>В таком виде нет.

RB>Reader может прочитать оба значения "before" и "after" между инкрементами, которые делает writer.
RB>В этом случае прочитанные значения будут равны, и reader может получить "битые" данные.

Да, что-то я совсем туплю

AG>>2. Как-то можно сделать, чтобы SwitchToThread с большей вероятностью делал что нужно? (т.е. активировал сохраняющий настройки процесс)


RB>Нет.


AG>>3. Что ещё можете посоветовать?


RB>Можно в MMF хранить две структуры, и флаг, который показывает, какая из структур активная.

RB>Reader-ы читают из текущей активной структуры.
RB>Writer пишет в неактивную структуру, и меняет флаг.

RB>Надо обеспечить, чтобы все reader-ы закончили читать из структуры, которую собирается обновлять writer.

RB>Если это происходит достаточно редко, можно на это забить.
RB>В противном случае, жестко можно решить interlocked-счетчиком reader-ов.

Да, с intelocked счётчиком читателей мне нравится тем, что ждать будет писатель.
Русский военный корабль идёт ко дну!
Re: Межпроцессное разделение данных без блокировки
От: ononim  
Дата: 05.04.10 15:12
Оценка: -1
ыть:
// Запись
  Settings new_settings = {/*...*/};
  for (;;)
  {//ждемс другого писателя если надо
  LONG new_before = InterlockedIncrement(&shared_data->version_before);
  if (new_before == (shared_data->version_after+1)) break;
  InterlockedDecrement(&shared_data->version_before);
  SwitchToThread();
  }
  memcpy( &shared_data->settings, &new_settings , sizeof(Settings));
  InterlockedIncrement(&shared_data->version_after);


// Чтение
  Settings local_settings;
  ReadSerrings:
  for(;;)
  {
   LONG after = InterlockedCompareExchange(&shared_data->version_after, 0, 0);
   memcpy(&local_settings, &shared_data->settings, sizeof(Settings));
   if (InterlockedCompareExchange(&shared_data->version_before, after, after)==after) break;
   SwitchToThread();
  }
Как много веселых ребят, и все делают велосипед...
Re[3]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 15:48
Оценка: -1
Здравствуйте, Alexander G, Вы писали:

AG>Да, с intelocked счётчиком читателей мне нравится тем, что ждать будет писатель.


А пока писатель будет ждать, придёт новый читатель, и ещё один, и ещё. По любому читателей придётся на входе тормазить. А учитывая, что обычно всё-таки важно побыстрее применить новые настройки, то логичнее всё-таки, на мой взгляд, тормозить читателей. Тем более, если изменение настроек происходят редко.

Как вариант на растерзание

// Запись
  Settings new_settings = {/*...*/};
 
  InterlockedIncrement(&shared_data->version);
  memcpy( &shared_data->settings, &new_settings , sizeof(Settings));
  InterlockedIncrement(&shared_data->version2);


// Чтение
  LONG before, after;
  Settings local_settings;

  for(;;)
  {
      before = shared_data->version;
      memcpy(&local_settings, &shared_data->settings, sizeof(Settings));
      after = shared_data->version;
     if (before == after) break;
     for(;;)
     {
         if (after == &shared_data->version2) break;
         SwitchToThread();
     }
  }
"Нормальные герои всегда идут в обход!"
Re[4]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 15:56
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR> if (after == &shared_data->version2) break;


Амперсанд от copy-paste остался


2. Как-то можно сделать, чтобы SwitchToThread с большей вероятностью делал что нужно? (т.е. активировал сохраняющий настройки процесс)


По-моему только заведомо высоким приоритетом писателя.
"Нормальные герои всегда идут в обход!"
Re[4]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 16:03
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

Пожалуй, точнее так

// Чтение
  LONG before, after;
  Settings local_settings;

  for(;;)
  {
      before = shared_data->version;
      memcpy(&local_settings, &shared_data->settings, sizeof(Settings));
      after = shared_data->version;
     if (before == after) break;
     for(;;)
     {
         if (shared_data->version == shared_data->version2) break;
         SwitchToThread();
     }
  }
"Нормальные герои всегда идут в обход!"
Re[5]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 16:08
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

Вот только от падения писателя оно не застраховано, и это придётся разруливать таймаутом хотя-бы.
"Нормальные герои всегда идут в обход!"
Re[4]: Межпроцессное разделение данных без блокировки
От: GlebZ Россия  
Дата: 05.04.10 16:15
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

А memcpy — атомарно? Что будет если переключение потока произойдет когда происходит memcpy от писателя?
Re[5]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 16:22
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>А memcpy — атомарно?

Нет конечно.

GZ>Что будет если переключение потока произойдет когда происходит memcpy от писателя?


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

Version до начала чтения и по его окончании будет разная. Нарвавшись на это мы ждём, когда писатель закончит запись и уравняет Version и Version2, после чего начинаем заново.
"Нормальные герои всегда идут в обход!"
Re[6]: Межпроцессное разделение данных без блокировки
От: GlebZ Россия  
Дата: 05.04.10 16:31
Оценка: 10 (1)
Здравствуйте, Jolly Roger, Вы писали:

JR>Version до начала чтения и по его окончании будет разная. Нарвавшись на это мы ждём, когда писатель закончит запись и уравняет Version и Version2, после чего начинаем заново.

Или я что-то путаю, или это из кода это не явствует.


// Запись
  Settings new_settings = {/*...*/};
 
  InterlockedIncrement(&shared_data->version);
  memcpy( &shared_data->settings, &new_settings , sizeof(Settings)); //в процессе произошла переключение к читателю


// Чтение
//сюда переключили
      before = shared_data->version;//читаем уже новую версию
      memcpy(&local_settings, &shared_data->settings, sizeof(Settings));//получаем полубитые данные
      after = shared_data->version; //читаем ту же версию (новую)
     if (before == after) break; //а причем тут version 2
Re: Межпроцессное разделение данных без блокировки
От: Pzz Россия https://github.com/alexpevzner
Дата: 05.04.10 16:48
Оценка: 9 (1) -1
Здравствуйте, Alexander G, Вы писали:

AG>Имеется несколько процессов, общие настройки между ними лежат POD-структуре в MMF. Один из процессов может читать и менять настройки, остальные — только читать.


AG>Чтобы обойтись без лишних блокировок, и исходя из того, что настройки меняются крайне редко, в MMF также есть номер текущей версии настроек чтение и запись настроек проиходит следующим образом:


Если настройки меняются крайне редко, то использование read/write lock'ов даст вам ничуть не худшее решение этой проблемы, и при том простое и понятное.

Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.
Re[7]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 16:55
Оценка:
Здравствуйте, GlebZ, Вы писали:

Да нет, это, похоже, я туплю Вероятно, надо так
after = shared_data->version2;

Но и это надо ещё обдумать.
"Нормальные герои всегда идут в обход!"
Re[8]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 05.04.10 16:58
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

Хотя нет, и так не пройдёт, memcpy читателя и писателя могут переплестись несколько раз. Всё, хватит на сегодня
"Нормальные герои всегда идут в обход!"
Re: Межпроцессное разделение данных без блокировки
От: GlebZ Россия  
Дата: 05.04.10 17:32
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Имеется несколько процессов, общие настройки между ними лежат POD-структуре в MMF. Один из процессов может читать и менять настройки, остальные — только читать.

Тут есть еще одна подводная скала(или грабли). Как то нас шибануло. Когда процесс меняет настройки, то он должен это делать на основе прочитанных данных. Следовательно, между чтением и записью одного такого процесса не должно быть никаких записей другого процесса.
Re: Межпроцессное разделение данных без блокировки
От: x64 Россия http://x64blog.name
Дата: 05.04.10 18:27
Оценка: 7 (2) +1
Господа начинающие системщики!
Посмотрите на этот пример, он весьма наглядно демонстрирует не-системный подход к задаче, равно как и недостаток знаний. Давайте посмотрим, что хочет автор этого топика:

  • Один писатель, много читателей.
  • Целостность данных, полученных каждым читателем.

    Во-первых, писатель у нас один, это уже большой плюс, т.к. не придётся их синхронизировать. Но есть ещё проблема с читателями. Хорошо, смотрим далее. На слове "целостность" мы уже должны насторожиться. Лучше всего в этот момент будет вспомнить понятие "транзакция", которая есть суть следующее:

    Операция, которая выполняется целиком и успешно, или не выполняется совсем.


    Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами. Это всё на интерлокерах можно сделать, никто никого не ждёт. Читателям только смотреть нужно будет, в какой из буфер писали последний раз, флажок там какой-нибудь. Вот и всё. Похожий принцип и в графике, например, применяется, называется swap buffers.

    Да, если писателей будет больше одного, то достаточно сделать по два буфера и по одному флажку на каждого из них, а принцип тот же.

    P.S.
    Сорри за многа букаф, просто показалось, что тут нужно расписать чуть подробнее.
  • JID: x64j@jabber.ru
    Re[2]: Межпроцессное разделение данных без блокировки
    От: Alexander G Украина  
    Дата: 05.04.10 19:06
    Оценка:
    Здравствуйте, x64, Вы писали:

    x64>Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами.


    Меняет, я так понимаю, индексы буферов, т.к. сами буфера одной операцией не поменяешь?
    Допустим, писатеь только что записал буфер, в момент, когда читеталь читал другой буфер. Теперь, читатель так и не получил управления, а писатель снова что-то записал, в другой буфер, т.е. в читаемый, и данные не целостны?
    Русский военный корабль идёт ко дну!
    Re[2]: Межпроцессное разделение данных без блокировки
    От: Alexander G Украина  
    Дата: 05.04.10 19:07
    Оценка:
    Здравствуйте, Pzz, Вы писали:

    Pzz>Если настройки меняются крайне редко, то использование read/write lock'ов даст вам ничуть не худшее решение этой проблемы, и при том простое и понятное.


    Pzz>Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.


    Вистовский подошел бы, если бы там были потоки, а не процессы.
    Русский военный корабль идёт ко дну!
    Re[3]: Межпроцессное разделение данных без блокировки
    От: Pzz Россия https://github.com/alexpevzner
    Дата: 05.04.10 19:12
    Оценка:
    Здравствуйте, Alexander G, Вы писали:

    Pzz>>Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.


    AG>Вистовский подошел бы, если бы там были потоки, а не процессы.


    Да, правда.

    Но как бы то ни было, сделать rwlock из подручного хлама несложно. В т.ч. из того, что есть в любой разумной версии венды.
    Re[3]: Межпроцессное разделение данных без блокировки
    От: Andrew S Россия http://alchemy-lab.com
    Дата: 05.04.10 20:57
    Оценка:
    x64>>Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами.

    AG>Меняет, я так понимаю, индексы буферов, т.к. сами буфера одной операцией не поменяешь?

    AG>Допустим, писатеь только что записал буфер, в момент, когда читеталь читал другой буфер. Теперь, читатель так и не получил управления, а писатель снова что-то записал, в другой буфер, т.е. в читаемый, и данные не целостны?

    Да, в таком виде это не работает. Налицо не системный подход

    По существу вопроса.
    Рекомендую поискать по словам lock-free. Практически все из того, что будет найдено, при некотором дотачивании напильником (а иногда и без этой процедуры) вам подойдет.

    Например, вот:
    http://rsdn.ru/forum/cpp/1800649.aspx
    Автор: remark
    Дата: 23.03.06
    http://www.rusyaz.ru/pr — стараемся писАть по-русски
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.