2011-03-15 3 views
9

J'essaie d'utiliser des threads (pour la première fois!) Dans une application GCC C qui fonctionne bien en mode non-threaded. Quand je l'exécute, certains threads donnent des résultats qui sont tous à zéro au lieu des réponses requises (que je connais à des fins de vérification), mais les threads donnant des zéros ne sont pas les mêmes chaque fois que je l'exécute. Ceux qui donnent des réponses non nulles sont corrects, donc le code semble fonctionner correctement. Je me demande si quelqu'un peut signaler des domaines où je pourrais avoir quelque chose qui n'est pas sûr pour les threads. Mes propres pensées sont peut-être en raison de la façon dont je collectionne les résultats ou l'allocation de mémoire - j'utilise malloc et libre mais ailleurs dans StackOverflow je vois que GCC malloc est considéré comme thread-safe si -lpthread (que je suis Faire). Rien n'utilise les variables globales/statiques - tout est passé comme arguments de fonction.Les threads peuvent-ils écrire sur différents éléments du même réseau de structures sans verrouillage?

Afin de renvoyer les résultats à main, ma routine threadée utilise un tableau de structures. Chaque thread écrit dans un élément distinct de ce tableau, ainsi ils n'essaient pas d'écrire dans la même mémoire. Peut-être que je dois utiliser une certaine forme de verrouillage lors de l'écriture des résultats, même si elles ne vont pas au même élément du tableau de la structure?

J'ai suivi la recette pour le code threadé ici: https://computing.llnl.gov/tutorials/pthreads/#Abstract

Je joindre des extraits de code (simplifié) en cas cela donne des indices (je l'ai omis/quelque chose modifié de manière incorrecte, mais je ne demande pas à quiconque de repérer bugs, juste la méthodologie générale).

typedef struct p_struct { /* used for communicating results back to main */ 
    int given[CELLS]; 
    int type; 
    int status; 
    /*... etc */ 
} puzstru; 

typedef struct params_struct { /* used for calling generate function using threads */ 
    long seed; 
    char *text; 
    puzzle *puzzp; 
    bool unique; 
    int required; 
} paramstru; 
/* ========================================================================================== */ 
void *myfunc(void *spv) /* calling routine for use by threads */ 
{ 
    paramstru *sp=(paramstru *)spv; 
    generate(sp->seed, sp->text, sp->puzzp, sp->unique, sp->required); 
    pthread_exit((void*) spv); 
} 
/* ========================================================================================== */ 
int generate(long seed, char *text, puzstru *puzzp, bool unique, int required) 
{ 
/* working code , also uses malloc and free, 
    puts results in the element of a structure array pointed to by "puzzp", 
    which is different for each thread 
    (see calling routine below :  params->puzzp=puz+thr;) 
    extract as follows: */ 
      puzzp->given[ix]=calcgiven[ix]; 
      puzzp->type=1; 
      puzzp->status=1; 
      /* ... etc */ 
} 
/* ========================================================================================== */ 


int main(int argc, char* argv[]) 
{ 
    pthread_t thread[NUM_THREADS]; 
    pthread_attr_t threadattr; 
    int thr,threadretcode; 
    void *threadstatus; 
    paramstru params[1]; 

    /* ....... ETC */ 

/* set up params structure for function calling parameters */ 
    params->text=mytext; 
    params->unique=TRUE; 
    params->required=1; 

    /* Initialize and set thread detached attribute */ 
    pthread_attr_init(&threadattr); 
    pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE); 

    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
     params->seed=ran_arr_next(startingseeds); 
     params->puzzp=puz+thr; 
     threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)params); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_create() is %d\n", threadretcode); 
      exit(-1); 
     } 
    } 

    /* Free thread attribute and wait for the other threads */ 
    pthread_attr_destroy(&threadattr); 
    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     threadretcode = pthread_join(thread[thr], &threadstatus); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_join() is %d\n", threadretcode); 
      exit(-1); 
     } 
     printf("Main: completed join with thread %d having a status of %ld\n",thr,(long)threadstatus); 
    } 

/* non-threaded code, print results etc ............. */ 

    free(startingseeds); 
    free(puz); 
    printf("Main: program completed. Exiting.\n"); 
    pthread_exit(NULL); 
} 

Pour le bénéfice des autres la lecture de ce - toutes les réponses sont correctes, et la réponse à la question dans le titre est OUI, les fils peuvent écrire en toute sécurité à différents éléments du même tableau de structures, mon problème était dans la routine d'appel - ce qui suit est l'extrait de code modifié (fonctionne maintenant très bien):

paramstru params[NUM_THREADS]; 

    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
    /* set up params structure for function calling parameters */ 
     params[thr].text=mytext; 
     params[thr].unique=TRUE; 
     params[thr].required=1; 
     params[thr].seed=ran_arr_next(startingseeds); 
     params[thr].puzzp=puz+thr; 
     threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)&params[thr]); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_create() is %d\n", threadretcode); 
      exit(-1); 
     } 
    } 
+0

Hmm. Où est la variable puz déclarée? Est-ce de type 'puzzle *'? Comment 'ix' est-il calculé? Je suppose que votre problème est dans le bloc que vous avez marqué comme "code de travail". ;) –

Répondre

3
paramstru params[1]; 

le code passe la même structure à toutes les discussions. Juste la boucle d'initialisation de fil est les données en écrasant un fil devrait fonctionner sur:

for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
     params->seed=ran_arr_next(startingseeds); /* OVERWRITE */ 
     params->puzzp=puz+thr; /* OVERWRITE */ 

La raison fonctionne le code non-thread est parce que chaque appel à myfunc() se termine avant que la structure params est modifiée.

+1

Merci, je suppose qu'il y avait une course entre un thread lisant les params et la prochaine itération de la boucle d'installation. – RussellG

+0

Vous pourriez fixer cette course avec une barrière (doucement lente) ou en utilisant une structure séparée pour chaque thread (un léger gaspillage de mémoire). –

1

Vous avez créé une seule copie de la structure de vos paramètres et vous l'écrasez en transmettant la même adresse à chaque thread. Vous ne voulez pas paramstru params[NUM_THREADS];?

+0

Oui merci, c'est ce que je vais faire. – RussellG

6

Pour répondre à votre question, il est parfaitement possible d'écrire sur différents éléments d'un même tableau à partir de différents filetages sans verrouillage. Il n'y aura jamais de data race si deux threads écrivent sur le même octet sans synchronisation (par exemple, verrouillage). Comme d'autres réponses le soulignent, la raison pour laquelle votre code est cassé est parce que vous passez un pointeur vers le même objet params à chacun de vos threads, puis vous modifiez cet objet. Vous voulez probablement créer un nouveau param pour chaque thread.

+3

Bien qu'il soit sûr, s'il n'est pas fait avec soin, il peut conduire à de mauvaises performances. Si plusieurs threads continuent d'accéder aux éléments du tableau sur la même ligne de cache, le rebond de la mémoire cache est lourd, ce qui est coûteux. – ninjalj

+0

Merci pour l'info concernant les différents octets. Je cherchais au mauvais endroit pour mon problème. – RussellG

+0

Etes-vous sûr que c'est garanti? Qu'en est-il des machines qui ne peuvent pas effectuer des écritures plus petites que les mots, pour lesquelles l'écriture d'un octet est une opération de lecture-modification-écriture? Je suis d'accord sur le fait que ces machines sont des conneries pathologiques qui ne devraient pas être utilisées à des fins réelles, mais à proprement parler je crois qu'elles doivent être prises en compte si vous revendiquez une portabilité totale ... –

Questions connexes