Re[3]: Межпроцессное разделение данных без блокировки
От: x64 Россия http://x64blog.name
Дата: 05.04.10 22:27
Оценка:
AG>Меняет, я так понимаю, индексы буферов, т.к. сами буфера одной операцией не поменяешь?

Да.

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


Да, ты прав.
Вот здесь, например, есть обсуждение проблемы, раздел "Retiring Old Data" и далее.
JID: x64j@jabber.ru
Re: Межпроцессное разделение данных без блокировки
От: x64 Россия http://x64blog.name
Дата: 05.04.10 22:58
Оценка: 9 (1) :))
Ещё есть вариант написать службу, и тогда не нужно будет ничего придумывать, ибо читать/писать будет только один процесс всегда.
JID: x64j@jabber.ru
Re[4]: Межпроцессное разделение данных без блокировки
От: Jolly Roger  
Дата: 06.04.10 07:13
Оценка:
Здравствуйте, Andrew S, Вы писали:

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


AS>Например, вот:

AS>http://rsdn.ru/forum/cpp/1800649.aspx
Автор: remark
Дата: 23.03.06


Там замечательный пример, но он базируется на возможности создать неограниченное число копий, а MMF не резиновый. Любое же заранее заданное число всегда оставляет риск нарваться на ситуацию, когда этого числа не хватит. Можно конечно "забить", и сказать : "у меня писать будут редко, поэтому пула из 10(100, 1000,...) буферов мне хватит всегда."

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

В голову приходит проверка хэш-подписи. То есть смотрим версию в MMF, если старая — работаем с локальной копией, если нет — читаем новые и считаем хэш. Не совпал — перечитываем. Но как-то это "топорно", по-моему, да и полной гарантии не даёт из-за неуникальности хэша.
"Нормальные герои всегда идут в обход!"
Re[4]: Межпроцессное разделение данных без блокировки
От: rus blood Россия  
Дата: 06.04.10 07:34
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

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


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

С учетом это фразы

настройки меняются крайне редко

можно вообще забить на синхронизацию.

Например, писатель пишет timestamp в структуру.
Перед записью новых данных писатель ждет определенного timeout-а на основе timestamp его новой структуры.
Имею скафандр — готов путешествовать!
Re[2]: Межпроцессное разделение данных без блокировки
От: rus blood Россия  
Дата: 06.04.10 07:36
Оценка: +2
Здравствуйте, GlebZ, Вы писали:

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


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

GZ>Тут есть еще одна подводная скала(или грабли). Как то нас шибануло. Когда процесс меняет настройки, то он должен это делать на основе прочитанных данных. Следовательно, между чтением и записью одного такого процесса не должно быть никаких записей другого процесса.

Здесь только один писатель, такой проблемы не будет.
Имею скафандр — готов путешествовать!
Re[3]: Межпроцессное разделение данных без блокировки
От: rus blood Россия  
Дата: 06.04.10 07:46
Оценка: -1
Здравствуйте, Alexander G, Вы писали:

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

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

Ты сам написал, что данные меняются "крайне редко".
Вероятность, что какой-то читатель не закончил с позапрошлым буфером, будет мала.
Чтобы совсем исключить столкновение, можно добавить счетчики читателей.
Причем, значение счетчика той структуры, которую ждет писатель, будет только уменьшаться, если будет ненулевое.

Но счетчики мне не нравятся, т.к. это лишние interlocked-операции. Если данные меняются крайне редко, можно сделать ожидание писателем определенного timeout-а с момента последней записи в ту структуру, в которую он собирается записывать новые данные. Т.е. с момента позапрошлого обновления данных.
Имею скафандр — готов путешествовать!
Re: Вариант без счетчиков читателей и блокировок
От: rus blood Россия  
Дата: 06.04.10 08:04
Оценка: 72 (2)
Здравствуйте, Alexander G, Вы писали:

struct DATA
{
    volatile long counter;
    ... данные
};

struct MMF
{
    volatile long flag;
    DATA data[2];
};

