Re[3]: Вариант без счетчиков читателей и блокировок
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 16:12
Оценка:
Здравствуйте, rus blood, Вы писали:

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


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


RB>>
RB>    #LoadLaod
RB>>    }
RB>>    while (!(flag == mmf->flag && counter == data->counter));   // сначала - flag, потом - counter
RB>>}
RB>>


И там и там:
    do
    {
        flag = mmf->flag;
        #LoadLaod
        data = &mmf->data[flag];
        counter = data->counter;
        #LoadLaod
        .... // читаем
        #LoadLaod
    }
    while (counter == data->counter);


Два считывания data->counter обрамляют критическую секцию, чтения данные не должны уходить из неё ни вверх, ни вниз.
Плюс я бы ограничился проверкой только "counter == data->counter".


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

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


Проблема только что деградирует в сотни и тысячи раз. И чем больше ядер/процессоров — тем хуже. Масштабируемость — отрицательная.


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

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

А если вот тут result заменят и удалят?

G6>        result->AddRef() ;
G6>        return( result ) ;
G6>    }
G6>} ;
G6>



1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 19.04.10 16:50
Оценка: 15 (1)
Здравствуйте, Alexander G, Вы писали:

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

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


Вполне работоспособно (после небольших правок) и называется SeqLock. Используется в ядре Linux как раз для таких ситуаций, в частности при чтении 64-битных таймстемпов на архитектурах, где не поддерживаются атомарные 64-битные чтения — использовать тут спинлок было бы убийством. За гарантированно работающей реализацией смотри ./kernel/linux/seqlock.h
В целом SeqLock — очень хороший алгоритм (если применим), т.к. сочетает в себе относительную просто реализацию + идеальную масштабируемость (доступ на чтение остаётся доступом на чтение на физическом уровне). А применим он, если структура полностью самодостаточная (не содержит указателей на внешние данные), и читатели могут пережить чтение неконсистентных данных (после которого, естественно, будет повторное чтение, но это чтение должно по-крайней мере закончиться без зависаний и падений).

Я предложил улучшение этого алгоритма, которое делает доступ на чтение полностью lock-free (нет необходимости делать SwitchToThread() вообще):
http://groups.google.com/group/lock-free/browse_frm/thread/5705265ef7a1a1a3
Примерно этот алгоритм уже предлагался тут:
http://rsdn.ru/forum/winapi/3763426.1.aspx
Автор: rus blood
Дата: 06.04.10

Единственное, что структуры может быть не 2, а произвольное число — в зависимости от частоты записи, и размера структуры. В данном случае, я думаю, вполне хватит двух объектов.

Кроме этого ничего советовать не буду. Если данные можно копировать memcpy'ем, и это не обходится очень дорого, то это — идеальный вариант.

Единственное, в контексте меж-процессного взаимодействия есть проблема поддержания консистентности данных при смерти пишущего процесса. Можно решить следующим образом.
Заводишь именованный мьютекс для писателя, при старте процесса он захватывает этот мьютекс, при выходе — освобождает. Мьютекс не используется для обеспечения консистентности данных, он используется только для определения смерти процесса. Если процесс при захвате мьютекса получает код, что предыдущий процесс умер насильственной смертью, то он проходит по всем объектам и инкрементирует нечётные счётчики. На первый взгляд, этого должно хватить.

Про reader-writer мьютексы не слушай. Они не обеспечивают параллельной работы читателей и не масштабируются. Да и robust реализацию его сделать не так-то и просто.


Вот код SeqLock из ядра 2.6.31:
typedef struct seqcount {
    unsigned sequence;
} seqcount_t;

#define seqcount_init(x)    do { *(x) = (seqcount_t) SEQCNT_ZERO; } while (0)

/* Start of read using pointer to a sequence counter only.  */
static inline unsigned read_seqcount_begin(const seqcount_t *s)
{
    unsigned ret;

repeat:
    ret = s->sequence;
    smp_rmb();
    if (unlikely(ret & 1)) {
        cpu_relax();
        goto repeat;
    }
    return ret;
}

/*
 * Test if reader processed invalid data because sequence number has changed.
 */
static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    smp_rmb();

    return s->sequence != start;
}

/*
 * Sequence counter only version assumes that callers are using their
 * own mutexing.
 */
static inline void write_seqcount_begin(seqcount_t *s)
{
    s->sequence++;
    smp_wmb();
}

static inline void write_seqcount_end(seqcount_t *s)
{
    smp_wmb();
    s->sequence++;
}



1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 20.04.10 09:53
Оценка:
Здравствуйте, remark, Вы писали:

