Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Очень быстрая синхронизация


Автор: cupper 17.5.2012, 17:42
Делаем кроссплатформенно. В общем такая проблема. Есть два потока, T1, T2. T1 крутится в цикле, с интервалом в 5mcs (тобишь очень быстро). В Т2 цикл выполняется с интервалов в 300 миллисекунд.
Нужно между ними синхронизатор две переменный (одна структура из двух полей).
Код

struct State
{
enum Action{A, B,C};

Action act;
time_t last_update;
}


Т.е. действие выполненное в T1 и время когда оно было выполнено. Нужно сделать минимальную задержку в T1. Так что мютексы, отпадают.
Есть две идеи:
1. В структура State добавляем булев флаг (аля критической секции)
Код

struct State
{
enum Action{A, B,C};

bool lock;

Action act;
time_t last_update;
}

// В потоке T1
while(boost::atomic_cas32(state.lock, true, false)) {};

state.act = ...
state.last_update = time(NULL)

boost::atomic_write(state.lock, false);

// В потоке T2 аналогично
...

Плохо то, что если и Т1 и Т2 выполняются одним ядром (псевдо мультипоточность) и мы например в Т1 начали работу (state.lock = true), потом планировщик переключился в T2, то T2 будет болтаться в пустом цыкле, пока управление не будет передано опять в T1 который освободит псевдомютекс. Вообще вероятность этого велика ли ? что столь мелкие части кода будет вставлено переключение контекстов ? Самая долгая операция будет time(NULL).

2. Обе переменные State вместить в одну int64 и работать с ней атомарно. Это будет вообще без блокировок. Но просто лень возиться с побитовыми операциями smile Ну и плюс, ограничение 32-х битного time_t

Автор: Леопольд 17.5.2012, 22:32
Может можно сделать проще с обычным мьютексом?

Код


void thr1()
{
   while(...)
   {
      // делаем что-то
      time_t t = time();
      if(last_update != t)
      {
         scoped_lock lock(mutex);
         // меняем обе переменные
      }
      else
      {
         // атомарно меняем одну переменную
      }
      
   }
}

void thr2()
{
   while(...)
   {
      {
          scoped_lock lock(mutex);
          // читаем значение переменных
      }
   }
}

смысл в том что синхронизация будет происходить не чаще чем раз в секунду (при условии что оба потока одновременно залочили мьютекс).

Автор: bsa 17.5.2012, 23:35
Цитата(cupper @  17.5.2012,  18:42 Найти цитируемый пост)
 Обе переменные State вместить в одну int64 и работать с ней атомарно.

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

Автор: volatile 18.5.2012, 00:05
Цитата(cupper @  17.5.2012,  17:42 Найти цитируемый пост)
enum Action{A, B,C};
Action act;

Если экшенов немного. 3, как здесь или 4, то можно попытаться их запихать с time_t в одну атомарную 32-битную переменную.
за счет младших битов например, или старших, смотря чем вы готовы пожертвовать, точностью или абсолютной величиной.

это и будет, имхо 
Цитата(cupper @  17.5.2012,  17:42 Найти цитируемый пост)
Очень быстрая синхронизация, ну прям очень очень быстрая 

 smile 


Автор: Леопольд 18.5.2012, 08:06
Цитата(bsa @  17.5.2012,  23:35 Найти цитируемый пост)
что-то я не уверен, что абсолютно все процессоры могут атомарно с такими переменными работать. Просто может получиться так, что эта атомарность будет реализована через мьютекс... 

Да и time_t скоро будет 64 бита.
Хотя, тут можно "схитрить", из потока в поток передавать только N младших бит, недостающие биты брать из сискола time()

Но мне больше по душе первый вариант. IMHO, он производительнее (нет второго сискола) и правильно хендлится смена сис. времени.

Автор: cupper 18.5.2012, 14:08
Цитата(Леопольд @  17.5.2012,  22:32 Найти цитируемый пост)
смысл в том что синхронизация будет происходить не чаще чем раз в секунд

думал об этом. Но это может привести к по секундному провалу времени выполнения th1.
Цитата(bsa @  17.5.2012,  23:35 Найти цитируемый пост)
что-то я не уверен, что абсолютно все процессоры могут атомарно с такими переменными работать. Просто может получиться так, что эта атомарность будет реализована через мьютекс... 

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

Цитата(volatile @  18.5.2012,  00:05 Найти цитируемый пост)
Если экшенов немного. 3, как здесь или 4, то можно попытаться их запихать с time_t в одну атомарную 32-битную переменную.

Порядок важен до  секунд до 10-20 (а может и 60-ти, зависит от настройки). 32битная time_t мне и так уже душу режит что это только до 2038 года smile
Состояний у меня не больше 8. Т.е. 3 млашие бита, т.е. точность до 8 секунд. Думаю пойдет. В крайнем случае можно расширить до 4 бит, и точность будет до 16 секунд, что тоже приемлемо. Но думаю, из за пункта выше, это в принципе не так важно. Но зато, наконец то до меня дошло как решить проблему 2038 года для себя smile


Цитата(Леопольд @  18.5.2012,  08:06 Найти цитируемый пост)
Да и time_t скоро будет 64 бита.

То, что разработка ведется на 64 битной системе, но собирается под x32 не дает мне понять сейчас time_t уже 64 для любой архитектуры или все еще зависит от разрядности ? Может можно как то явно обращаться к time64 ? Под винду это есть _time64, но вот под линукс... там я что то этого не нашел. 

Цитата(Леопольд @  18.5.2012,  08:06 Найти цитируемый пост)
Хотя, тут можно "схитрить", из потока в поток передавать только N младших бит, недостающие биты брать из сискола time()

не понял.

