web-dev-qa-db-fra.com

Comment "multithread" code C

J'ai une application de calcul de nombre écrite en C. C'est une sorte de boucle principale qui appelle pour chaque valeur, pour des valeurs croissantes de "i", une fonction qui effectue des calculs. J'ai lu sur le multithreading, et j'envisage d'en apprendre un peu, en C. Je me demande si un code général comme le mien pourrait être automatiquement multithread et comment.

Merci

P.D. Pour avoir une idée de mon code, disons qu'il ressemble à ceci:

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

Une alternative au multithread de votre code serait d'utiliser pthreads (fournit un contrôle plus précis que OpenMP).

En supposant que x, y & result sont des tableaux de variables globales,

#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 );
...
}

(Compilez votre code avec gcc prog.c -lpthread)

18
user191776

Si la tâche est hautement parallélisable et que votre compilateur est moderne, vous pouvez essayer OpenMP. http://en.wikipedia.org/wiki/OpenMP

26
Novikov

Vous devriez jeter un oeil sur openMP pour cela. L'exemple C/C++ de cette page est similaire à votre 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 */

}

Si vous préférez ne pas utiliser openMP, vous pouvez utiliser directement pthreads ou cloner/attendre.

Peu importe la route que vous choisissez, vous divisez simplement vos tableaux en morceaux que chaque thread va traiter. Si tout votre traitement est purement informatisé (comme suggéré par votre fonction exemple), vous devriez alors avoir intérêt à avoir autant de threads que de processeurs logiques.

Il est parfois fastidieux d’ajouter des threads pour effectuer un traitement parallèle. Assurez-vous donc de donner à chaque thread suffisamment de travail pour le rattraper. Habituellement, vous le ferez, mais si chaque thread ne donne qu'un calcul à faire et que les calculs ne sont pas si difficiles à faire, vous pouvez en réalité ralentir les choses. Vous pouvez toujours avoir moins de threads que de processeurs si c'est le cas.

Si vous travaillez avec IO, vous constaterez peut-être qu’il est avantageux de disposer de plus de threads que de processeurs, car un thread peut bloquer en attendant que certains IO en complète thread peut faire ses calculs. Vous devez cependant faire attention IO dans le même fichier dans les threads.

9
nategoose

Si vous souhaitez fournir un accès simultané à une seule boucle pour une sorte d'informatique scientifique ou similaire, OpenMP, comme @Novikov, est vraiment votre meilleur choix. c'est ce pour quoi il a été conçu.

Si vous cherchez à apprendre l'approche plus classique que vous verriez plus généralement dans une application écrite en C ... Sur POSIX, vous voulez pthread_create() et al. Je ne suis pas sûr de votre expérience en matière de concurrence dans d’autres langues, mais avant d’en venir trop à ce sujet, vous voudrez bien connaître vos primitives de synchronisation (mutex, sémaphores, etc.), ainsi que comprendre quand vous le ferez. besoin de les utiliser. Ce sujet pourrait être un livre entier ou un ensemble de SO questions en soi.

9
asveikau

Le compilateur C++ d’Intel est capable de paralelliser automatiquement votre code. C'est juste un commutateur de compilateur que vous devez activer. Cependant, cela ne fonctionne pas aussi bien qu’OpenMP (c’est-à-dire qu’il ne réussit pas toujours ou que le programme qui en résulte est plus lent) . Sur le site Web d’Intel: * OS et Mac OS * X) ou/Qparallel (Windows * OS) identifie automatiquement les structures de boucle contenant du parallélisme.Lors de la compilation, le compilateur tente automatiquement de décomposer les séquences de code en threads distincts pour le traitement en parallèle. le programmeur est nécessaire. "

3
darklon

Selon le système d'exploitation, vous pouvez utiliser des threads posix. Vous pouvez également implémenter le multithreading sans pile à l'aide de machines d'état. Il existe un très bon livre intitulé "Embedded multitasking" de Keith E. Curtis. Il s’agit simplement d’un ensemble d’énoncés soigneusement conçus. Fonctionne très bien, je l'ai utilisé sur tout, des macs Apple, des semi-conducteurs de lapin, des AVR et des PC.

Vali

3
ValiRossi

un bon exercice pour apprendre la programmation simultanée dans n’importe quelle langue serait de travailler sur la mise en œuvre d’un pool de threads.
Dans ce modèle, vous créez des threads à l’avance. Ces threads sont traités comme une ressource. Un objet/une structure de pool de threads est utilisé pour assigner une tâche définie par l'utilisateur à ces threads pour exécution. Lorsque la tâche est terminée, vous pouvez collecter ses résultats. Vous pouvez utiliser le pool de threads comme modèle de conception à usage général pour la simultanéité . L'idée principale pourrait ressembler à

#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.

Le mécanisme de collecte des données des tâches et la quantité de threads utilisés dans le pool doivent être choisis en fonction de vos besoins et des capacités du matériel et de l'environnement d'exécution.
Veuillez également noter que ce modèle ne dit pas comment vous devriez "synchroniser" vos tâches les unes avec les autres/en dehors de l'environnement. De plus, la gestion des erreurs peut être un peu délicate (exemple: que faire lorsqu'une tâche échoue). Ces deux aspects doivent être pensés à l’avance: ils peuvent limiter l’utilisation du modèle de pool de threads.

À propos du pool de threads:
http://fr.wikipedia.org/wiki/Thread_pool_pattern
http://docs.Oracle.com/cd/E19253-01/816-5137/ggedn/index.html

