Wie starte ich Threads in normalem C?

Lesezeit: 6 Minuten

Ich habe fork() in C verwendet, um einen anderen Prozess zu starten. Wie starte ich einen neuen Thread?

  • Es hängt von der Plattform ab. So geht’s unter Windows: http://msdn.microsoft.com/en-us/library/ms682453.aspx

    – Eric Z Bart

    11. September 2008 um 15:08 Uhr

  • Wie in der von Ihnen verlinkten CreateThread-Dokumentation erläutert, sollte _beginthreadex und nicht CreateThread verwendet werden, wenn das Programm die C-Laufzeitbibliothek verwendet.

    – ChrisN

    11. September 2008 um 15:24 Uhr

  • Danke, das war mir nicht aufgefallen. Woher wissen Sie, ob Sie die CRT verwenden? Ich dachte, alles benutzt es.

    – Eric Z Bart

    11. September 2008 um 16:19 Uhr

  • Sie können den Compiler anweisen, die Standardbibliotheken nicht einzuschließen. Dann können Sie die CRT nicht versehentlich verwenden und Sie können die Win32-Funktionen vollständig verwenden.

    – Zan Luchs

    6. April 2010 um 3:59 Uhr

Da Sie fork() erwähnt haben, gehe ich davon aus, dass Sie sich in diesem Fall auf einem Unix-ähnlichen System befinden POSIX-Threads (normalerweise als pthreads bezeichnet) sind das, was Sie verwenden möchten.

Insbesondere ist pthread_create() die Funktion, die Sie zum Erstellen eines neuen Threads benötigen. Seine Argumente sind:

int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr, void *
   (*start_routine)(void *), void * arg);

Das erste Argument ist der zurückgegebene Zeiger auf die Thread-ID. Das zweite Argument sind die Thread-Argumente, die NULL sein können, es sei denn, Sie möchten den Thread mit einer bestimmten Priorität starten. Das dritte Argument ist die vom Thread ausgeführte Funktion. Das vierte Argument ist das einzelne Argument, das an die Thread-Funktion übergeben wird, wenn sie ausgeführt wird.

  • Hier ist ein Beispiel zur Verwendung: timmurphy.org/2010/05/04/…

    – gewaltig

    18. Juli 2013 um 11:25 Uhr

  • Zählen POSIX-Threads jetzt nicht als “einfaches C”, da der C11-Standard sie definiert?

    – Hydronium

    6. Oktober 2013 um 18:50 Uhr

  • Während die C11-Multithreading-API ist schwer beeinflusst von POSIX-Threads, sind sie nicht identisch. Daher erscheint es mir nicht richtig zu sagen, dass POSIX-Threads “einfach C” sind.

    – Ben Ylvisaker

    27. September 2014 um 20:49 Uhr

AFAIK, ANSI C definiert kein Threading, aber es sind verschiedene Bibliotheken verfügbar.

Wenn Sie Windows verwenden, verknüpfen Sie es mit msvcrt und verwenden Sie _beginthread oder _beginthreadex.

Wenn Sie auf anderen Plattformen laufen, sehen Sie sich die pthreads-Bibliothek an (ich bin sicher, dass es auch andere gibt).

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

C11-Gewinde + C11 atomic_int

Zu glibc 2.28 hinzugefügt. Getestet in Ubuntu 18.10 amd64 (kommt mit glic 2.28) und Ubuntu 18.04 (kommt mit glibc 2.27) durch Kompilieren von glibc 2.28 aus der Quelle: Mehrere glibc-Bibliotheken auf einem einzigen Host

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

Haupt c

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

atomic_int atomic_counter;
int non_atomic_counter;

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

int main(void) {
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], mythread, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("atomic     %d\n", atomic_counter);
    printf("non-atomic %d\n", non_atomic_counter);
}

GitHub-Upstream.

Kompilieren und ausführen:

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

Mögliche Ausgabe:

atomic     10000
non-atomic 4341

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

Siehe auch: Wie führt man ein atomares Inkrement durch und ruft in C ab?

Demontageanalyse

Zerlegen mit:

gdb -batch -ex "disassemble/rs mythread" main.out

enthält:

17              ++non_atomic_counter;
   0x00000000004007e8 <+8>:     83 05 65 08 20 00 01    addl   $0x1,0x200865(%rip)        # 0x601054 <non_atomic_counter>

