векторное копирование и многопоточность: как обеспечить многократное чтение, даже если возможны случайные записи? - PullRequest
2 голосов
/ 30 января 2012
Pseudocode:
Function1_vector_copy () {
vectora = vectorb;
}
Function2_vector_search() {
find k in vectora;
}

Программа многопоточная. Хотя многие потоки могут выполнять поиск, векторное копирование выполняется только одним потоком, но иногда. Проблема в том, что может, vector_search не должен потерпеть неудачу. Vector_copy может быть отложено, но vector_search не должно быть. Это без задержек, без ошибок. Проблема заключается в том, что переменная общего доступа vectora должна быть постоянной, чтобы вектор_поиску вообще не вызывал ошибки. Каков оптимальный способ достижения этого?

Редактировать:

Использование ответа на другой вопрос

boost::shared_mutex _access;

Function1_vector_copy() {

  // get upgradable access
  boost::upgrade_lock lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock uniqueLock(lock);
  // now we have exclusive access

  vectora.swap(vectorb);
}

Function2_vector_search() {

  // get shared access
  boost::shared_lock lock(_access);

  // now we have shared access

  find k in vectora ;

}

Если поток с обновляемым владельцем пытается выполнить обновление, в то время как другие потоки имеют общее владение, попытка не удастся и поток будет блокироваться до тех пор, пока не будет приобретено исключительное право собственности. - подталкивание-документация

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

Ответы [ 2 ]

2 голосов
/ 31 января 2012

Я думаю, что идея mcmcc была не слишком далека, но вместо универсального std :: swap (), который будет выполнять векторное копирование, вы должны иметь возможность использовать std :: vector :: swap ().Функционально векторная своп-функция работает аналогично универсальной, но она намного быстрее, поскольку просто обменивается несколькими переменными указателя / размера и фактически не делает копий элементов.Таким образом, ваша блокировка будет доступна только для нескольких инструкций по сборке.

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

Недостатком второго решения является то, что оно определенно потребует больше памяти, и это немного непредсказуемо.Я полагаю, вы могли бы объявить свой «активный» векторный указатель volatile , который заставлял бы ЦП синхронизировать кэш при каждом чтении значения указателя, в противном случае есть некоторый недетерминированный элемент, в котором может быть несколько ядер ЦП.глядя на разные снимки основной памяти.Кроме того, без какой-либо синхронизации, как вы гарантируете, что другие потоки выполняются с вектором, который собирается сдуться?И если вы делаете какую-либо синхронизацию, вы возвращаетесь к блокировке RW.

Хотя я думаю, что вы могли бы получить вторую альтернативу для работы и с некоторой относительно стабильной настройкой ... вероятно.Моим первым инстинктом было бы использовать RW lock и vector :: swap ().

UPDATE: На самом деле, я просто перечитал спецификации и, похоже, STL специализируется на глобальном std :: swap.специально для контейнеров, включая std :: vector.Таким образом, даже при вызове глобальной функции сложность равна O (1).Ответ mcmcc работает.

Итак, мы остановили это предположение, я просто написал немного кода и прошел через него с помощью отладчика.Вот мой код приложения:

std::vector< int >      a, b;

for( int i = 0; i < 10; i++ )
{
    a.push_back( i );
    b.push_back( i * 10 );
}

std::swap( a, b );

и вот что находится внутри вызова глобальной универсальной версии std :: swap:

template<class _Ty,
class _Alloc> inline
void swap(vector<_Ty, _Alloc>& _Left, vector<_Ty, _Alloc>& _Right)
{   // swap _Left and _Right vectors
_Left.swap(_Right);
}

Как вы можете увидеть шаблон std :: swapФункция, которая в универсальном режиме выполняет функцию конструктора копирования, была специально предназначена для векторов.А внутри он просто делегирует всю работу std :: vector :: swap (), что, как мы установили, является более быстрым вариантом.

2 голосов
/ 31 января 2012
Function1_vector_copy () 
{
    VectorType tmp = vectorb;

    rwLock.acquireWrite();
    swap(tmp,vectora);
    rwLock.releaseWrite();
}

Остальное оставлено в качестве упражнения для читателя.

ОБНОВЛЕНИЕ: Обобщая комментарии ниже ...

Прежде всего я должен пояснить, что исходный вызов swap(...) выше был задуман как псевдокод, который не обязательно должен восприниматься буквально. Я не знал точно, что было VectorType (и технически до сих пор не знаю). Я предполагал, что вы намерены сделать своп () настолько эффективным, насколько это возможно. В конце концов, это был псевдокод ...

В любом случае, std::swap() специализируется для всех типов контейнеров в стандартной библиотеке C ++. Так что если VectorType на самом деле std::vector<UDT>, то swap(tmp,vectora); должен быть оптимальным и работать без изменений (полагаясь на поиск Кенига для разрешения символа).

...