У меня есть некоторые данные, которые читаются и обновляются несколькими потоками. И чтение, и запись должны быть атомарными. Я думал сделать это так:
// 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);
}
Данные защищены путем кражи указателя на них для каждого чтения и записи, что должно сделать их потокобезопасными, но для каждого доступа требуются две взаимосвязанные инструкции. Будет много операций как чтения, так и записи, и я не могу заранее сказать, будет ли больше операций чтения или записи.
Можно ли сделать это более эффективно, чем это? Это также блокирует при чтении, но поскольку вполне возможно иметь больше записей, чем читает, нет смысла оптимизировать чтение, если только это не налагает штраф на запись.
Я думал о чтении, получающем указатель без блокированной инструкции (вместе с порядковым номером), копирующем данные, а затем имеющем способ сообщить, изменился ли порядковый номер, и в этом случае следует повторить попытку. Однако для этого потребуются некоторые барьеры памяти, и я не знаю, может ли это улучшить скорость.
----- РЕДАКТИРОВАТЬ -----
Спасибо всем, отличные комментарии! На самом деле я не запускал этот код, но позже попробую сравнить текущий метод с критическим разделом (если у меня будет время). Я все еще ищу оптимальное решение, поэтому я вернусь к более сложным комментариям позже. Еще раз спасибо!