Locklock читатель / писатель
У меня есть некоторые данные, которые читаются и обновляются несколькими потоками. И чтение, и запись должны быть атомарными. Я думал сделать это так:
// 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);
}
Данные защищены путем кражи указателя на них при каждом чтении и записи, что должно сделать их потокобезопасными, но для каждого доступа требуются две взаимосвязанные инструкции. Будет много и операций чтения, и записи, и я не могу заранее сказать, будет ли больше операций чтения или записи.
Можно ли сделать это более эффективно, чем это? Это также блокирует при чтении, но, поскольку вполне возможно иметь больше записей, чем читает, нет смысла оптимизировать чтение, если только это не налагает штраф на запись.
Я думал о чтениях, получая указатель без блокированной инструкции (вместе с порядковым номером), копируя данные, а затем получая способ сообщить, изменился ли порядковый номер, и в этом случае он должен повторить попытку. Однако для этого потребуются некоторые барьеры памяти, и я не знаю, может ли это улучшить скорость.
----- РЕДАКТИРОВАТЬ -----
Спасибо всем, отличные комментарии! На самом деле я не запускал этот код, но позже попробую сравнить текущий метод с критическим разделом (если у меня будет время). Я все еще ищу оптимальное решение, поэтому я вернусь к более сложным комментариям позже. Еще раз спасибо!
2 ответа
То, что вы написали, по сути является спин-блокировкой. Если вы собираетесь это сделать, то вы можете просто использовать мьютекс, такой как boost:: mutex. Если вы действительно хотите спинлок, используйте системный или из библиотеки, а не свой собственный.
Другие возможности включают в себя некоторую форму копирования при записи. Сохраняйте структуру данных по указателю и просто читайте указатель (атомарно) со стороны чтения. На стороне записи затем создайте новый экземпляр (при необходимости копируя старые данные) и атомарно поменяйте местами указатель. Если для записи требуется старое значение и имеется более одного записывающего, вам нужно будет либо выполнить цикл сравнения-обмена, чтобы убедиться, что значение не изменилось с момента его чтения (остерегайтесь проблем с ABA), либо мьютекс для писатели. Если вы делаете это, то вам нужно быть осторожным в том, как вы управляете памятью - вам нужен какой-то способ восстановить экземпляры данных, когда ни один поток не ссылается на них (но не раньше).
Есть несколько способов решить эту проблему, особенно без мьютексов или механизмов блокировки. Проблема в том, что я не уверен, каковы ограничения в вашей системе.
Помните, что атомарные операции - это то, что часто перемещается компиляторами в C++.
Вообще я бы решил проблему следующим образом:
Множественный производитель-один-потребитель, имеющий по одному производителю-одному-потребителю на поток записи. Каждый поток записывает в свою очередь. Единый потребительский поток, который собирает полученные данные и сохраняет их в хранилище данных с одним потребителем и несколькими читателями. Реализация для этого - большая работа, и она рекомендуется только в том случае, если вы работаете с критичным по времени приложением и у вас есть время для этого решения.
Есть больше вещей, чтобы прочитать об этом, так как реализация зависит от платформы:
Атомарные и т. Д. Операции на windows / xbox360: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx
Многопоточный однопроизводный-однопользовательский без замков:
http://www.codeproject.com/KB/threads/LockFree.aspx
Что такое "volatile" на самом деле и может быть использовано для:
http://www.drdobbs.com/cpp/212701484
Херб Саттер написал хорошую статью, которая напоминает вам об опасности написания такого кода: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2