18              __atomic_fetch_add(&atomic_counter, 1, __ATOMIC_SEQ_CST);
   0x00000000004007ef <+15>:    f0 83 05 61 08 20 00 01 lock addl $0x1,0x200861(%rip)        # 0x601058 <atomic_counter>

Wir sehen also, dass das atomare Inkrement auf Befehlsebene mit dem erfolgt f0 Präfix sperren.

Mit aarch64-linux-gnu-gcc 8.2.0 erhalten wir stattdessen:

11              ++non_atomic_counter;
   0x0000000000000a28 <+24>:    60 00 40 b9     ldr     w0, [x3]
   0x0000000000000a2c <+28>:    00 04 00 11     add     w0, w0, #0x1
   0x0000000000000a30 <+32>:    60 00 00 b9     str     w0, [x3]

12              ++atomic_counter;
   0x0000000000000a34 <+36>:    40 fc 5f 88     ldaxr   w0, [x2]
   0x0000000000000a38 <+40>:    00 04 00 11     add     w0, w0, #0x1
   0x0000000000000a3c <+44>:    40 fc 04 88     stlxr   w4, w0, [x2]
   0x0000000000000a40 <+48>:    a4 ff ff 35     cbnz    w4, 0xa34 <mythread+36>

Die atomare Version hat also tatsächlich eine cbnz Schleife, die bis zum läuft stlxr Shop gelingen. Beachten Sie, dass ARMv8.1 all dies mit einer einzigen LDADD-Anweisung erledigen kann.

Dies ist analog zu dem, was wir mit C++ erhalten std::atomic: Was genau ist std::atomic?

Benchmark

MACHEN. Erstellen Sie einen Benchmark, um zu zeigen, dass Atomic langsamer ist.

POSIX-Threads

Haupt c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <stdlib.h>
#include <pthread.h>

enum CONSTANTS {
    NUM_THREADS = 1000,
    NUM_ITERS = 1000
};

int global = 0;
int fail = 0;
pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER;

void* main_thread(void *arg) {
    int i;
    for (i = 0; i < NUM_ITERS; ++i) {
        if (!fail)
            pthread_mutex_lock(&main_thread_mutex);
        global++;
        if (!fail)
            pthread_mutex_unlock(&main_thread_mutex);
    }
    return NULL;
}

int main(int argc, char **argv) {
    pthread_t threads[NUM_THREADS];
    int i;
    fail = argc > 1;
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_create(&threads[i], NULL, main_thread, NULL);
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_join(threads[i], NULL);
    assert(global == NUM_THREADS * NUM_ITERS);
    return EXIT_SUCCESS;
}

Kompilieren und ausführen:

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

Der erste Durchlauf funktioniert einwandfrei, der zweite schlägt wegen fehlender Synchronisation fehl.

Es scheint keine POSIX-standardisierten atomaren Operationen zu geben: UNIX Portable Atomic Operations

Getestet auf Ubuntu 18.04. GitHub-Upstream.

GCC __atomic_* Einbauten

Für diejenigen, die kein C11 haben, können Sie mit dem atomare Inkremente erreichen __atomic_* GCC-Erweiterungen.

Haupt c

#define _XOPEN_SOURCE 700
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>

enum Constants {
    NUM_THREADS = 1000,
};

int atomic_counter;
int non_atomic_counter;

void* mythread(void *arg) {
    (void)arg;
    for (int n = 0; n < 1000; ++n) {
        ++non_atomic_counter;
        __atomic_fetch_add(&atomic_counter, 1, __ATOMIC_SEQ_CST);
    }
    return NULL;
}

int main(void) {
    int i;
    pthread_t threads[NUM_THREADS];
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_create(&threads[i], NULL, mythread, NULL);
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_join(threads[i], NULL);
    printf("atomic     %d\n", atomic_counter);
    printf("non-atomic %d\n", non_atomic_counter);
}

Kompilieren und ausführen:

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

Ausgabe und generierte Assembly: wie im Beispiel „C11-Threads“.

Getestet in Ubuntu 16.04 amd64, GCC 6.4.0.

pthreads ist ein guter Anfang, schau mal hier

Threads sind nicht Teil des C-Standards, daher besteht die einzige Möglichkeit, Threads zu verwenden, darin, eine Bibliothek zu verwenden (z API)

Benutzeravatar von Jay Conrod
Jay Conrod

Probier das aus Gewinde (POSIX-Thread)-Bibliothek.

1412340cookie-checkWie starte ich Threads in normalem C?

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

Privacy policy