MMF* mmf = ...

void write()
{
    DATA* data = &mmf->data[1 - mmf->flag];
    long counter = data->counter;
    data->counter = ++ counter;
    #StoreStore
    ... // пишем
    #StoreStore
    data->counter = ++ counter;
    #StoreStore
    mmf->flag = 1 - mmf->flag;
}

void read()
{
    long flag;
    long counter;
    DATA* data;

    do
    {
        flag = mmf->flag;
        #LoadLaod
        data = &mmf->data[flag];
        counter = data->counter;
        .... // читаем
        #LoadLaod
    }
    while (flag == mmf->flag && counter == data->counter);   // сначала - flag, потом - counter
}
Имею скафандр — готов путешествовать!
Re[2]: Вариант без счетчиков читателей и блокировок
От: rus blood Россия  
Дата: 06.04.10 08:08
Оценка:
Здравствуйте, rus blood, Вы писали:

Наоборот, конечно...

RB>
    #LoadLaod
RB>    }
RB>    while (!(flag == mmf->flag && counter == data->counter));   // сначала - flag, потом - counter
RB>}
RB>
Имею скафандр — готов путешествовать!
Re: Межпроцессное разделение данных без блокировки
От: okman Беларусь https://searchinform.ru/
Дата: 06.04.10 08:21
Оценка: 9 (1) +1 -1
Alexander G, приветствую.

А зачем усложнять ? Такой код малопонятен и не дает никакого выигрыша, поскольку настройки все равно
меняются редко. К тому же его труднее сопровождать. На мой взгляд, решение с обычными блокировками
лучше соответствует сути задачи.
Re: Межпроцессное разделение данных без блокировки
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 06.04.10 08:54
Оценка: -1
Здравствуйте, Alexander G, Вы писали:
AG>3. Что ещё можете посоветовать?

Засинхронизировать доступ к шаренным данным с помощью примитивов синхронизации и не морочить себе голову (вы ж не embedded утройства программируете, правда? sync-примитивы на целевой платформе имеются, не так ли?)

Например при одном писателе, редких записях, прорве читателей и частых чтениях прекрасно подойдут boost::shared_mutex и его типы блокировок:

boost::shared_mutex syncObj;

// Читаем данные:
// читатели друг дружку не ждут,
// впадают в ожидание, если данные собираются быть захвачены
// или уже захвачены писателем
{
    boost::shared_lock<boost::shared_mutex> lockObj(syncObj);
    // TODO
}

// Пишем данные:
// писатель ждёт всех читателей, кто ещё не успел закончить работу
// (в это время блокируется захват примитива "новыми читателями"),
// после чего захватывает окончательно и начинает писать.
{
    boost::upgrade_lock<boost::shared_mutex> lockObj(syncObj);
    boost::upgrade_to_unique_lock<boost::shared_mutex> lockObj2(lockObj);
    // TODO
}
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
Re[2]: Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 06.04.10 09:22
Оценка:
Здравствуйте, Rakafon, Вы писали:


R>Например при одном писателе, редких записях, прорве читателей и частых чтениях прекрасно подойдут boost::shared_mutex и его типы блокировок:


boost::shared_mutex не работает для межпроцессной синхронизации
Русский военный корабль идёт ко дну!
Re[2]: Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 06.04.10 09:26
Оценка:
Здравствуйте, okman, Вы писали:

O>А зачем усложнять ? Такой код малопонятен и не дает никакого выигрыша, поскольку настройки все равно

O>меняются редко. К тому же его труднее сопровождать. На мой взгляд, решение с обычными блокировками
O>лучше соответствует сути задачи.

Выигрыш в том, что меняются настройки очень редко, но читаются они очень часто. Но наверное так и сделаю: счётчик версии настроек, но дальше — блокировка.
Русский военный корабль идёт ко дну!
Re[3]: Межпроцессное разделение данных без блокировки
От: rus blood Россия  
Дата: 06.04.10 10:16
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Выигрыш в том, что меняются настройки очень редко, но читаются они очень часто. Но наверное так и сделаю: счётчик версии настроек, но дальше — блокировка.