Автор: Леопольд 18.5.2012, 20:50
Цитата(cupper @  18.5.2012,  14:08 Найти цитируемый пост)
не понял.


Код

std::atomic<uint32_t> data;

void thr1()
{
   ...
   std::uint32_t local_data = static_cast<std::uint32_t>(::time()) << 4;
   local_data |= local_flag; // local_flag шириной в 4 бита
   data.store(local_data, std::memory_order_release);
   ...
}

void thr2()
{
   ...
   std::uint32_t local_data = data.load(std::memory_order_acquire);
   if( (local_data >> 4) & 0xFFFFFFF != 0xFFFFFF) // условие добавил после коммента volatile
   {   
      local_flag = static_cast<EnumType>(local_data & 0xF);
      local_time = (::time() & ~0xFFFFFFF) | static_cast<time_t>(local_data >> 4) ;
   }
   ...
}


Цитата(cupper @  18.5.2012,  14:08 Найти цитируемый пост)
думал об этом. Но это может привести к по секундному провалу времени выполнения th1.
В смысле посекундному? Если это не риалтайм, то ничего страшного если один раз в секунду потоки будут синхронно получать доступ к переменным. Как только поток проапдейтил/прочитал переменную, мьютекс сразуже разлочивается, тут счёт на микросекунды. Преждевременная оптимизация - зло, впрочем IMHO, меньшее чем преждевременная пессимизация...

Ты не рассматривал возможность использования condition variable?

Автор: volatile 18.5.2012, 23:50
Цитата(Леопольд @  18.5.2012,  20:50 Найти цитируемый пост)
   time_t local_time = (::time() & ~0xFFF) | static_cast<time_t>(local_data >> 4) ;

Леопольд, не очень хороший код.  smile 
Между вывовами время, может измениться. и где гарантия что это не затронет 4 старших бита?
маловероятно, пожалуй. Но тем не менее, не чисто.
(Раз неприятность может случиться, то по законам Мерфи она обязательно случится).

Цитата(cupper @  18.5.2012,  14:08 Найти цитируемый пост)
Порядок важен до  секунд до 10-20 (а может и 60-ти, зависит от настройки)

Ну раз так, что проще запихать экшены, просто в младщие разряды.
Можно конечно и сохранить точность времени, как предложил Леопольд, путем дополнительного вызова ::time(),
но там толко нужно делать округление.
Но раз это не столь необходимо, то это, уже видимо оверкилл.
задача же стояла, "самое быстрое..."

 

Автор: Леопольд 19.5.2012, 10:19
Цитата(volatile @  18.5.2012,  23:50 Найти цитируемый пост)
Между вывовами время, может измениться. и где гарантия что это не затронет 4 старших бита?
маловероятно, пожалуй. Но тем не менее, не чисто.

Это будет происходить раз в 8 лет, думаю можно как-то похендлить это "переполнение" , например не доверять значениям (local_data >> 4) & 0xFFFFFFF == 0xFFFFFFF.
Цитата(Леопольд @  18.5.2012,  08:06 Найти цитируемый пост)
Но мне больше по душе первый вариант. IMHO, он производительнее (нет второго сискола) и правильно хендлится смена сис. времени.


Добавлено @ 10:28
Цитата(volatile @  18.5.2012,  23:50 Найти цитируемый пост)
Леопольд, не очень хороший код.
Константа неправильная, должна быть 0xFFFFFFF (поправил в примере)

Автор: cupper 21.5.2012, 08:39
Цитата(Леопольд @  18.5.2012,  20:50 Найти цитируемый пост)
Если это не риалтайм, то ничего страшного если один раз в секунду потоки будут синхронно получать доступ к переменным. Как только поток проапдейтил/прочитал переменную, мьютекс сразуже разлочивается, тут счёт на микросекунды. Преждевременная оптимизация - зло


Не реалтайм, но счет идет как раз на микросекунды. Даже лишнее переключение потоков уже наверно скажется на общей картине.
Цитата(Леопольд @  18.5.2012,  20:50 Найти цитируемый пост)
Ты не рассматривал возможность использования condition variable?

не тот случай.
Цитата(volatile @  18.5.2012,  23:50 Найти цитируемый пост)
Между вывовами время, может измениться. и где гарантия что это не затронет 4 старших бита?
маловероятно, пожалуй. Но тем не менее, не чисто.
(Раз неприятность может случиться, то по законам Мерфи она обязательно случится).

Я не понял подход, как это должно работать с 4 младшими битами и дополнительным time ?

Опустим переменную состояние. Оставим только время time32_t

в потоке th1 оно должно обновляться на каждой итерации цикла (или например с интервалом в 1-20 секунду) (это имело бы смысл если бы операции > И = сильно различались по времени в пользу >. Но они же по сути с одинаковой скоростью выполняются, так смысл усложнять ?)
Код

while true
do_something()
if(CurTime - lastTime > 10 sec)
     a.time = CurTime

в потоке th2 каждый 300 миллисекунд считывается a.time. 

Куда втыкается 
Код

  time_t local_time = (::time() & ~0xFFF) | static_cast<time_t>(local_data >> 4) ;

и зачем ?

Автор: volatile 21.5.2012, 23:20
Цитата(cupper @  21.5.2012,  08:39 Найти цитируемый пост)
Куда втыкается 
  time_t local_time = (::time() & ~0xFFF) | static_cast<time_t>(local_data >> 4) ;
и зачем ? 


Это вопрос ко мне?

Автор: cupper 22.5.2012, 16:40
Цитата(volatile @ 21.5.2012,  23:20)
Цитата(cupper @  21.5.2012,  08:39 Найти цитируемый пост)
Куда втыкается 
  time_t local_time = (::time() & ~0xFFF) | static_cast<time_t>(local_data >> 4) ;
и зачем ? 


Это вопрос ко мне?

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

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)