Wie verwende ich atomare Variablen in C?

Lesezeit: 5 Minuten

Benutzeravatar von lppier
besser

Ich muss eine atomare Variable in C verwenden, da auf diese Variable über verschiedene Threads zugegriffen wird. Ich will keine Race-Condition.

Mein Code läuft auf CentOS. Welche Möglichkeiten habe ich?

  • en.wikipedia.org/wiki/Volatile_variable Auch das kann lesenswert sein: pages.cs.wisc.edu/~remzi/OSTEP/threads-sema.pdf

    – Ricky Mutschlechner

    15. August 2014 um 1:02 Uhr


  • Es sieht so aus, als wären pthreads für CentOS verfügbar, sodass Sie Mutex verwenden könnten, um den Zugriff auf Dinge zu synchronisieren.

    – Sergej Kalinitschenko

    15. August 2014 um 1:08 Uhr

  • Der Typ volatile sig_atomic_t hat garantiert atomaren Zugriff

    – MM

    15. August 2014 um 1:58 Uhr

  • @RickyMutschlechner: volatile MACHT DAS NICHT. Bitte lesen Sie den Wikipedia-Artikel, auf den Sie verlinkt haben: “Operationen auf flüchtigen Variablen sind nicht atomar …”

    – Dietrich Ep

    15. August 2014 um 2:01 Uhr


  • @RickyMutschlechner: Sie können ein Spin-Lock nicht einfach mit implementieren volatile, da der Prozessor IO neu ordnet, auch wenn der Compiler dies nicht tut. Die Frage sagt auch “will keine Race-Bedingung” und volatile ist einfach nicht das richtige Werkzeug für den Job, wenn Sie sich Sorgen um die Rennbedingungen machen.

    – Dietrich Ep

    15. August 2014 um 3:41 Uhr

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

C11 atomare Primitive

http://en.cppreference.com/w/c/language/atomic

_Atomic const int * p1;  // p is a pointer to an atomic const int
const atomic_int * p2;   // same
const _Atomic(int) * p3; // same

Hinzugefügt in glibc 2.28. Getestet in Ubuntu 18.04 (glibc 2.27) durch Kompilieren von glibc aus der Quelle: Mehrere glibc-Bibliotheken auf einem einzigen Host Später auch getestet auf Ubuntu 20.04, glibc 2.31.

Beispiel übernommen aus: https://en.cppreference.com/w/c/language/atomic

Haupt c

#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>

atomic_int acnt;
int cnt;

int f(void* thr_data)
{
    (void)thr_data;
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
        // for this example, relaxed memory order is sufficient, e.g.
        // atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
    }
    return 0;
}

int main(void)
{
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);

    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

Kompilieren und ausführen:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out

Mögliche Ausgabe:

The atomic counter is 10000
The non-atomic counter is 8644

Der nicht-atomare Zähler ist sehr wahrscheinlich kleiner als der atomare, da der Zugriff über Threads hinweg auf die nicht-atomare Variable sehr schnell erfolgt.

Disassemblierungsanalyse unter: Wie starte ich Threads in normalem C?

Wenn Sie GCC auf Ihrer CentOS-Plattform verwenden, können Sie die __atomic eingebaute Funktionen.

Besonders interessant könnte diese Funktion sein:

— Eingebaute Funktion: bool __atomic_always_lock_free (size_t size, void *ptr)

Diese integrierte Funktion gibt true zurück, wenn Objekte von size Bytes generieren immer lock-freie atomare Anweisungen für die Zielarchitektur. size muss in eine Kompilierzeitkonstante aufgelöst werden und das Ergebnis wird ebenfalls in eine Kompilierzeitkonstante aufgelöst.

ptr ist ein optionaler Zeiger auf das Objekt, das verwendet werden kann, um die Ausrichtung zu bestimmen. Ein Wert von 0 gibt an, dass eine typische Ausrichtung verwendet werden sollte. Der Compiler kann diesen Parameter auch ignorieren.

      if (_atomic_always_lock_free (sizeof (long long), 0))

Benutzeravatar von Eric Eberhard
Erich Eberhard