А чем тебе такой вариант
Автор: rus blood
Дата: 06.04.10
без блокировок и interlocked-операций не подходит?

Смотри, вот как идет код читателя в "плохом" случае:
// flag = mmf->flag;
// counter = data->counter;
R1: load(flag), допустим, тут прочитали 0.
R2: load(counter[0])
..... читаем

// while (!(flag == ... && counter == ...));
R3: load(flag), допустим, тут тоже прочитали 0.
R4: load(counter[0]) - здесь ноль, прочитанный на шаге R1 !!!


A вот как выполняется код писателя, если flag равен 0 на шаге R1 и шаге R3:

flag содержит 0.
..............
W1: counter[1] ++
// пишем
W2: counter[1] ++
W3: store(flag) <- 1

W4: counter[0] ++
// пишем
W5: counter[0] ++
W6: store(flag) <- 0


Причем шаги W3 и W6 прошли между R1 и R3.
Это значит, что шаги W4 и W5 попали внутрь R1 и R3.
Единственная возможность читателю прочитать одинаковое значение counter на шагах R2 и R4 — это выполнение шагов W4 и W5 до шага R2.
Но это означает, что писатель закончил обновление данных до того, как читатель их прочитал.

В любом другом случае значение counter на шаге R4 будет отличаться от прочитанного на шаге R2 и читатель пойдет на второй круг.
Имею скафандр — готов путешествовать!
Re[3]: Межпроцессное разделение данных без блокировки
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 06.04.10 10:32
Оценка: 10 (1)
Здравствуйте, Alexander G, Вы писали:
AG>boost::shared_mutex не работает для межпроцессной синхронизации

Попробуйте named_upgradable_mutex
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
Re[5]: Межпроцессное разделение данных без блокировки
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.04.10 20:13
Оценка: 3 (1)
AS>>Рекомендую поискать по словам lock-free. Практически все из того, что будет найдено, при некотором дотачивании напильником (а иногда и без этой процедуры) вам подойдет.

AS>>Например, вот:

AS>>http://rsdn.ru/forum/cpp/1800649.aspx
Автор: remark
Дата: 23.03.06


JR>Там замечательный пример, но он базируется на возможности создать неограниченное число копий, а MMF не резиновый. Любое же заранее заданное число всегда оставляет риск нарваться на ситуацию, когда этого числа не хватит. Можно конечно "забить", и сказать : "у меня писать будут редко, поэтому пула из 10(100, 1000,...) буферов мне хватит всегда."


Есть примеры очередей и без копирования.
Кроме того, в boost::interprocess есть как именованые примитивы синхронизации, так и возможность создать оные на основе MMF.

JR>Но гораздо интереснее попытаться изобрести собственный велосипед, пусть даже ошибочный. Ведь опыт — он, как сказал Классик, любимый сынок ошибок трудных и не очень, а опыт, он лишним никому не будет, ни начинающим системщикам, ни даже заканчивающим


Опыт, он, конечно, не лишний. Но прежде чем изобретать такие сложные вещи, как примитивы синхронизации, необходимо четко изучить то, что уже сделано в этой области.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re: Межпроцессное разделение данных без блокировки
От: gwg-605 Россия  
Дата: 15.04.10 06:13
Оценка:
Здравствуйте, Alexander G, Вы писали:

Прошу прощения если поздно но обычно решал эту проблему одним указателем, объектом с автореференсом и локом
лок используется для блокировки кода писателя, если вдруг писателей несколько.

template<class _T>
class TMultiReaderAndOneWriterLock {
protected:
    CCriticalSection m_Sync ;     // protect for multi writers
    volatile _T* m_ActiveObject ;    // pointer to active data, must be alligned to CPU word
public:
    void Write( _T* obj ) {        // writer put new data
        m_Sync.Enter() ;
        _T* old = m_ActiveObject ;
        obj->AddRef() ;
        m_ActiveObject = obj ;
        m_Sync.Leave() ;
        old->Release() ;
    }

