Questions à choix multiples¶
Cette semaine porte sur la communication et la synchronisation entre threads. Plus précisément, la matière est décrite dans les deux sections suivantes :
- Utilisation de plusieurs threads (sauf la section Utilisation d’instruction atomique)
- Communication entre threads (jusqu’à la section Le problème des philosophes)
Question 1. Utilisation de pthread_create(3)¶
f
en lui passant la chaîne de caractères s
comme argument. Lequel ?void * f( void * param) {
// incomplet
return NULL;
}
int main (int argc, char *argv[]) {
pthread_t t;
int err;
char *s;
err=pthread_create(&t,NULL,&(f),(void *) s);
}
void * f (void * param) {
// incomplet
return NULL;
}
int main (int argc, char *argv[]) {
pthread_t t;
int err;
char *s;
err=pthread_create(&t,NULL,&(f),(void *) &s);
}
void f(void * param) {
// incomplet
return NULL;
}
int main (int argc, char *argv[]) {
pthread_t *t;
int err;
char *s;
err=pthread_create(t,NULL,*f,(void *) *s);
}
void *f(void ** param) {
// incomplet
return NULL;
}
int main (int argc, char *argv[]) {
pthread_t t;
int err;
char s;
err=pthread_create(&t,NULL,&(f),(void *) s);
}
Question 2. Passage d’arguments à un thread¶
Considérons un thread qui a pour objectif de convertir une fraction en un nombre en virgule flottante. Ce n’est pas une bonne utilisation de threads puisque le calcul à effectuer est très simple, mais cela nous permettra de voir comment un thread peut recevoir des arguments directement. En dehors des threads, cette fonction de conversion pourrait s’écrire :
struct fraction {
int num;
int denum;
};
typedef struct fraction Fraction_t;
float tofloat(Fraction_t t) {
return (float) t.num/ (float) t.denum;
}
void *mythread(void * param) {
Fraction_t *f=(Fraction_t *) param;
float *r=(float *)malloc(sizeof(float));
*r=(float) f->num/ (float) f->denum;
return((void *) r);
}
int main (int argc, char *argv[]) {
pthread_t t;
Fraction_t f;
f.num=1;
f.denum=3;
float *r;
int err;
err=pthread_create(&t,NULL,&mythread,&(f));
err=pthread_join(t,(void **) &r);
}
void *mythread(void * param) {
Fraction_t f= *param;
float r;
r=(float) f.num/ (float) f.denum;
return((void *) &r);
}
int main (int argc, char *argv[]) {
pthread_t t;
Fraction_t f;
f.num=1;
f.denum=3;
float r;
int err;
err=pthread_create(&t,NULL,&mythread,&(f));
err=pthread_join(t,(void **) &r);
}
void *mythread(void * param) {
Fraction_t *t=(Fraction_t *) param;
float *r=(float *)malloc(sizeof(float));
*r=(float) t->num/ (float) t->denum;
return((void *) r);
}
int main (int argc, char *argv[]) {
pthread_t t;
Fraction_t f;
f.num=1;
f.denum=3;
float r;
int err;
err=pthread_create(&t,NULL,&mythread,&f);
r=pthread_join(t,NULL);
}
float mythread(Fraction_t param) {
float *r=(float *)malloc(sizeof(float));
*r=(float) param->num/ (float) param->denum;
return(r);
}
int main (int argc, char *argv[]) {
pthread_t t;
Fraction_t f;
f.num=1;
f.denum=3;
printf("%f \n",tofloat(f));
float *r;
int err;
err=pthread_create(&t,NULL,&mythread,&(f));
err=pthread_join(t,(void *) &r);
}
Question 3. Algorithme de Peterson¶
/* initialisation */
bool in1 = false;
bool in2 = false;
int last = 1;
// thread 1
while (true) {
in1 = true;
last = 1;
while ( in2 && (last==1)) {};
section_critique();
in1=false;
// ...
}
// thread2
while (true) {
in2 = true;
last = 2;
while ( in1 && (last==2)) {};
section_critique();
in2=false;
// ...
}
/* initialisation */
bool in1 = false;
bool in2 = false;
int last = 2;
// thread 1
while (true) {
in1 = true;
last = 1;
while ( in2 && (last==1)) {};
section_critique();
in1=false;
// ...
}
// thread2
while (true) {
in2 = true;
last = 2;
while ( in1 && (last==2)) {};
section_critique();
in2=false;
// ...
}
// initialisation
bool in1 = false;
bool in2 = false;
int last = 1;
// thread 1
while (true) {
in1 = true;
last = 1;
while ( in1 && (last==1)) {};
section_critique();
in1=false;
// ...
}
// thread2
while (true) {
in2 = true;
last = 2;
while ( in2 && (last==2)) {};
section_critique();
in2=false;
// ...
}
// initialisation
bool in1 = false;
bool in2 = false;
int last = 2;
// thread 1
while (true) {
in1 = true;
last = 1;
while ( in2 && (last==2)) {};
section_critique();
in1=false;
// ...
}
// thread2
while (true) {
in2 = true;
last = 2;
while ( in1 && (last==1)) {};
section_critique();
in2=false;
// ...
}
// initialisation
bool in1 = false;
bool in2 = false;
int last = 1;
// thread 1
while (true) {
last = 1;
in1 = true;
while ( in2 && (last==1)) {};
section_critique();
in1=false;
// ...
}
// thread2
while (true) {
last = 2;
in2 = true;
while ( in1 && (last==2)) {};
section_critique();
in2=false;
// ...
}
Question 4. Initialisation de mutex¶
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
err= pthread_mutexattr_init(&attr);
if(err!=0)
error(err,"pthread_mutexattr_init");
err=pthread_mutex_init( &mutex, &attr);
if(err!=0)
error(err,"pthread_mutex_init");
pthread_mutex_t mutex;
err=pthread_mutex_init( &mutex, NULL);
if(err!=0)
error(err,"pthread_mutex_init");
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
err= pthread_mutexattr_init(attr);
if(err!=0)
error(err,"pthread_mutexattr_init");
err=pthread_mutex_init(mutex, attr);
if(err!=0)
error(err,"pthread_mutex_init");
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
err= pthread_mutexattr_init(&attr);
if(err!=0)
error(err,"pthread_mutexattr_init");
err=pthread_mutex_init(&mutex, attr);
if(err!=0)
error(err,"pthread_mutex_init");
pthread_mutex_t *mutex;
pthread_mutexattr_t *attr;
err= pthread_mutexattr_init(attr);
if(err!=0)
error(err,"pthread_attr_init");
err=pthread_mutex_init(mutex, attr);
if(err!=0)
error(err,"pthread_mutex_init");
Question 5. Utilisation de pthread_mutex_lock(3posix) et pthread_mutex_unlock(3posix)¶
update
qui prend comme arguments la variable à mettre à jour et le mutex qui y est associé. Parmi les extraits ci-dessous, lequel permet de mettre à jour la variable sans risque de contention entre les threads qui y accèdent ?void update(int * val, pthread_mutex_t * mutex) {
err=pthread_mutex_lock(mutex);
if(err!=0)
error(err,"pthread_mutex_lock");
// mise à jour de la variable globale
err=pthread_mutex_unlock(mutex);
if(err!=0)
error(err,"pthread_mutex_unlock");
}
void update(int * val, pthread_mutex_t * mutex) {
err=pthread_mutex_unlock(mutex);
if(err!=0)
error(err,"pthread_mutex_unlock");
// mise à jour de la variable globale
err=pthread_mutex_lock(mutex);
if(err!=0)
error(err,"pthread_mutex_lock");
}
void update(int val, pthread_mutex_t mutex) {
err=pthread_mutex_lock(mutex);
if(err!=0)
error(err,"pthread_mutex_lock");
// mise à jour de la variable globale
err=pthread_mutex_unlock(mutex);
if(err!=0)
error(err,"pthread_mutex_unlock");
}
void update(int * val, pthread_mutex_t mutex) {
err=pthread_mutex_lock(&mutex);
if(err!=0)
error(err,"pthread_mutex_lock");
// mise à jour de la variable globale
err=pthread_mutex_unlock(&mutex);
if(err!=0)
error(err,"pthread_mutex_unlock");
}
Question 6. Utilisation de plusieurs mutex¶
Dans certains programmes, il est nécessaire de définir plusieurs mutex qui sont utilisés par différents threads pour gérer l’accès à des variables partagées. Considérons un programme qui utilise trois variables globales et est découpé en plusieurs threads.
long a=5; // variable globale partagée
long b=7; // variable globale partagée
long c=9; // variable globale partagée
pthread_mutex_t x; // variable globale associée à a
pthread_mutex_t y; // variable globale associée à b
pthread_mutex_t z; // variable globale associée à c
void update(int * val1, int * val2, pthread_mutex_t * mutex1, pthread_mutex_t * mutex2) {
err=pthread_mutex_lock(mutex1);
if(err!=0)
error(err,"pthread_mutex_lock");
err=pthread_mutex_lock(mutex2);
if(err!=0)
error(err,"pthread_mutex_lock");
// mise à jour val1
// mise à jour val2
err=pthread_mutex_unlock(mutex1);
if(err!=0)
error(err,"pthread_mutex_unlock");
err=pthread_mutex_unlock(mutex2);
if(err!=0)
error(err,"pthread_mutex_unlock");
}
a
, b
et c
. Parmi les fragments de code ci-dessous qui utilisent plusieurs threads, un seul est correct. Lequel ?// thread A
update(&a,&b,&x,&y);
update(&a,&c,&x,&z);
// thread B
update(&b,&c,&y,&z);
update(&a,&c,&x,&z);
// thread A
update(&a,&b,&x,&y);
update(&b,&c,&y,&z);
// thread B
update(&b,&c,&y,&z);
update(&a,&c,&x,&z);
// thread A
update(&a,&b,&x,&y);
update(&c,&a,&z,&x);
// thread B
update(&b,&c,&y,&z);
update(&a,&c,&x,&z);
// thread A
update(&a,&b,&x,&y);
update(&a,&c,&x,&z);
// thread B
update(&b,&c,&y,&z);
update(&c,&a,&z,&x);
.. comment:: Lorsqu'un thread utilise plusieurs ressources protégées par un mutex, il est important que les accès à ces mutex se fasse chaque fois dans le même ordre. Dans cet exemple, il faut toujours accéder à ``x`` puis à ``y`` puis à ``z`` (ou un autre ordre). Accéder à ``z`` puis à ``x`` dans le thread B et à ``x`` puis à ``z`` dans le thread A est une source de deadlocks potentiels.
// thread A
update(&a,&b,&x,&y);
update(&a,&b,&x,&y);
// thread B
update(&b,&a,&y,&x);
update(&a,&c,&x,&z);
f
a la bonne signature, mais le dernier argument à pthread_create(3) doit être de typevoid *
, ors
est unchar *
et donc ce dernier argument doit être(void *) s
.