7

Więc używamy wersji boost, która jest całkiem stara, a do aktualizacji potrzebuję do atomowej operacji CAS w C++ dla mojego kodu. (Nie jesteśmy jeszcze przy użyciu C++ 0x albo)Porównaj i zamień w C++

I stworzył następującą funkcję 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__ ("lock\n\t" 
      "cmpxchg %2,%0" 
      : "+m"(*mem), "+a"(prev) 
       : "r"(with) 
       : "cc"); 

    return prev; 
} 

Mój kod, który wykorzystuje funkcję nieco w następujący sposób:

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(); 
} 

My test tego kodu spawnuje 10 wątków i sygnalizuje wszystkim, aby wywołać funkcję z tą samą zmienną masterDeserialize.

To działa dobrze przez większość czasu, ale raz na kilka tysięcy - kilka milionów testowych iteracji 2 wątki mogą wejść na ścieżkę nabycia blokady MASTER.

Nie jestem pewien, jak to jest możliwe, ani jak tego uniknąć.

Próbowałem użyć zapory pamięci przed resetowaniem masterDeserialize, myśląc, że cpu OOO może mieć wpływ, ale to nie ma wpływu na wynik.

Oczywiście działa na komputerze z wieloma rdzeniami i jest skompilowany w trybie debugowania, więc GCC nie powinien zmieniać kolejności wykonania dla optymalizacji.

Jakieś sugestie co do tego, co jest nie tak?

EDYCJA: Próbowałem użyć prymitywu gcc zamiast kodu zespołu, otrzymałem ten sam wynik.

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

używam na multi rdzeniem, multi maszynie procesora, ale jest to maszyna wirtualna, to możliwe, że takie zachowanie jest spowodowane jakoś przez VM?

+0

Skąd wiadomo, że w obu przypadkach jednocześnie wprowadzają tę samą ścieżkę kodu? Twoje wyjście do debugowania nie jest chronione przez żadną blokadę. Ponadto, tylko ze względu na kompletność i ma znaczenie tylko na słabo uporządkowanych architekturach (i wygląda to podobnie do x86/x86-64): Ustawienie 'masterDeserialize' powinno również być operacją atomową. – JustSid

+0

1. Ścieżka kodu: wszystkie wątki, które wywołują te funkcje, są ładowane i czekają na jakąś flagę, która jest współdzielona między nimi. Ustawiam flagę na true z wątku kontrolnego. 2. To jest architektura x86-64, kompiluję jako 32 bit dla tego programu. 3. Po prostu ustawienie masterDeserialize jest atomowe, ale chcę, aby tylko jeden wątek znalazł się w ścieżce kodu masterDeserialize == true jednocześnie, dlatego samo ustawienie to za mało - muszę przetestować starą wartość, aby każdy wątek wiedzieć, którą drogę wybrać. –

+0

Warto również wspomnieć, że gdy jeden wątek wejdzie w masterDeserialize = true, żaden inny wątek w tej iteracji nie powinien tego robić. (sekcja kodu wykonuje pewną deserializację i ustawia wskaźnik na wartość inną niż null, gdy wskaźnik ten nie jest pusty, przepływ nie wchodzi w życie, Inną rzeczą wartą wspomnienia jest to, że informacje debugowania służą do debugowania problemu w kod, który jest wystawiany przez takie dziwne zachowanie –

Odpowiedz

1

Nie więcej niż dwie, ale jakakolwiek liczba wątków może teoretycznie stać się "wzorcami" w tym kodzie. Problem polega na tym, że wątek, który wziął ścieżkę główną po zakończeniu ustawia zmienną masterDeserialize z powrotem na 0, umożliwiając ponowne "nabycie" przez wątek, który może dotrzeć bardzo późno do CAS (na przykład z powodu prewencji).

Naprawienie jest proste - dodaj trzeci stan (z np. Wartością 2) do tej flagi w celu oznaczenia "master został zakończony", i użyj tego stanu (zamiast początkowego stanu 0) na końcu ścieżka mistrza do sygnalizowania swojej pracy jest zakończona. Dlatego tylko jeden z wątków, które wywołują myFunc, może kiedykolwiek zobaczyć 0, co daje gwarancję, której potrzebujesz. Aby ponownie użyć flagi, musisz ją ponownie zainicjować w sposób jawny na 0.

+0

Dzięki Alexey! Czuję się naprawdę głupio, że tęsknię za tym. –