2009-05-16 11 views
5

Mam dość konkretne pytanie dotyczące współbieżnego programowania w C. Zrobiłem sporo badań na ten temat, ale widziałem kilka sprzecznych odpowiedzi, więc mam nadzieję na pewne wyjaśnienia. Mam program, który jest coś jak poniżej (przepraszam za podłużnego bloku kodu):równoczesny dostęp zmienny w c

typedef struct { 
    pthread_mutex_t mutex; 
    /* some shared data */ 
    int eventCounter; 
} SharedData; 

SharedData globalSharedData; 

typedef struct { 
    /* details unimportant */ 
} NewData; 

void newData(NewData data) { 
    int localCopyOfCounter; 

    if (/* information contained in new data triggers an 
     event */) { 
    pthread_mutex_lock(&globalSharedData.mutex); 
    localCopyOfCounter = ++globalSharedData.eventCounter; 
    pthread_mutex_unlock(&globalSharedData.mutex); 
    } 
    else { 
    return; 
    } 

    /* Perform long running computation. */ 

    if (localCopyOfCounter != globalSharedData.eventCounter) { 
    /* A new event has happened, old information is stale and 
     the current computation can be aborted. */ 
    return; 
    } 

    /* Perform another long running computation whose results 
    depend on the previous one. */ 

    if (localCopyOfCounter != globalSharedData.eventCounter) { 
    /* Another check for new event that causes information 
     to be stale. */ 
    return; 
    } 

    /* Final stage of computation whose results depend on two 
    previous stages. */ 
} 

Jest basen wątków obsługujących połączenia dla danych przychodzących, więc wielu wystąpień newData mogą być uruchomione w tym samym czasie . W środowisku wieloprocesorowym występują dwa problemy, o których wiem, że część kodu związana z obsługą kontrułowa jest poprawna: uniemożliwienie kompilatorowi buforowania współużytkowanej kopii licznika w rejestrze, aby inne wątki go nie widziały i wymuszenie CPU, aby zapisać pamięć licznika do pamięci w odpowiednim czasie, aby inne wątki mogły ją zobaczyć. Wolałbym nie używać wywołania synchronizacyjnego wokół czeków licznika, ponieważ częściowy odczyt wartości licznika jest akceptowalny (wygeneruje wartość inną niż kopia lokalna, co powinno wystarczyć do stwierdzenia, że ​​zdarzenie miało miejsce). Czy byłoby wystarczające zadeklarowanie pola eventCounter w SharedData, aby było niestabilne, czy też muszę tu zrobić coś innego? Czy istnieje lepszy sposób na poradzenie sobie z tym?

Odpowiedz

2

Niestety, standard C mówi bardzo niewiele o współbieżności. Jednak większość kompilatorów (gcc i msvc, tak czy inaczej) będzie traktować lotny odczyt jak gdyby posiadający acquire semantics - zmienna lotna zostanie przeładowana z pamięci przy każdym dostępie. Jest to pożądane, twój kod taki jak teraz może skończyć się porównaniem wartości buforowanych w rejestrach. Nie zdziwiłbym się nawet, gdyby oba porównania zostały zoptymalizowane.

Tak więc odpowiedź brzmi "tak", spraw, by eventCounter był ulotny. Ewentualnie, jeśli nie chcesz zbytnio ograniczać kompilatora, możesz użyć następującej funkcji do wykonania odczytów eventCounter.

int load_acquire(volatile int * counter) { return *counter; } 

if (localCopy != load_acquire(&sharedCopy)) 
    // ... 
0

zapobiegania kompilator z buforowania lokalna kopia licznik w rejestrze więc inne wątki nie widzą go

lokalnej kopii licznik jest „lokalny”, stworzony na wykonanie stos i widoczne tylko dla bieżącego wątku. Każdy inny wątek działa na innym stosie i ma własną lokalną zmienną licznika (bez współbieżności).

Twój globalny licznik powinien być uznany za niestabilny, aby uniknąć optymalizacji rejestru.

+0

Masz całkowitą pewność co do lokalnej kopii, właśnie naprawiłem ten błąd w moim tekście. – user98166

0

Można również użyć zestawu kodowanego ręcznie lub kompilatora intrinsics, który będzie garuntee kontroli atomowej przeciwko muteks, mogą również atomowo ++ i - twój licznik.

volatile to useless w tych dniach, w przeważającej części, powinieneś spojrzeć na bariery pamięci, które są innym procesorem niskiego poziomu, aby pomóc w rywalizacji wielordzeniowej.

Jednak najlepszą radą, jaką mogę dać, byłby dla ciebie, aby ugrzęźć w różnych zarządzanych i natywnych wielordzeniowych bibliotekach wsparcia. Domyślam się, że niektóre ze starszych takich jak OpenMP lub MPI (bazujące na wiadomościach) wciąż kopią i ludzie będą mówić o tym, jak fajnie są ... jednak dla większości programistów, takich jak nowe, intelowskie interfejsy API TBB lub Microsoft's, właśnie wykopałem w górę tego kodu projektu article, najwyraźniej za pomocą cmpxchg8b, który jest niskiego poziomu trasy sprzętu, o którym wspomniałem na początku ...

Powodzenia.