R>Вполне работоспособно (после небольших правок) и называется SeqLock. Используется в ядре Linux как раз для таких ситуаций, в частности при чтении 64-битных таймстемпов на архитектурах, где не поддерживаются атомарные 64-битные чтения — использовать тут спинлок было бы убийством. За гарантированно работающей реализацией смотри ./kernel/linux/seqlock.h


Суть исправления только в этом? :


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



R>Я предложил улучшение этого алгоритма, которое делает доступ на чтение полностью lock-free (нет необходимости делать SwitchToThread() вообще):

R>http://groups.google.com/group/lock-free/browse_frm/thread/5705265ef7a1a1a3
R>Примерно этот алгоритм уже предлагался тут:
R>http://rsdn.ru/forum/winapi/3763426.1.aspx
Автор: rus blood
Дата: 06.04.10

R>Единственное, что структуры может быть не 2, а произвольное число — в зависимости от частоты записи, и размера структуры. В данном случае, я думаю, вполне хватит двух объектов.

Я ещё подумаю, я что-то плохо понимаю его.

R>Единственное, в контексте меж-процессного взаимодействия есть проблема поддержания консистентности данных при смерти пишущего процесса. Можно решить следующим образом.

R>Заводишь именованный мьютекс для писателя, при старте процесса он захватывает этот мьютекс, при выходе — освобождает. Мьютекс не используется для обеспечения консистентности данных, он используется только для определения смерти процесса. Если процесс при захвате мьютекса получает код, что предыдущий процесс умер насильственной смертью, то он проходит по всем объектам и инкрементирует нечётные счётчики. На первый взгляд, этого должно хватить.

так можно?
// Чтение
...
  memcpy(&local_settings, &shared_data->settings, sizeof(Settings));
  after = InterlockedAdd(&shared_data->version, 0);
  if (before != after) {
    if (after % 2)
    {
      // главный начал и не окончил запись
      DWORD code = 0;
      VERIFY(GetExitCodeProcess(main_process_handle, &code));
      if (code != STILL_ACTIVE) {
        // главный умер, больше делать нечего
        memset(&local_settings, 0, sizeof(Settings)); 
        goto NothingMoreToDo; 
      }
      // попробуем дать главному дописать
      SwitchToThread();
    }
    goto ReadSettings;
  }


R>

Русский военный корабль идёт ко дну!
Re[2]: Межпроцессное разделение данных без блокировки
От: Alexander G Украина  
Дата: 20.04.10 10:11
Оценка:
Здравствуйте, remark, Вы писали:

R>Единственное, что структуры может быть не 2, а произвольное число — в зависимости от частоты записи, и размера структуры. В данном случае, я думаю, вполне хватит двух объектов.


От лишних обозримого вреда нет?

У меня данные всё равно на MMF, поэтому меньше чем размер страницы там не выделишь, а настроек там байт 20.
Русский военный корабль идёт ко дну!
Re[3]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 20.04.10 11:09
Оценка: 5 (1)
Здравствуйте, Alexander G, Вы писали:

AG>Суть исправления только в этом? :


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


Забыл упомянуть — никогда не надо читать данные с помощью InterlockedXXX, это превращает их в записи, а записи не масштабируются.
Плюс я бы не делал лишние SwitchToThread().
Это для MSVC/x86, предполагается, что shared_data->version объявлена volatile:

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



1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Межпроцессное разделение данных без блокировки
От: remark Россия http://www.1024cores.net/
Дата: 20.04.10 11:14
Оценка: 5 (1)
Здравствуйте, Alexander G, Вы писали:

R>>Я предложил улучшение этого алгоритма, которое делает доступ на чтение полностью lock-free (нет необходимости делать SwitchToThread() вообще):

R>>http://groups.google.com/group/lock-free/browse_frm/thread/5705265ef7a1a1a3
R>>Примерно этот алгоритм уже предлагался тут:
R>>http://rsdn.ru/forum/winapi/3763426.1.aspx
Автор: rus blood
Дата: 06.04.10

R>>Единственное, что структуры может быть не 2, а произвольное число — в зависимости от частоты записи, и размера структуры. В данном случае, я думаю, вполне хватит двух объектов.

AG>Я ещё подумаю, я что-то плохо понимаю его.


Если коротко, то там примерно так же как и у тебя, за исключением следующего.
Объекта 2. Один из них является "текущим" для чтения. Писатель всегда пишет не в "текущий", и после записи делает его "текущим". Т.о. у нас всегда есть хотя бы 1 консистентный объект для чтения, и если читатель увидел неконсистентный объект (version % 2 != 0), то значит, что он читает не тот объект, который надо. Соотв. он просто перечитывает какой объект текущий и повторяет.
В твоём же варианте, если писатель прерван по время записи, все читатели будут заблокированы на это время (объект только один, и он неконсистентный, делать нечего — приходится ждать).


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