    // read current data
    // NOTES: Reader MUST release data after use it
    //      Reader can use data as long as needed,
    //      but better practice is to release it asap
    inline _T* Read() {            
        _T* result = m_ActiveObject ;
        result->AddRef() ;
        return( result ) ;
    }
} ;


AddRef() и Release() — обязательно должны использовать атомарные операции инкременда и декремента.

ЗЫ. Писал по памяти, кода под рукой нету, прошу прощения за неточности
ЗЫЫ. Я знаю что данная схема не всегда приминима, но для ПС где мы можем позволить себе копии данных это работает причем если реально один писатель то обходимся без вызова системых функций и переключения контекстов.
ЗЫЫЫ. А адаптировать ее для шаред памяти можно без проблем. Перетащи темплейт в структуру и выдели тройку блоков под инстанцы объекта. Три блока: один активный, один ждет когда ридеры его отрелизят, а один в который писатель пишет.
Re[2]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 15:57
Оценка:
Здравствуйте, Pzz, Вы писали:

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


Принципиально худшее и полностью не масштабируемое. read/write lock НЕ обеспечивает параллельную работу нескольких читателей в общем случае, он обеспечивает более-менее параллельную работу только если размер критической секции для читателей составляет десятки микросекунд. Если же секция маленькая (что обычно имеет место быть), то read/write lock не только не обеспечивает физически параллельной работы, то и получается медленнее обычного лока. Основная его проблема, что читатели получаются вовсе не читатели, а писатели — пишут во внутренние данные лока. А записи а разделяемые данные очень медленные и убивают масштабируемость на корню. На физическом уровне нельзя писать в одну ячейку памяти параллельно, все записи сериализуются аппаратным обеспечением, отсюда и проблемы.
Когда я тестировал истинный (физический) доступ на чтение против доступа, который пишет в разделяемые данные, на 4-ёх ядерном процессоре, я получал разницу в 300 раз. При чём чем больше ядер/процессоров, тем эта разница увеличивается, причём драматически. Если бы речь шла о многопроцессорной системе, то разница была бы более чем в 1000 раз.
Доступ на чтение — это очень хорошая вещь в многопоточном окружении, и очень желательно реализовывать его именно как доступ на чтение на физическом уровне, без каких-либо записей в разделяемые данные. Это ключ к линейной масштабируемости.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 15:58
Оценка:
Здравствуйте, x64, Вы писали:

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


Слабо набросать реализацию?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 16:01
Оценка:
Здравствуйте, x64, Вы писали:

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


x64>Да, ты прав.

x64>Вот здесь, например, есть обсуждение проблемы, раздел "Retiring Old Data" и далее.

На всякий случай, SMR (Hazard pointers) запатентована Sun Microsystems (ныне Oracle) на территории США и возможно других стран, и не может там свободно использоваться.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 16:04
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

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


AS>>Например, вот:

AS>>http://rsdn.ru/forum/cpp/1800649.aspx
Автор: remark
Дата: 23.03.06


JR>Там замечательный пример, но он базируется на возможности создать неограниченное число копий, а MMF не резиновый. Любое же заранее заданное число всегда оставляет риск нарваться на ситуацию, когда этого числа не хватит. Можно конечно "забить", и сказать : "у меня писать будут редко, поэтому пула из 10(100, 1000,...) буферов мне хватит всегда."


В целом, я его делал именно для такой ситуации — хранение редко изменяемых объектов.
Да, этот алгоритм потенциально использует неограниченное число копий. Теоретически, его можно изменить под использование не более N копий (тогда, кстати, не нужен будет DWCAS). Но в целом я бы не рекомендовал его использовать в данной ситуации, тут действительно лучше подойдёт оригинальный вариант из первого поста ветки (немного подправленный), более подробно в отдельном сообщении.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.