2
votes

Pourquoi ce code mutex ne fonctionne-t-il pas comme prévu?

Il y a un tas de messages et de réponses sur ce sujet, mais aucun ne semble vraiment modéliser mon problème. Après avoir fouillé sur Google et recherché stackoverflow, je ne vois pas tout à fait la réponse à ma question.

J'ai deux threads, un maître et un esclave. L'esclave doit attendre le maître avant de passer un certain point, j'ai donc créé un mutex comme variable globale:

printf("master thread starting lock sequence\n");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("master thread intra lock sequence\n");fflush(stdout);
pthread_mutex_lock(&lock);
printf("master thread completed lock sequence\n");fflush(stdout);

Et puis dans l'initialisation pour le maître thread, bien avant que le thread esclave puisse y accéder, je le verrouille:

initialise dans le thread maître:

printf("slave thread starting lock sequence\n");fflush(stdout);
pthread_mutex_lock(&lock);
printf("slave thread intra lock sequence\n");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("slave thread completed lock sequence\n");fflush(stdout);

Puis dans l'esclave , quand il est temps d'attendre le maître, je fais ceci:

l'esclave doit attendre ici:

pthread_mutex_unlock(&lock);
pthread_mutex_lock(&lock);

En attendant, de retour dans le maître, j'ai ceci quand il est temps de "libérer" l'esclave qui est bloqué en attente:

pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

(Remarque: l'ordre de verrouillage / déverrouillage est inverse pour le maître.)

Quand cela n'a pas fonctionné comme attendu, j'ai jeté quelques printf pour confirmer que le maître se déverrouille, puis re-verrouille le mutex avant que l'esclave ne puisse le déverrouiller. Je crois comprendre que l'esclave a demandé le verrou bien avant que le maître ne soit arrivé pour faire le déverrouillage et le (re) verrouillage, et peu importe le temps écoulé entre le déverrouillage et le (re) verrouillage du maître, l'esclave devrait toujours pouvoir pour verrouiller le mutex parce qu'il a déjà été "en ligne" en attente.

Cependant, ce que je vois se passer, c'est que le maître déverrouille le mutex, puis le reverrouille immédiatement même si l'esclave a été patiemment bloqué en attendant sa chance de le verrouiller.

Voici le code avec les printf et la sortie générée:

slave:

pthread_mutex_lock(&lock)

master:

pthread_mutex_t lock;

Voici maintenant ce que je vois comme sortie:

séquence de verrouillage de démarrage du thread esclave p >

... puis un certain temps s'écoule (plusieurs secondes) pendant que l'esclave est bloqué, et enfin cela apparaît:

séquence de verrouillage de démarrage du thread principal

séquence de verrouillage intra-thread maître

séquence de verrouillage du thread maître terminée

En attendant, il n'y a plus de progrès de l'esclave, qui reste bloqué à jamais. Je me serais attendu à ce qu'il empêche le maître de se refermer et j'aurais craché son empreinte indiquant qu'il avait avancé. Cette sortie indique clairement que l'esclave bloqué n'a pas eu la chance de verrouiller le mutex même s'il attendait patiemment son tour en ligne.

Alors, qu'est-ce qui me manque à propos des mutex et du verrouillage / déverrouiller?

-gt-


4 commentaires

Duplicata du Problème avec la commande ou la planification de verrouillage . Pthreads ne garantit pas l'équité.


"Alors qu'est-ce qui me manque à propos des mutex et du verrouillage / déverrouillage?" D'un rapide survol, il semble que vous essayez de prédire le comportement, mais que vous faites peut-être des hypothèses sur le fonctionnement du planificateur. Quel OS, quel planificateur?


Je pense que ce qui me manque, c'est le «manque de garantie d'équité», comme l'a souligné Raymond. Donc ce n'est pas "premier arrivé, premier servi" comme je l'ai supposé. En d'autres termes, juste parce que l'esclave a déjà fait la queue pour verrouiller le mutex, cela n'empêche pas le maître de le verrouiller à nouveau avant que l'esclave ait sa chance. Je crois que c'est ce qui me manquait. Raymond, ça sonne juste?


Pour le genre de chose que vous souhaitez réaliser, la signalisation via une variable de condition est l'approche classique de pthreads. Lisez simplement la page de manuel de pthread_cond_signal (). Sur mon système, un dérivé de Debian, les pages de manuel pthreads sont disponibles en faisant 'sudo apt install glibc-doc'.


3 Réponses :


0
votes

Comme indiqué dans les commentaires de votre question, les mutex pthreads ne garantissent pas l'équité.

L'outil approprié pour ce travail est une variable d'indicateur partagée, protégée par un mutex et attendue en utilisant une variable de condition:

pthread_mutex_lock(&lock);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);

L'esclave attend le drapeau:

pthread_mutex_lock(&lock);
while (!flag)
    pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);

Lorsque le maître veut libérer l'esclave, il définit le drapeau et signale la condition variable:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int flag = 0;

(Notez que le mutex n'est pas maintenu lorsqu'il est bloqué dans pthread_cond_wait () ). p >


0 commentaires

0
votes

Quand cela ne fonctionnait pas comme prévu, j'ai jeté des printf pour confirmer que le maître se déverrouille, puis reverrouille le mutex avant que l'esclave ne puisse le déverrouiller. Je crois comprendre que l'esclave a demandé le verrou bien avant que le maître n'arrive pour faire le déverrouillage et le (re) verrouillage, et peu importe le temps écoulé entre le déverrouillage et le (re) verrouillage du maître, l'esclave devrait toujours pouvoir pour verrouiller le mutex car il a déjà été "en ligne" en attente.

Il n'y a aucune raison d'être juste envers les threads, ils ne déposent pas de griefs syndicaux si vous les maltraitez. Mais il y a une raison de faire en sorte que le système dans son ensemble fasse autant de travail que possible le plus rapidement possible. L'arrêt d'un thread pour en démarrer un autre a un impact négatif significatif sur les performances car le nouveau thread démarre avec tous les caches froids et la même chose se produit lorsque vous revenez en arrière.

C'est votre travail de vous assurer que votre code, lorsqu'il s'exécute, fait le travail que vous voulez qu'il fasse. Le planificateur essaiera de faire le travail le plus rapidement possible avec seulement un clin d'œil occasionnel à l'équité.


0 commentaires

0
votes

Peut-être que vous pouvez modéliser le problème après le sémaphore, j'ai trouvé que c'est généralement plus facile à comprendre et à implémenter. quelque chose comme l'exemple de pseudo-code suivant.

//slave do something DDD
sem_wait(&data_ready); //slave may sleep here, and maybe interrupted by signal, need to handle that
//slave do something EEE
sem_post(&slave_done);
//slave do something FFF

Dans le thread maître:

//master do something AAA
sem_post(data_ready);
//master do something BBB
sem_wait(slave_done);  //master may sleep here, and maybe interrupted by signal, need to handle that
//master do something CCC

Dans le thread esclave:

//before create slave thread, create semaphore first
sem_t data_ready, slave_done;
sem_init(&data_ready, 0);
sem_init(&slave_done, 0);


0 commentaires