2010-07-29 1 views
2

У меня есть некоторые данные, которые читаются и обновляются несколькими потоками. Оба чтения и записи должны быть атомарными. Я думал сделать это следующим образом:Lockless reader/writer

// Values must be read and updated atomically 
struct SValues 
{ 
    double a; 
    double b; 
    double c; 
    double d; 
}; 

class Test 
{ 
public: 
    Test() 
    { 
     m_pValues = &m_values; 
    } 

    SValues* LockAndGet() 
    { 
     // Spin forver until we got ownership of the pointer 
     while (true) 
     { 
      SValues* pValues = (SValues*)::InterlockedExchange((long*)m_pValues, 0xffffffff); 
      if (pValues != (SValues*)0xffffffff) 
      { 
       return pValues; 
      } 
     } 
    } 

    void Unlock(SValues* pValues) 
    { 
     // Return the pointer so other threads can lock it 
     ::InterlockedExchange((long*)m_pValues, (long)pValues); 
    } 

private: 
    SValues* m_pValues; 
    SValues m_values; 
}; 

void TestFunc() 
{ 
    Test test; 

    SValues* pValues = test.LockAndGet(); 

    // Update or read values 

    test.Unlock(pValues); 
} 

Данных защищен путем кражи указателя на него для каждого чтение и запись, которые должны сделать его потокобезопасен, но это требует два ВЗАИМОСВЯЗАННЫХ инструкции для каждого доступа. Там будет много и чтения и записи, и я не могу сказать заранее, если будет больше чтений или больше записей.

Можете ли вы сделать это более эффективно? Это также блокируется при чтении, но поскольку вполне возможно иметь больше записей, то читает, что нет смысла оптимизировать чтение, если только оно не наносит штраф на запись.

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

----- EDIT -----

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

+0

Что такое проблема с использованием примитивов синхронизации потоков по умолчанию? – naivnomore

+0

Должен признаться, я просто предположил, что могу сделать это быстрее, чем это. 1) Здесь я показываю только один экземпляр, но на самом деле у меня будет, возможно, 10000 экземпляров этих защищенных записей данных, и это будет означать 10000 критических разделов. Но, возможно, это не проблема, я не знаю, я никогда не пробовал что-то подобное. 2) Я надеялся, что могу придумать что-то быстрее, чем критический раздел. Там может быть легко миллионы чтений/записей в секунду. И на личном уровне я просто подумал, что было бы здорово сделать так же быстро, как и по-человечески (возможно?). – Rabbit

+0

Windows CRITICAL_SECTION очень легкий, если только он не должен блокироваться. Я не думаю, что оживленные пользовательские потоки, подобные этому, - это ужасно хорошая идея - вы неявно сигнализируете планировщику, что у вас есть много дел, тогда как на самом деле все наоборот. –

ответ

3

То, что вы написали, по сути, является спин-блокировкой. Если вы собираетесь это сделать, тогда вы можете просто использовать мьютекс, например boost::mutex. Если вам действительно нужна спин-блокировка, используйте системный или один из библиотеки, а не свой собственный.

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

+0

Правда, это спин-блокировка. После небольшого исследования кажется, что спин-блокировка может быть реализована без необходимости блокировки инструкции при выходе и без записи ничего при вращении. Это может быть решение, которое я ищу. Сейчас я занят, но я вернусь к нему позже. – Rabbit

+0

Моим лучшим решением на данный момент является использование спин-блокировки для каждого указателя, который мне нужно защитить. Они используют только 4 байта. Его по существу то же, что и примерный код в этом вопросе, за исключением того, что спин-блокировка не будет использовать связанную команду во время вращения, и она использует инструкцию asm pause. – Rabbit

2

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

Помните, что атомарные операции - это то, что часто перемещается компиляторами на C++.

Вообще-то я бы решить эту проблему так:

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

Есть больше вещей, чтобы прочитать об этом, так как реализация конкретной платформы:

Atomic и т.д. операции на окнах/xbox360: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx

Многопоточный одного производителя и одним потребителем без замков :
http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

Что "летучий" на самом деле, и может быть использован для:
http://www.drdobbs.com/cpp/212701484

Herb Sutter написал хорошую статью, которая напоминает вам об опасности написания такого рода кода: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

+0

Другое хорошее решение для бесплатной очереди: http://www.drdobbs.com/architecture-and-design/210604448 – Simon

+0

И, пожалуйста, пожалуйста, обратите внимание на условия гонки. – Simon