Questions à choix multiples

Question 1. Utilisation de pthread_create(3)

La fonction pthread_create(3) permet de créer un thread. Parmi les fragments de code ci-dessous, un seul crée correctement un thread qui appelle la fonction 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;
}
Parmi les programmes ci-dessous, un seul calcule correctement la valeur attendue (le test des valeurs de retour des fonctions n’est pas présenté pour garder le code concis). Lequel ?
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. Initialisation de mutex

Avant de pouvoir utiliser un mutex POSIX, il est nécessaire de déclarer la structure correspondante et initialiser le mutex. Parmi les fragments de code ci-dessous, lequel est celui qui déclare et initialise correctement un 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 4. Utilisation de pthread_mutex_lock(3posix) et pthread_mutex_unlock(3posix)

Un programme utilisant plusieurs threads doit mettre à jour une variable globale partagée entre tous les threads. Pour cela, le développeur écrit une fonction 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 5. 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");

}
Ce programme utilise plusieurs threads qui modifient les variables 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);
// thread A

update(&a,&b,&x,&y);
update(&a,&b,&x,&y);

// thread B

update(&b,&a,&y,&x);
update(&a,&c,&x,&z);

Question 6. Utilisation des sémaphores

Avant d’être utilisé, un sémaphore doit être déclaré et initialisé. Après son utilisation, il doit être détruit. Parmi les séquences d’instructions ci-dessous, une seule initialise correctement un sémaphore à la valeur 1. Laquelle ?
sem_t semaphore;

sem_init(&semaphore, 0,1);

// ...

sem_destroy(&semaphore);
sem_t semaphore;

sem_init(semaphore, 1,0);

// ...

sem_destroy(semaphore);
sem_t semaphore;

sem_init(&semaphore, 1,0);

// ...

sem_destroy(&semaphore);
sem_t *semaphore;
semaphore=(sem_t *)malloc(sizeof(struct sem_t));
if (semaphore==NULL)
   error("malloc");

sem_init(semaphore, 1, 0);

// ...

sem_destroy(semaphore);
  sem_t *semaphore;
  semaphore=(sem_t *)malloc(sizeof(struct sem_t));
  if (semaphore==NULL)
     error("malloc");

  sem_init(semaphore, 1, 0);

  // ...

  sem_destroy(&semaphore);

.. comment::

   `sem_init(3)`_ prend comme troisième argument la valeur initiale du sémaphore. `sem_destroy(3)`_ prennent comme premier argument un pointeur vers une structure ``sem_t``.

Question 7. Exclusion mutuelle

Les sémaphores peuvent être utilisés tout comme les mutex pour résoudre des problèmes d’exclusion mutuelle. Parmi les extraits de programmes ci-dessous, une seule est une solution correcte au problème de l’exclusion mutuelle en utilisant des sémaphores. Laquelle ?
static sem_t semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_wait(&semaphore);
    global=increment(global);
    sem_post(&semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;

  sem_init(&semaphore, 0,1);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}
sem_t * semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_wait(semaphore);
    global=increment(global);
    sem_post(semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;
  semaphore=(sem_t *)malloc(sizeof(sem_t))
  if(semaphore==NULL)
    error("malloc");

  sem_init(semaphore, 0,1);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}
static sem_t semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_wait(&semaphore);
    global=increment(global);
    sem_post(&semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;

  sem_init(&semaphore, 0,0);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}
static sem_t semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_post(&semaphore);
    global=increment(global);
    sem_wait(&semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;

  sem_init(&semaphore, 0,0);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}
sem_t * semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_wait(semaphore);
    global=increment(global);
    sem_post(semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;
  semaphore=(sem_t *)malloc(sizeof(sem_t))
  if(semaphore==NULL)
    error("malloc");

  sem_init(semaphore, 0,0);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}
sem_t * semaphore;
long global=0;

int increment(int i) {
   // ...
}

void *inc(void * param) {
  for(int j=0;j<1000000;j++) {
    sem_post(semaphore);
    global=increment(global);
    sem_wait(semaphore);
  }
}

int main (int argc, char *argv[])  {
  pthread_t thread[NTHREADS];
  int err;
  semaphore=(sem_t *)malloc(sizeof(sem_t))
  if(semaphore==NULL)
    error("malloc");

  sem_init(semaphore, 0,0);

  for(int i=0;i<NTHREADS;i++) {
    err=pthread_create(&(thread[i]),NULL,&inc,NULL);
    if(err!=0)
      error(err,"pthread_create");
  }

  // reste non fourni
}

Question 8. Fonctions “thread-safe”

La plupart des fonctions de la librairie standard sont des fonctions thread-safe, c’est-à-dire des fonctions que l’on peut utiliser dans plusieurs threads distincts sans risquer de problèmes d’accès concurrent. Cependant, certaines fonctions, souvent parmi les plus anciennes, ne sont pas thread-safe. Parmi les fonctions ci-dessous, une seule est thread-safe. Laquelle ?

gettimeofday(2)

strsep(3)

getenv(3)

getopt(3)

asctime(3)

strtok(3)

strerror(3)

getopt(3)

Vérifiez vos réponses