web-dev-qa-db-de.com

Wie kann man C-Code "multithread" machen?

Ich habe eine Zahl-Crunching-Anwendung, die in C geschrieben wurde. Es ist eine Art Hauptschleife, die für jeden Wert zur Erhöhung der Werte von "i" eine Funktion aufruft, die einige Berechnungen durchführt. Ich habe über Multithreading gelesen, und ich denke darüber nach, ein wenig darüber zu lernen, in C. Ich frage mich, ob allgemeiner Code wie der von mir automatisch Multithreading sein könnte und wie.

Vielen Dank

P.D. Um eine Vorstellung von meinem Code zu bekommen, lassen Sie uns sagen, dass es ungefähr so ​​ist:

main(...)
for(i=0;i<=ntimes;i++)get_result(x[i],y[i],result[i]);

...

void get_result(float x,float y,float result){
  result=sqrt(log (x) + log (y) + cos (exp (x + y));
(and some more similar mathematical operations)
}
33
flow

Eine Alternative zum Multithread-Code wäre die Verwendung von pthreads (bietet eine genauere Kontrolle als OpenMP).

Angenommen, x, y & result sind globale Variablenarrays,

#include <pthread.h>

...

void *get_result(void *param)  // param is a dummy pointer
{
...
}

int main()
{
...
pthread_t *tid = malloc( ntimes * sizeof(pthread_t) );

for( i=0; i<ntimes; i++ ) 
    pthread_create( &tid[i], NULL, get_result, NULL );

... // do some tasks unrelated to result    

for( i=0; i<ntimes; i++ ) 
    pthread_join( tid[i], NULL );
...
}

(Kompilieren Sie Ihren Code mit gcc prog.c -lpthread)

18
user191776

Wenn die Aufgabe in hohem Maße parallelisierbar ist und Ihr Compiler modern ist, können Sie OpenMP ausprobieren. http://en.wikipedia.org/wiki/OpenMP

26
Novikov

Sie sollten sich hierzu openMP ansehen. Das C/C++ - Beispiel auf dieser Seite ähnelt Ihrem Code: https://computing.llnl.gov/tutorials/openMP/#SECTIONS

#include <omp.h>
#define N     1000

main ()
{

int i;
float a[N], b[N], c[N], d[N];

/* Some initializations */
for (i=0; i < N; i++) {
  a[i] = i * 1.5;
  b[i] = i + 22.35;
  }

#pragma omp parallel shared(a,b,c,d) private(i)
  {

  #pragma omp sections nowait
    {

    #pragma omp section
    for (i=0; i < N; i++)
      c[i] = a[i] + b[i];

    #pragma omp section
    for (i=0; i < N; i++)
      d[i] = a[i] * b[i];

    }  /* end of sections */

  }  /* end of parallel section */

}

Wenn Sie es vorziehen, openMP nicht zu verwenden, können Sie entweder pthreads oder clone/wait direkt verwenden.

Egal für welche Route Sie sich entscheiden, Sie teilen nur Ihre Arrays in Blöcke auf, die von jedem Thread verarbeitet werden. Wenn Ihre gesamte Verarbeitung rein rechnerisch ist (wie von Ihrer Beispielfunktion vorgeschlagen), sollten Sie nur so viele Threads verwenden, wie Sie über logische Prozessoren verfügen.

Das Hinzufügen von Threads für die parallele Verarbeitung ist mit einem gewissen Aufwand verbunden. Stellen Sie also sicher, dass Sie jedem Thread genügend Arbeit geben, um ihn auszugleichen. Normalerweise ist das der Fall, aber wenn jeder Thread nur eine Berechnung zu erledigen hat und die Berechnungen nicht so schwierig sind, können Sie die Geschwindigkeit verlangsamen. In diesem Fall können Sie immer weniger Threads als Prozessoren haben.

Wenn in Ihrer Arbeit einige IO vorhanden sind, stellen Sie möglicherweise fest, dass mehr Threads als Prozessoren ein Gewinn sind, da ein Thread blockiert und darauf wartet, dass einige IO einen anderen Thread beenden seine Berechnungen. Sie müssen jedoch vorsichtig sein, IO für dieselbe Datei in Threads.

9
nategoose

Wenn Sie hoffen, Parallelität für eine einzelne Schleife für ein wissenschaftliches Computing oder ähnliches bereitzustellen, ist OpenMP, wie @Novikov sagt, wirklich die beste Wahl. dafür wurde es entwickelt.

Wenn Sie den klassischeren Ansatz erlernen möchten, den Sie normalerweise in einer in C geschriebenen Anwendung sehen würden ... Auf POSIX möchten Sie pthread_create() et al. Ich bin nicht sicher, wie Ihr Hintergrund mit Parallelität in anderen Sprachen aussehen könnte, aber bevor Sie sich eingehend damit befassen, sollten Sie Ihre Synchronisationsprimitive (Mutexe, Semaphore usw.) ziemlich gut kennenlernen und verstehen, wann Sie wollen müssen sie verwenden. Dieses Thema könnte ein ganzes Buch oder eine Reihe von SO Fragen an sich selbst sein.

9
asveikau

Der C++ - Compiler von Intel ist in der Lage, Ihren Code automatisch zu paralellisieren. Es ist nur ein Compiler-Switch, den Sie aktivieren müssen. Es funktioniert jedoch nicht so gut wie OpenMP (dh, es gelingt nicht immer oder das resultierende Programm ist langsamer) . Von der Website von Intel: "Autoparallelisierung, die durch das parallele (Linux) ausgelöst wird * OS und Mac OS * X) oder/Qparallel (Windows * OS) identifiziert automatisch die Schleifenstrukturen, die Parallelität enthalten Der Compiler versucht beim Kompilieren automatisch, die Codefolgen für die parallele Verarbeitung in separate Threads zu zerlegen der Programmierer wird benötigt. "

3
darklon

Je nach Betriebssystem können Sie Posix-Threads verwenden. Sie können stattdessen stapelloses Multithreading mithilfe von Statusmaschinen implementieren. Es gibt ein wirklich gutes Buch mit dem Titel "Embedded Multitasking" von Keith E. Curtis. Es ist nur ein ordentlich zusammengestellter Satz von Case-Anweisungen. Funktioniert großartig, ich habe es für alles verwendet, von Apple Macs, Kaninchen-Halbleiter, AVR, PC.

Vali

3
ValiRossi

eine gute Übung für das Erlernen der gleichzeitigen Programmierung in einer beliebigen Sprache wäre das Arbeiten an einer Thread-Pool-Implementierung.
In diesem Muster erstellen Sie zuvor einige Threads. Diese Threads werden als Ressource behandelt. Ein Thread-Pool-Objekt/eine -Struktur wird verwendet, um diesen Threads eine benutzerdefinierte Aufgabe zur Ausführung zuzuweisen. Wenn die Aufgabe abgeschlossen ist, können Sie die Ergebnisse sammeln. Sie können den Thread-Pool als allgemeine Entwurfsmuster für Parallelität verwenden .. Die Hauptidee könnte ähnlich aussehen

#define number_of_threads_to_be_created 42
// create some user defined tasks
Tasks_list_t* task_list_elem = CreateTasks();
// Create the thread pool with 42 tasks
Thpool_handle_t* pool = Create_pool(number_of_threads_to_be_created);

// populate the thread pool with tasks
for ( ; task_list_elem; task_list_elem = task_list_elem->next) {
   add_a_task_to_thpool (task_list_elem, pool);
}
// kick start the thread pool
thpool_run (pool);

// Now decide on the mechanism for collecting the results from tasks list.
// Some of the candidates are:
// 1. sleep till all is done (naive)
// 2. pool the tasks in the list for some state variable describing that the task has
//    finished. This can work quite well in some situations
// 3. Implement signal/callback mechanism that a task can use to signal that it has 
//    finished executing.

Der Mechanismus zum Sammeln von Daten von Tasks und die Menge der im Pool verwendeten Threads sollten entsprechend Ihren Anforderungen und den Möglichkeiten der Hardware- und Laufzeitumgebung ausgewählt werden.
Beachten Sie bitte auch, dass dieses Muster nichts aussagt, wie Sie Ihre Aufgaben miteinander/außerhalb der Umgebung "synchronisieren" sollen. Auch die Fehlerbehandlung kann etwas schwierig sein (Beispiel: Was tun, wenn eine Aufgabe fehlschlägt). Diese beiden Aspekte müssen im Voraus durchdacht werden - sie können die Verwendung des Thread-Pool-Musters einschränken.

Über den Thread-Pool:
http://en.wikipedia.org/wiki/Thread_pool_pattern
http://docs.Oracle.com/cd/E19253-01/816-5137/ggedn/index.html

Eine gute Literatur über Pthreads, um loszulegen:
http://www.advancedlinuxprogramming.com/alp-folder/alp-ch04-threads.pdf

3
Marcin

Um den Teil "automatisch multithreaded" der OP-Frage gezielt zu behandeln:

Eine wirklich interessante Sicht auf das Programmieren von Parallelismus wurde in eine Sprache namens Cilk Plus von MIT erfunden, die jetzt im Besitz von Intel ist. Um Wikipedia zu zitieren, ist das die Idee

msgstr "Der Programmierer sollte dafür verantwortlich sein, die Parallelität freizulegen. Identifizieren von Elementen, die sicher ausgeführt werden können. Diese sollte parallel ausgeführt werden; er sollte dann der Laufzeitumgebung überlassen werden, insbesondere der Scheduler, um während der Ausführung zu entscheiden , wie die Arbeit tatsächlich zwischen den Prozessoren aufgeteilt werden soll. "

Cilk Plus ist eine Obermenge von Standard C++. Es enthält nur ein paar zusätzliche Schlüsselwörter (_Cilk_spawn, _Cilk_sync und _Cilk_for), mit denen der Programmierer Teile seines Programms als parallelisierbar kennzeichnen kann. Der Programmierer gibt nicht Mandat an, dass beliebiger Code in einem neuen Thread ausgeführt werden soll, er erlaubt nur , dem leichtgewichtigen Laufzeitplaner, einen neuen Thread zu erstellen, und zwar nur dann, wenn dies tatsächlich das Richtige ist Laufzeitbedingungen.

Um Cilk Plus zu verwenden, fügen Sie einfach die Schlüsselwörter in Ihren Code ein und erstellen Sie mit Intels C++ - Compiler .

2
AlcubierreDrive

Ihr Code wird vom Compiler nicht automatisch multithreaded, wenn dies Ihre Frage war. Bitte beachten Sie, dass die C-Standards selbst nichts über Multithreading wissen, denn ob Sie Multithreading verwenden können, hängt nicht von der Sprache ab, die Sie für die Codierung verwenden, sondern von der Zielplattform, für die Sie codieren. In C geschriebener Code kann so ziemlich alles ausführen, für den ein C-Compiler existiert. Es gibt sogar einen C-Compiler für einen C64-Computer (fast vollständig ISO-99-konform). Um mehrere Threads zu unterstützen, muss die Plattform über ein Betriebssystem verfügen, das dies unterstützt. In der Regel bedeutet dies, dass mindestens eine bestimmte CPU-Funktionalität vorhanden sein muss. Ein Betriebssystem kann Multithreading fast ausschließlich in Software ausführen. Dies ist äußerst langsam und es gibt keinen Speicherschutz. Es ist jedoch möglich, dass jedoch auch in diesem Fall mindestens programmierbare Interrupts erforderlich sind.

Wie Multithread-C-Code geschrieben wird, hängt daher vollständig vom Betriebssystem Ihrer Zielplattform ab. Es gibt POSIX-konforme Systeme (OS X, FreeBSD, Linux usw.) und Systeme, die über eine eigene Bibliothek dafür verfügen (Windows). Einige Systeme verfügen über mehr als eine Bibliothek (OS X hat beispielsweise die POSIX-Bibliothek, aber es gibt auch den Carbon Thread Manager, den Sie in C verwenden können (obwohl ich denke, dass dies heutzutage eher ein Erbe ist).

Natürlich gibt es plattformübergreifende Thread-Bibliotheken, und einige moderne Compiler unterstützen beispielsweise OpenMP, wobei der Compiler automatisch Code erstellt, um Threads auf der ausgewählten Zielplattform zu erstellen. aber nicht viele Compiler unterstützen es, und die, die es unterstützen, sind normalerweise nicht vollständig. In der Regel erhalten Sie die breiteste Systemunterstützung, wenn Sie POSIX-Threads verwenden, die häufiger als "pthreads" bezeichnet werden. Die einzige große Plattform, die es nicht unterstützt, ist Windows. Hier können Sie kostenlose Drittanbieter-Bibliotheken wie this verwenden. Es gibt auch mehrere andere Ports ( Cygwin hat sicherlich einen). Wenn Sie an einem Tag über eine Benutzeroberfläche verfügen, möchten Sie möglicherweise eine plattformübergreifende Bibliothek wie wxWidgets oder SDL verwenden, die beide auf allen unterstützten Plattformen eine konsistente Multithread-Unterstützung bieten. 

1
Mecki

Sie können pthreads verwenden, um Multithreading in C ..__ durchzuführen.

#include<pthread.h>
#include<stdio.h>

void *mythread1();  //thread prototype
void *mythread2();

int main(){
    pthread_t thread[2];
    //starting the thread
    pthread_create(&thread[0],NULL,mythread1,NULL);
    pthread_create(&thread[1],NULL,mythread2,NULL);
    //waiting for completion
    pthread_join(thread[0],NULL);
    pthread_join(thread[1],NULL);


    return 0;
}

//thread definition
void *mythread1(){
    int i;
    for(i=0;i<5;i++)
        printf("Thread 1 Running\n");
}
void *mythread2(){
    int i;
    for(i=0;i<5;i++)
        printf("Thread 2 Running\n");
}

Referenz: C-Programm zur Implementierung von Multithreading-Multithreading in C

1
Mohd Shibli

Wenn eine Iteration in einer Schleife unabhängig von den vorherigen ist, gibt es einen sehr einfachen Ansatz: Versuchen Sie es mit Multi-Processing anstatt mit Multithreading. 

Angenommen, Sie haben 2 Kerne und ntimes ist 100, dann 100/2 = 50. Erstellen Sie also 2 Versionen des Programms, bei denen die erste von 0 bis 49, die andere von 50 bis 99 durchläuft beschäftigt. 

Dies ist ein sehr vereinfachter Ansatz, aber Sie müssen sich nicht mit der Erstellung von Threads, der Synchronisierung usw. herumschlagen

1
ifyes

C11-Threads in Glibc 2.28.

Getestet in Ubuntu 18.04 (glibc 2.27) durch Kompilieren von glibc aus source: Mehrere glibc-Bibliotheken auf einem einzigen Host

Beispiel aus: https://de.cppreference.com/w/c/language/atomic

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

atomic_int acnt;
int cnt;

int f(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 -std=c11 main.c -pthread
./a.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 auf die nicht-atomare Variable schnell erfolgt.

TODO: zerlegen und sehen, wozu ++acnt; kompiliert wird.

POSIX-Threads

#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 pthread_mutex.c -pthread
./a.out
./a.out 1

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

Getestet auf Ubuntu 18.04. GitHub Upstream .