Une bonne littérature sur les pthreads pour y aller:
http://www.advancedlinuxprogramming.com/alp-folder/alp-ch04-threads.pdf

3
Marcin

Pour répondre spécifiquement à la partie "automatiquement multithread" de la question du PO:

Une vue vraiment intéressante sur la façon de programmer le parallélisme a été conçue dans un langage appelé Cilk Plus inventé par MIT et appartenant maintenant à Intel. Pour citer Wikipedia, l’idée est que

"le programmeur devrait être responsable d'exposer le parallélisme, en identifiant les éléments qui peuvent être exécutés en toute sécurité être exécutés en parallèle; il devrait ensuite être laissé à l'environnement d'exécution, planificateur, pour décider en cours d’exécution comment répartir le travail entre les processeurs. "

Cilk Plus est un sur-ensemble de la norme C++. Il ne contient que quelques mots-clés supplémentaires (_Cilk_spawn, _Cilk_sync et _Cilk_for) qui permettent au programmeur de baliser des parties de leur programme comme pouvant être parallélisées. Le programmeur n'a pas mandat aucun code à exécuter sur un nouveau thread, il ne fait que autoriser le planificateur d'exécution léger pour générer un nouveau thread si et seulement s'il est réellement approprié. faire dans les conditions d'exécution particulières.

Pour utiliser Cilk Plus, ajoutez simplement ses mots-clés dans votre code et compilez-le avec le compilateur C++ d'Intel .

2
AlcubierreDrive

Votre code n'est pas automatiquement multithread par le compilateur si telle était votre question. Veuillez noter que les normes C elles-mêmes ne connaissent rien au multi-threading, car le fait de savoir si vous pouvez utiliser le multi-threading ou non ne dépend pas de la langue utilisée pour le codage, mais de la plate-forme de destination pour laquelle vous codez. Le code écrit en C peut fonctionner sur à peu près n'importe quoi pour lequel un compilateur C existe. Un compilateur C existe même pour un ordinateur C64 (presque totalement conforme à la norme ISO-99); Cependant, pour prendre en charge plusieurs threads, la plate-forme doit disposer d'un système d'exploitation prenant en charge cette fonctionnalité, ce qui signifie généralement qu'au moins certaines fonctionnalités de la CPU doivent être présentes. Un système d'exploitation peut effectuer le multithreading presque exclusivement dans un logiciel, ce qui sera terriblement lent et il n'y aura pas de protection de la mémoire, mais cela est possible, mais même dans ce cas, vous aurez besoin d'au moins des interruptions programmables.

Alors, comment écrire du code C multi-thread dépend entièrement du système d'exploitation de votre plate-forme cible. Il existe des systèmes compatibles POSIX (OS X, FreeBSD, Linux, etc.) et des systèmes dotés de leur propre bibliothèque (Windows). Certains systèmes ont plus qu'une bibliothèque pour cela (par exemple, OS X a la bibliothèque POSIX, mais il existe également le Carbon Thread Manager que vous pouvez utiliser en C (bien que je pense que c'est plutôt un héritage de nos jours).

Bien sûr, il existe des bibliothèques de threads multi-plateformes et certains compilateurs modernes prennent en charge des tâches telles que OpenMP, dans laquelle le compilateur crée automatiquement du code pour créer des threads sur la plate-forme cible de votre choix. mais peu de compilateurs le supportent et ceux qui le supportent ne sont généralement pas complets. Généralement, vous obtenez le support système le plus large en utilisant des threads POSIX, appelés le plus souvent "pthreads". Windows est la seule plate-forme majeure à ne pas le prendre en charge et vous pouvez utiliser ici des bibliothèques tierces gratuites telles que celle-ci . Plusieurs autres ports existent également ( Cygwin en a un à coup sûr). Si vous avez un jour une interface utilisateur, vous pouvez utiliser une bibliothèque multiplate-forme telle que wxWidgets ou SDL , offrant toutes les deux un support cohérent multi-thread sur toutes les plates-formes prises en charge. 

1
Mecki

Vous pouvez utiliser pthreads pour effectuer un multithreading dans C . Voici un exemple simple basé sur pthreads.

#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");
}

Référence: Programme C pour implémenter le multithreading-multithreading en C

1
Mohd Shibli

Si une itération en boucle est indépendante de celle qui la précède, il existe une approche très simple: essayez le multi-traitement plutôt que le multi-threading. 

Supposons que vous avez 2 cœurs et que ntimes vaut 100, puis 100/2 = 50, créez donc 2 versions du programme où la première itère de 0 à 49, l’autre de 50 à 99. Exécutez les deux, vos cœurs doivent être conservés occupé. 

C'est une approche très simpliste, mais vous n'avez pas à vous soucier de la création de threads, de la synchronisation, etc.

1
ifyes

Threads C11 dans la glibc 2.28.

Testé dans Ubuntu 18.04 (glibc 2.27) en compilant glibc à partir du code source: Plusieurs bibliothèques glibc sur un même hôte

Exemple tiré de: https://en.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);
}

Compiler et exécuter:

gcc -std=c11 main.c -pthread
./a.out

Sortie possible:

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

Il est très probable que le compteur non atomique soit plus petit que le compteur atomique en raison de l’accès rapide à la variable non atomique à travers les threads.

TODO: désassemblez et voyez ce que ++acnt; compile.

Threads POSIX

#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;
}

Compiler et exécuter:

gcc -std=c99 pthread_mutex.c -pthread
./a.out
./a.out 1

La première exécution fonctionne correctement, la seconde échoue à cause d'une synchronisation manquante.

Testé sur Ubuntu 18.04. GitHub en amont .