Locklock читатель / писатель - PullRequest
2 голосов
/ 29 июля 2010

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

// 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 ]

3 голосов
/ 29 июля 2010

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

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

2 голосов
/ 29 июля 2010

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

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

Как правило, я бы решил проблему следующим образом:

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

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

Атомарные и т. Д. Операции на windows / 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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...