2011-03-15 10 views
9

Estoy tratando de usar hilos (por primera vez) en una aplicación GCC C que funciona bien en modo no roscado. Cuando lo ejecuto, algunos subprocesos dan resultados que son todos cero en lugar de las respuestas requeridas (que sé para fines de verificación), pero los subprocesos que dan ceros no son los mismos cada vez que lo ejecuto. Los que dan respuestas distintas de cero son correctos, por lo que el código parece funcionar bien como tal. Me pregunto si alguien puede señalar áreas en las que podría tener algo que no sea seguro para subprocesos.¿Pueden los hilos escribir en diferentes elementos del mismo conjunto de estructuras sin bloqueo?

Pienso que puede deberse a la forma en que recopilo resultados o a la asignación de memoria. Utilizo malloc y libre, pero en otros lugares de StackOverflow veo que GCC malloc se considera seguro para subprocesos si se vincula con -lpthread (que soy obra). Nada usa variables globales/estáticas: todo se pasa como argumentos de funciones.

Para pasar los resultados a main, mi rutina con hilos utiliza una matriz de estructuras. Cada hilo escribe en un elemento distinto de esta matriz, por lo que no intentan escribir en la misma memoria. ¿Quizás necesite usar algún tipo de bloqueo al escribir resultados aunque no vayan al mismo elemento de la matriz de estructura?

que siguieron la receta para el código de roscado aquí: https://computing.llnl.gov/tutorials/pthreads/#Abstract

Puedo adjuntar (simplificado) extractos de código en caso de que esto da ninguna pista (que puede haber omitido/algo modificado de forma incorrecta, pero no estoy pidiendo que cualquiera pueda detectar errores, solo la metodología general).

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

Para el beneficio de otros leyendo esto - todas las respuestas eran correctas, y la respuesta a la pregunta en el título es sí, las discusiones pueden escribir de forma segura a los diferentes elementos de la misma matriz de estructuras, mi problema era en la rutina de llamada - el siguiente es el fragmento de código modificado (ahora funciona 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. ¿Dónde se declara puz variable? ¿Es de tipo 'rompecabezas *'? ¿Cómo se calcula 'ix'? Supongo que su problema está en el bloque que marcó como "código de trabajo". ;) –

Respuesta

3
paramstru params[1]; 

el código está pasando la misma estructura en todas las roscas. Sólo el bucle de inicialización hilo es sobrescribir los datos que un hilo debe trabajar en:

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 razón por la que funciona el código no roscado se debe a que cada llamada a myfunc() termina antes de que se cambió la estructura params.

+1

Gracias, supongo que hubo una carrera entre un hilo leyendo los parámetros y la próxima iteración del ciclo de configuración. – RussellG

+0

Puedes arreglar esta carrera con una barrera (levemente lenta) o usando una estructura separada para cada hilo (un poco desperdicio de memoria). –

1

Usted solo creó una copia de su estructura de parámetros y la sobrescribe y pasa la misma dirección a cada hilo. ¿No quieres paramstru params[NUM_THREADS];?

+0

Sí, gracias, esto es lo que haré. – RussellG

6

Para responder a su pregunta, es perfectamente correcto escribir en diferentes elementos de la misma matriz desde diferentes subprocesos sin bloquear. Solo habrá un data race si dos hilos escriben en el mismo byte sin sincronización (por ejemplo, bloqueo).

Como otras respuestas señalan, la razón por la cual su código as-escrito se rompe es porque pasa un puntero al mismo objeto params a cada uno de sus hilos, y luego modifica ese objeto. Probablemente desee crear un nuevo param para cada hilo.

+3

Si bien es seguro, si no se hace con cuidado, puede provocar un mal rendimiento. Si varios subprocesos siguen accediendo a los elementos de la matriz en la misma línea de caché, tendrá un gran rebote de caché, que es caro. – ninjalj

+0

Gracias por la información sobre diferentes bytes. Estaba buscando en el lugar equivocado para mi problema. – RussellG

+0

¿Estás seguro de que está garantizado? ¿Qué ocurre con las máquinas que no pueden realizar escrituras más pequeñas que la palabra, para lo cual escribir un byte es una operación de lectura, modificación y escritura? Estoy de acuerdo en que estas máquinas son basura patológica que no debe usarse para ningún propósito del mundo real, pero, en términos estrictos, creo que deben tenerse en cuenta si usted reclama una portabilidad completa ... –

Cuestiones relacionadas