Ich werde meine zwei Cent hineinwerfen, falls jemand davon profitiert. Atomare Operationen sind ein großes Problem in Linux. Ich habe einmal gatomic.h verwendet, nur um festzustellen, dass es weg ist. Ich sehe alle möglichen atomaren Optionen mit fragwürdiger Zuverlässigkeit oder Verfügbarkeit – und ich sehe, dass sich die Dinge ständig ändern. Sie können mit Tests komplex sein, die je nach Betriebssystemebene, Prozessor usw. erforderlich sind. Sie können einen Mutex verwenden – nicht nur komplex durch furchtbar langsam.

Obwohl dies in Threads möglicherweise nicht ideal ist, funktioniert dies hervorragend für atomare Operationen auf Shared-Memory-Variablen. Es ist einfach und funktioniert auf jedem Betriebssystem und Prozessor und jeder Konfiguration, die dem Mann (oder der Frau) bekannt ist, absolut zuverlässig, einfach zu programmieren und wird immer funktionieren.

Jeder Code kann mit einem einfachen Primitiv – einem Semaphor – atomar gemacht werden. Es ist etwas, das wahr/falsch, 1/0, ja/nein, gesperrt/entsperrt ist – binär.

Sobald Sie die Semaphore eingerichtet haben:

set semaphore   //must be atomic

Machen Sie den ganzen Code, den Sie mögen, der atomar sein wird, da die Semaphore für Sie blockieren wird

release semaphore  //must be atomic

Relativ geradlinig, mit Ausnahme der Zeilen “muss atomar sein”.

Es stellt sich heraus, dass Sie Ihren Semaphoren ganz einfach eine Nummer zuweisen (ich verwende eine Definition, damit sie einen Namen wie “#define OPEN_SEM 1” und “#define “CLASS_SEM 2” usw. haben.

Finden Sie Ihre größte Zahl heraus und öffnen Sie bei der Initialisierung Ihres Programms eine Datei in einem Verzeichnis (ich verwende eine nur für diesen Zweck). Wenn nicht vorhanden, erstellen Sie es:

if (ablockfd < 0) {         //ablockfd is static in case you want to 
                            //call it over and over           
    char *get_sy_path();                
    char lockname[100];                 

    strcpy(lockname, get_sy_path());    
    strcat(lockname, "/metlock");       
    ablockfd = open(lockname, O_RDWR);
    //error code if ablockfd bad
}

Nun, um ein Semaphor zu erhalten:

Verwenden Sie nun Ihre Semaphornummer, um einen “Datensatz” in Ihrer Datei mit der Länge von einem Byte zu “sperren”. Hinweis: Die Datei belegt niemals tatsächlich Speicherplatz auf der Festplatte, und es findet keine Festplattenoperation statt.

//sem_id is passed in and is set from OPEN_SEM or CLASS_SEM or whatever you call your semaphores.

lseek(ablockfd, sem_id, SEEK_SET); //seeks to the bytes in file of 
                                   //your semaphore number
result = lockf(ablockfd, F_LOCK, 1);   

if (result != -1) {                    
   //got the semaphore
} else {
    //failed
}

Um zu testen, ob die Semaphore gehalten wird:

result = lockf(ablockfd, F_TEST, 1);  //after same lseek

Um die Semaphore freizugeben:

result = lockf(ablockfd, F_ULOCK, 1);   //after same lseek

Und all die anderen Dinge, die Sie mit lockf tun können – blockieren/nicht blockieren usw.

Hinweis – dies ist VIEL schneller als ein Mutex, es verschwindet, wenn der Prozess stirbt (eine gute Sache), einfach zu codieren, und ich kenne kein Betriebssystem mit einem Prozessor mit einer beliebigen Anzahl von ihnen oder einer Anzahl von Kernen, die dies nicht atomar können einen Datensatz sperren … so einfacher Code, der einfach funktioniert. Die Datei existiert nie wirklich (keine Bytes, aber im Verzeichnis), scheint keine praktische Begrenzung dafür zu sein, wie viele Sie haben können. Ich habe dies jahrelang auf Maschinen ohne einfache atomare Lösungen verwendet.

1432590cookie-checkWie verwende ich atomare Variablen in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy