Имеется несколько процессов, общие настройки между ними лежат 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: Межпроцессное разделение данных без блокировки
Здравствуйте, Alexander G, Вы писали:
AG>Вопросы: AG>1. Это вообще работоспособно?
В таком виде нет.
Reader может прочитать оба значения "before" и "after" между инкрементами, которые делает writer.
В этом случае прочитанные значения будут равны, и reader может получить "битые" данные.
AG>2. Как-то можно сделать, чтобы SwitchToThread с большей вероятностью делал что нужно? (т.е. активировал сохраняющий настройки процесс)
Нет.
AG>3. Что ещё можете посоветовать?
Можно в MMF хранить две структуры, и флаг, который показывает, какая из структур активная.
Reader-ы читают из текущей активной структуры.
Writer пишет в неактивную структуру, и меняет флаг.
Надо обеспечить, чтобы все reader-ы закончили читать из структуры, которую собирается обновлять writer.
Если это происходит достаточно редко, можно на это забить.
В противном случае, жестко можно решить interlocked-счетчиком reader-ов.
Имею скафандр — готов путешествовать!
Re[2]: Межпроцессное разделение данных без блокировки
Здравствуйте, 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: Межпроцессное разделение данных без блокировки
// Запись
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]: Межпроцессное разделение данных без блокировки
Здравствуйте, 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]: Межпроцессное разделение данных без блокировки
Здравствуйте, GlebZ, Вы писали:
GZ>А memcpy — атомарно?
Нет конечно.
GZ>Что будет если переключение потока произойдет когда происходит memcpy от писателя?
Дело даже не переключении как таковом, потоки могут просто работать одновременно.
Version до начала чтения и по его окончании будет разная. Нарвавшись на это мы ждём, когда писатель закончит запись и уравняет Version и Version2, после чего начинаем заново.
"Нормальные герои всегда идут в обход!"
Re[6]: Межпроцессное разделение данных без блокировки
Здравствуйте, 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: Межпроцессное разделение данных без блокировки
Здравствуйте, Alexander G, Вы писали:
AG>Имеется несколько процессов, общие настройки между ними лежат POD-структуре в MMF. Один из процессов может читать и менять настройки, остальные — только читать.
AG>Чтобы обойтись без лишних блокировок, и исходя из того, что настройки меняются крайне редко, в MMF также есть номер текущей версии настроек чтение и запись настроек проиходит следующим образом:
Если настройки меняются крайне редко, то использование read/write lock'ов даст вам ничуть не худшее решение этой проблемы, и при том простое и понятное.
Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.
Re[7]: Межпроцессное разделение данных без блокировки
Здравствуйте, Alexander G, Вы писали:
AG>Имеется несколько процессов, общие настройки между ними лежат POD-структуре в MMF. Один из процессов может читать и менять настройки, остальные — только читать.
Тут есть еще одна подводная скала(или грабли). Как то нас шибануло. Когда процесс меняет настройки, то он должен это делать на основе прочитанных данных. Следовательно, между чтением и записью одного такого процесса не должно быть никаких записей другого процесса.
Re: Межпроцессное разделение данных без блокировки
Господа начинающие системщики!
Посмотрите на этот пример, он весьма наглядно демонстрирует не-системный подход к задаче, равно как и недостаток знаний. Давайте посмотрим, что хочет автор этого топика:
Один писатель, много читателей.
Целостность данных, полученных каждым читателем.
Во-первых, писатель у нас один, это уже большой плюс, т.к. не придётся их синхронизировать. Но есть ещё проблема с читателями. Хорошо, смотрим далее. На слове "целостность" мы уже должны насторожиться. Лучше всего в этот момент будет вспомнить понятие "транзакция", которая есть суть следующее:
Операция, которая выполняется целиком и успешно, или не выполняется совсем.
Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами. Это всё на интерлокерах можно сделать, никто никого не ждёт. Читателям только смотреть нужно будет, в какой из буфер писали последний раз, флажок там какой-нибудь. Вот и всё. Похожий принцип и в графике, например, применяется, называется swap buffers.
Да, если писателей будет больше одного, то достаточно сделать по два буфера и по одному флажку на каждого из них, а принцип тот же.
P.S.
Сорри за многа букаф, просто показалось, что тут нужно расписать чуть подробнее.
JID: x64j@jabber.ru
Re[2]: Межпроцессное разделение данных без блокировки
Здравствуйте, x64, Вы писали:
x64>Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами.
Меняет, я так понимаю, индексы буферов, т.к. сами буфера одной операцией не поменяешь?
Допустим, писатеь только что записал буфер, в момент, когда читеталь читал другой буфер. Теперь, читатель так и не получил управления, а писатель снова что-то записал, в другой буфер, т.е. в читаемый, и данные не целостны?
Русский военный корабль идёт ко дну!
Re[2]: Межпроцессное разделение данных без блокировки
Здравствуйте, Pzz, Вы писали:
Pzz>Если настройки меняются крайне редко, то использование read/write lock'ов даст вам ничуть не худшее решение этой проблемы, и при том простое и понятное.
Pzz>Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.
Вистовский подошел бы, если бы там были потоки, а не процессы.
Русский военный корабль идёт ко дну!
Re[3]: Межпроцессное разделение данных без блокировки
Здравствуйте, Alexander G, Вы писали:
Pzz>>Их, правда, нет в готовом виде в Win32 API до Висты, но их можно сделать из подручного хлама из других примитивов синхронизации, и есть некоторое количество готовых реализаций в разных библиотеках.
AG>Вистовский подошел бы, если бы там были потоки, а не процессы.
Да, правда.
Но как бы то ни было, сделать rwlock из подручного хлама несложно. В т.ч. из того, что есть в любой разумной версии венды.
Re[3]: Межпроцессное разделение данных без блокировки
x64>>Заметьте — "выполняется целиком". Ага, т.е. целостность нам гарантируется. Подумаем, как мы могли бы применить транзакции в данном случае. Нам нужно, чтобы какое-то время, пока писатель делает свою работу, данные не были прочитаны. И прерывать работу читателей тоже нельзя. Отлично, в таком случае во время записи данных пусть читатели работают с той же копией данных, а писать в это время пишет куда-то ещё. Второй буфер, грубо говоря. По окончании записи писатель меняет данные местами.
AG>Меняет, я так понимаю, индексы буферов, т.к. сами буфера одной операцией не поменяешь? AG>Допустим, писатеь только что записал буфер, в момент, когда читеталь читал другой буфер. Теперь, читатель так и не получил управления, а писатель снова что-то записал, в другой буфер, т.е. в читаемый, и данные не целостны?
Да, в таком виде это не работает. Налицо не системный подход
По существу вопроса.
Рекомендую поискать по словам lock-free. Практически все из того, что будет найдено, при некотором дотачивании напильником (а иногда и без этой процедуры) вам подойдет.