Сравнение и обмен в C++


Итак, мы используем версию boost, которая довольно стара на данный момент, и до обновления мне нужно иметь атомарную операцию CAS в C++ для моего кода. (мы еще не используем C++0x)

Я создал следующую функцию cas:

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp)
{
    uint32_t prev = cmp;
    // This version by Mans Rullgard of Pathscale
    __asm__ __volatile__ ( "locknt"
            "cmpxchg %2,%0"
            : "+m"(*mem), "+a"(prev)
              : "r"(with)
                : "cc");

    return prev;
}

Мой код, который использует эту функцию, выглядит примерно следующим образом:

void myFunc(uint32_t &masterDeserialize )
{
    std::ostringstream debugStream;

    unsigned int tid = pthread_self();
    debugStream << "myFunc, threadId: " << tid << " masterDeserialize= " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl;

    // memory fence
    __asm__ __volatile__ ("" ::: "memory");
    uint32_t retMaster = CAS(&masterDeserialize, 1, 0);
    debugStream << "After cas, threadid = " << tid << " retMaster = " << retMaster << " MasterDeserialize = " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl;
    if(retMaster != 0) // not master deserializer.
    {
       debugStream << "getConfigurationRowField, threadId: " << tid << " NOT master.  retMaster = " << retMaster << std::endl;

       DO SOMETHING...
    }
    else
    {
        debugStream << "getConfigurationRowField, threadId: " << tid << " MASTER. retMaster = " << retMaster << std::endl;

        DO SOME LOGIC  

        // Signal we're done deserializing.
        masterDeserialize = 0;
    }
    std::cout << debugStream.str();
}

Мой тест этого кода порождает 10 потоков и сигнализирует всем им, чтобы вызвать функцию стой же переменной masterDeserialize .

Это хорошо работает большую часть времени, но раз в пару тысяч - пару миллионов тестовых итераций 2 потока могут одновременно войти в путь получения мастер-замка.

Я не знаю, как это возможно, или как этого избежать.

Я попытался использовать забор памяти перед сбросом masterDeserialize, думая, что cpu OOO может повлиять, но это никак не влияет на результат.

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

Какие-либо предложения относительно того, что не так с вышеизложенным?

Править: Я попробовал использовать GCC примитив вместо ассемблерного кода, получил тот же результат.

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp)
{
    return __sync_val_compare_and_swap(mem, cmp, with);
}

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

1 7

1 ответ:

Не только два, но и любое количество потоков теоретически могут стать "хозяевами" в этом коде. Проблема заключается в том, что поток, который занял главный путь после завершения, устанавливает переменную masterDeserialize обратно в 0, что делает возможным "приобретение" снова потоком, который может прибыть в CAS очень поздно (например, из-за упреждающего действия).

Исправление на самом деле просто-добавьте третье состояние (например, со значением 2) к этому флагу, чтобы означать "мастер завершил", и используйте это состояние (вместо начального состояния 0) в конце пути мастера, чтобы сигнализировать, что его работа выполнена. Таким образом, только один из потоков, вызывающих myFunc, может видеть 0, что дает вам необходимую гарантию. Чтобы повторно использовать флаг, необходимо явно повторно инициализировать его до 0.