{"id":1212,"date":"2011-10-22T09:12:11","date_gmt":"2011-10-22T08:12:11","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1212"},"modified":"2011-10-22T09:12:11","modified_gmt":"2011-10-22T08:12:11","slug":"prises-de-mutex-et-priorites","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2011\/10\/22\/prises-de-mutex-et-priorites\/","title":{"rendered":"Prises de mutex et priorit\u00e9s"},"content":{"rendered":"<p style=\"text-align: justify; padding-left: 30px;\">Cet article est extrait de la version pr\u00e9paratoire de mon livre \u00e0 venir \u00ab\u00a0<em>Solutions temps-r\u00e9el sous Linux<\/em>\u00ab\u00a0. Le sujet m&rsquo;en a \u00e9t\u00e9 inspir\u00e9 par des exp\u00e9riences r\u00e9alis\u00e9es lors d&rsquo;une r\u00e9cente session de formation (merci entre autres \u00e0 Alejandro, Manuel et Sebastien pour m&rsquo;avoir encourag\u00e9 et aid\u00e9 \u00e0 explorer ce sujet).<\/p>\n<p>&nbsp;<\/p>\n<h1>Prise de mutex en temps-partag\u00e9<\/h1>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;une application multi-t\u00e2ches utilise des ressources partag\u00e9es, il est g\u00e9n\u00e9ralement n\u00e9cessaire d&rsquo;utiliser des m\u00e9canismes de synchronisation afin d&rsquo;\u00e9viter les probl\u00e8mes de concurrence d&rsquo;acc\u00e8s. Dans le cas d&rsquo;un programme multi-threads on utilisera des mutex Posix. Une question peut se poser quand plusieurs threads sont en attente pour tenter de prendre un mutex alors que ce dernier est verrouill\u00e9&nbsp;: qui va l&rsquo;obtenir lorsqu&rsquo;il sera lib\u00e9r\u00e9 par son actuel d\u00e9tenteur&nbsp;?<\/p>\n<p>\n<!--more-->\n<\/p>\n<p style=\"text-align: justify;\">Effectuons un premier essai avec un ensemble de cinq threads en concurrence pour obtenir le m\u00eame mutex. (Les sources se trouvent <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-10-22\/Prise-mutex.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-10-22\/Prise-mutex.tar.bz2\">dans cette archive<\/a>).<\/p>\n<pre><strong>mutex-01.c:<\/strong>\n#include &lt;pthread.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n\nstatic pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;\n\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        usleep(10000 * numero);\n        while (1) {\n                fprintf(stderr, \"[%d] demande le mutexn\", numero);\n                <strong>pthread_mutex_lock(&amp; mutex)<\/strong>;\n                fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n                sleep(1);\n                fprintf(stderr, \"[%d] lache le mutexn\", numero);\n                <strong>pthread_mutex_unlock(&amp; mutex)<\/strong>;\n                usleep(10000);\n        }\n        return NULL;\n}\n\n#define NB_THREADS 5\nint main(void)\n{\n        int i;\n        pthread_t thread[NB_THREADS];\n\n        pthread_mutex_lock(&amp; mutex);\n        for (i = 0; i &lt; NB_THREADS; i ++)\n                pthread_create(&amp; thread[i], NULL, fonction_thread, (void *) (i+1));\n        sleep(1);\n        fprintf(stderr, \"Liberation initiale du mutexn\");\n        pthread_mutex_unlock(&amp; mutex);\n        for (i = 0; i &lt; NB_THREADS; i ++)\n                pthread_join(thread[i], NULL);\n        return EXIT_SUCCESS;\n}<\/pre>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">Les sommeils dans l&rsquo;ex\u00e9cution des threads ont plusieurs r\u00f4le&nbsp;: le premier <code>usleep()<\/code> assure que les threads commencent leurs boucles dans l&rsquo;ordre de cr\u00e9ation, le <code>sleep()<\/code> alors que le mutex est tenu par le thread permet un affichage r\u00e9gulier et lisible des messages. Enfin le dernier <code>usleep()<\/code> garantit que le thread qui vient de l\u00e2cher un mutex ne le r\u00e9clame pas \u00e0 nouveau instantan\u00e9ment. Nous nous concentrerons par la suite sur ce dernier point.<\/p>\n<pre>$ <strong>.\/mutex-01<\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [1] tient le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[...]\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n  <strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n$<\/pre>\n<p style=\"text-align: justify;\">Clairement, le syst\u00e8me lors de la lib\u00e9ration du mutex, le syst\u00e8me l&rsquo;affecte au premier thread dans la file d&rsquo;attente. Comment cela se d\u00e9roule-t-il&nbsp;?<\/p>\n<p style=\"text-align: justify;\">L&rsquo;appel <code>pthread_mutex_lock()<\/code> est avant tout une fonction de la biblioth\u00e8que C. Un syst\u00e8me utilisant la biblioth\u00e8que NPTL incluse dans la GlibC, tire parti des fonctionnalit\u00e9s offertes par le kernel Linux depuis la version 2.6&nbsp;: les Futex (<em>Fast User Space Mutex<\/em>). Lorsqu&rsquo;un thread essaye de verrouiller un mutex, et que ce dernier est libre, la prise du mutex s&rsquo;effectue enti\u00e8rement dans l&rsquo;espace utilisateur (\u00e0 l&rsquo;int\u00e9rieur de la NPTL, sans passer par le kernel). Il n&rsquo;y aura d&rsquo;appel au noyau &#8211; op\u00e9ration plus co\u00fbteuse en temps &#8211; que si le mutex est d\u00e9j\u00e0 verrouill\u00e9. C&rsquo;est une tr\u00e8s bonne optimisation lorsque la protection des donn\u00e9es partag\u00e9es entre les threads n&rsquo;entra\u00eene que rarement des situations de contention. Dans notre cas toutefois, chaque appel <code>pthread_mutex_lock()<\/code> entre dans le noyau et le thread s&rsquo;endort dans une file d&rsquo;attente.<\/p>\n<p style=\"text-align: justify;\">De m\u00eame l&rsquo;appel <code>pthread_mutex_unlock()<\/code> de la NPTL peut d\u00e9verrouiller un mutex en restant simplement dans l&rsquo;espace utilisateur si aucun autre thread n&rsquo;est en attente du m\u00eame mutex. N\u00e9anmoins dans notre exemple chaque lib\u00e9ration du mutex entra\u00eenera un appel-syst\u00e8me qui r\u00e9veillera <em>tous<\/em> les threads en attente et les laissera \u00ab\u00a0lutter\u00a0\u00bb pour l&rsquo;obtention du mutex lors de l&rsquo;ordonnancement suivant.<\/p>\n<p style=\"text-align: justify;\">Dans un ordonnancement temps-partag\u00e9, il est logique que les prises de mutex de l&rsquo;exemple pr\u00e9c\u00e9dent s&rsquo;effectuent dans l&rsquo;ordre d&rsquo;arriv\u00e9e initiale car tous les threads sont sym\u00e9triques. Si toutefois il y avait des diff\u00e9rences entre eux (valeur de <em>nice<\/em>, consommation du CPU, etc.) l&rsquo;ordre ne serait plus garanti. Modifions par exemple la fonction des threads, de mani\u00e8re \u00e0 ce qu&rsquo;un seul d&rsquo;entre-eux (le num\u00e9ro 2) se comporte \u00ab\u00a0bien\u00a0\u00bb vis-\u00e0-vis de l&rsquo;ordonnanceur, les autres r\u00e9clamant le mutex avec insistance en bouclant autour de <code>pthread_mutex_trylock()<\/code>.<\/p>\n<pre><strong>mutex-02.c:<\/strong>\n[...]\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        usleep(10000 * numero);\n        while (1) {\n                fprintf(stderr, \"[%d] demande le mutexn\", numero);\n                if (numero != 2)\n                        <strong>while (pthread_mutex_trylock(&amp; mutex) != 0)<\/strong>\n                                <strong>;<\/strong>\n                else\n                        <strong>pthread_mutex_lock(&amp; mutex)<\/strong>;\n                fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n                sleep(1);\n                fprintf(stderr, \"[%d] lache le mutexn\", numero);\n                pthread_mutex_unlock(&amp; mutex);\n                usleep(10000);\n        }\n        return NULL;\n}\n[...]<\/pre>\n<p style=\"text-align: justify;\">\u00c0 l&rsquo;ex\u00e9cution, le comportement est tr\u00e8s diff\u00e9rent. Le thread num\u00e9ro 2 est visiblement favoris\u00e9 par l&rsquo;ordonnanceur et retrouve le mutex plus souvent que les autres qui sont \u00ab\u00a0punis\u00a0\u00bb par le <em>scheduler<\/em> pour leurs comportements hyst\u00e9riques autour du <code>pthread_mutex_trylock()<\/code>.<\/p>\n<pre>$ <strong>.\/mutex-02 <\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [2] tient le mutex\n[2] lache le mutex\n        [5] tient le mutex\n[2] demande le mutex\n[5] lache le mutex\n        [2] tient le mutex\n[5] demande le mutex\n[2] lache le mutex\n        [4] tient le mutex\n[2] demande le mutex\n[4] lache le mutex\n        [2] tient le mutex\n[4] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [2] tient le mutex\n[3] demande le mutex\n[2] lache le mutex\n        [3] tient le pthread_mutex_trylock()mutex\n[2] demande le mutex\n[3] lache le mutex\n        [2] tient le mutex\n  <strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n$<\/pre>\n<h1>Comportement en temps-r\u00e9el<\/h1>\n<p style=\"text-align: justify;\">Le comportement observ\u00e9 ci-dessus est le fruit d&rsquo;un ordonnancement en temps-partag\u00e9. Nous savons qu&rsquo;il n&rsquo;y a aucune garantie particuli\u00e8re offerte par cet ordonnancement, et que son comportement peut varier d&rsquo;une version \u00e0 l&rsquo;autre de Linux ou de la biblioth\u00e9que C (<em>Glibc<\/em> avec <em>NPTL<\/em> ou <em>uClibc<\/em> avec <em>Linux Threads<\/em>). Pour plus de d\u00e9terminisme, il convient de se tourner vers un ordonnancement temps-r\u00e9el.<\/p>\n<p style=\"text-align: justify;\">Le programme est l\u00e9g\u00e8rement modifi\u00e9 car les threads ne vont plus boucler (sinon le plus prioritaire aura toujours le mutex). Nous allons observer l&rsquo;ordre de prise du mutex lors de la lib\u00e9ration initiale . Commen\u00e7ons avec des threads dont la priorit\u00e9 est \u00e9gale \u00e0 leur num\u00e9ro d&rsquo;ordre de cr\u00e9ation.<\/p>\n<pre><strong>mutex-03.c:<\/strong>\n#include &lt;pthread.h&gt;\n#include &lt;sched.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &amp;ltunistd.h&gt;\n\nstatic pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;\n\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        struct sched_param param;\n\n        param.<strong>sched_priority = numero<\/strong>;\n        if (<strong>pthread_setschedparam(pthread_self(), SCHED_FIFO, &amp; param)<\/strong> != 0) {\n                perror(\"setschedparam\");\n                exit(EXIT_FAILURE);\n        }\n\n        fprintf(stderr, \"[%d] demande le mutexn\", numero);\n        pthread_mutex_lock(&amp; mutex);\n        fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n        sleep(1);\n        fprintf(stderr, \"[%d] lache le mutexn\", numero);\n        pthread_mutex_unlock(&amp; mutex);\n\n        return NULL;\n}\n\n#define NB_THREADS 5\nint main(void)\n{\n        int i;\n        pthread_t thread[NB_THREADS];\n\n        pthread_mutex_lock(&amp; mutex);\n        for (i = 0; i &lt; NB_THREADS; i ++) {\n                pthread_create(&amp; thread[i], NULL, fonction_thread, (void *) (i+1));\n                usleep(10000);\n        }\n        sleep(1);\n        fprintf(stderr, \"Liberation initiale du mutexn\");\n        pthread_mutex_unlock(&amp; mutex);\n        for (i = 0; i &lt; NB_THREADS; i ++)\n                pthread_join(thread[i], NULL);\n        return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Pour que les threads puissent prendre un ordonnancement temps-r\u00e9el, il faut ex\u00e9cuter le programme avec les privil\u00e8ges <em>root<\/em>.<\/p>\n<pre># <strong>.\/mutex-03<\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [5] tient le mutex\n[5] lache le mutex\n        [4] tient le mutex\n[4] lache le mutex\n        [3] tient le mutex\n[3] lache le mutex\n        [2] tient le mutex\n[2] lache le mutex\n        [1] tient le mutex\n[1] lache le mutex\n#<\/pre>\n<p style=\"text-align: justify;\">Les prises successives du mutex se sont bien r\u00e9alis\u00e9es en fonction de la priorit\u00e9 du thread. Inversons les priorit\u00e9s pour v\u00e9rifier que l&rsquo;ordre initial de demande du mutex n&rsquo;influe pas.<\/p>\n<pre><strong>mutex-04.c<\/strong>:\n[...]\n param.<strong>sched_priority = 10 - numero<\/strong>;\n[...]<\/pre>\n<p>A l&rsquo;ex\u00e9cution, l&rsquo;ordre de prise du mutex est identique \u00e0 l&rsquo;exemple pr\u00e9c\u00e9dent.<\/p>\n<pre># <strong>.\/mutex-04<\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [1] tient le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[5] lache le mutex\n#<\/pre>\n<h1>Reprise de mutex en temps-r\u00e9el<\/h1>\n<p style=\"text-align: justify;\">Un probl\u00e8me r\u00e9current dans les syst\u00e8mes embarqu\u00e9s temps-r\u00e9el est la n\u00e9cessit\u00e9 de synchroniser plusieurs threads sur la prise permanente du m\u00eame mutex. Grossi\u00e8rement, nous voudrions que chaque thread puisse faire une boucle infinie&nbsp;:<\/p>\n<pre>while (1)  {\n          pthread_mutex_lock(&amp; mutex);\n          \/\/ Traitement critique\n             [...]\n          pthread_mutex_unlock(&amp; mutex);\n}<\/pre>\n<p style=\"text-align: justify;\">Dans ce cas, tous les threads (dont le nombre peut \u00eatre variable en fonction de l&rsquo;\u00e9volution du syst\u00e8me) seront plac\u00e9s au m\u00eame niveau de priorit\u00e9 et ordonnanc\u00e9s en temps-r\u00e9el <em>Round-Robin<\/em> par exemple. Nous esp\u00e8rons ainsi que chacun d&rsquo;entre-eux pourra successivement recevoir le mutex et effectuer son traitement critique.<\/p>\n<pre><strong>mutex-05.c:<\/strong>\n[...]\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        struct sched_param param;\n\n        param.sched_priority = 10;\n        if (pthread_setschedparam(pthread_self(), SCHED_RR, &amp; param) != 0) {\n                perror(\"setschedparam\");\n                exit(EXIT_FAILURE);\n        }\n\n        while (1) {\n                fprintf(stderr, \"[%d] demande le mutexn\", numero);\n                pthread_mutex_lock(&amp; mutex);\n                fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n                sleep(1);\n                fprintf(stderr, \"[%d] lache le mutexn\", numero);\n                pthread_mutex_unlock(&amp; mutex);\n        }\n\n        return NULL;\n}\n[...]<\/pre>\n<p style=\"text-align: justify;\">L&rsquo;ex\u00e9cution semble pourtant d\u00e9cevante&#8230;<\/p>\n<pre># <strong>.\/mutex-05<\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n[1] lache le mutex\n[1] demande le mutex\n        [1] tient le mutex\n <strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n#<\/pre>\n<p style=\"text-align: justify;\">Comment se fait-il qu&rsquo;un seul thread acc\u00e8de au mutex alors qu&rsquo;ils sont cinq \u00e0 tenter de l&rsquo;obtenir simultan\u00e9ment, et que nous les ayons plac\u00e9s en ordonnancement <em>Round-Robin<\/em> ? Pour comprendre le fonctionnement, reprenons les verrouillage et d\u00e9verouillage des mutex.<\/p>\n<p style=\"text-align: justify;\">Notre thread num\u00e9ro 1 a obtenu le mutex car il \u00e9tait le premier de son niveau de priorit\u00e9 \u00e0 s&rsquo;ex\u00e9cuter. Il lib\u00e8re ensuite le mutex, ce qui se traduit par un appel-syst\u00e8me car le mutex est r\u00e9clam\u00e9 par les quatre autres threads (endormis pour le moment). Durant cet appel-syst\u00e8me, le kernel r\u00e9veille les threads en attente, afin qu&rsquo;ils puissent tenter \u00e0 nouveau leur chance en r\u00e9clamant le mutex. Avant de revenir dans l&rsquo;espace utilisateur, l&rsquo;ordonnanceur est invoqu\u00e9, et ce dernier juge que le thread num\u00e9ro 1, \u00e9tant en temps-r\u00e9el peut continuer \u00e0 s&rsquo;ex\u00e9cuter jusqu&rsquo;\u00e0 ce qu&rsquo;il ait consomm\u00e9 tout le temps CPU allou\u00e9 \u00e0 sa tranche de temps (100 millisecondes). Il faut \u00eatre conscient que le thread ne consomme que tr\u00e8s peu de temps processeur, car il passe l&rsquo;essentiel de son temps en sommeil dans le <code>sleep()<\/code> au milieu de la boucle.<\/p>\n<p style=\"text-align: justify;\">Le thread 1 reprenant son ex\u00e9cution, il peut \u00e0 nouveau r\u00e9clamer le mutex qu&rsquo;il vient juste de lib\u00e9rer, et l&rsquo;obtenir car les autres threads n&rsquo;ont pas pu \u00eatre ordonnanc\u00e9s. Lorsque notre thread va s&rsquo;endormir dans le <code>sleep()<\/code>, ses confr\u00e8res vont demander successivement le mutex &#8211; d\u00e9j\u00e0 verrouill\u00e9 &#8211; et s&rsquo;endormir l&rsquo;un apr\u00e8s l&rsquo;autre.<\/p>\n<p style=\"text-align: justify;\">Pour que les autres threads arrivent \u00e0 prendre le mutex, il faudra attendre que le thread 1 ait consomm\u00e9 l&rsquo;int\u00e9gralit\u00e9 de sa tranche de temps CPU, ce qui n&rsquo;adviendra qu&rsquo;apr\u00e8s un tr\u00e8s grand nombre d&rsquo;it\u00e9rations si nous conservons le sommeil d&rsquo;une seconde. Dans l&rsquo;exemple suivant, nous allons supprimer ce sommeil afin de forcer le thread \u00e0 consommer du temps CPU. Une variable globale contenant le numero du dernier thread ayant pris le mutex permettra de voir les transitions horodat\u00e9es.<\/p>\n<pre><strong>mutex-06.c:<\/strong>\n[...]\n<strong>static int dernier_thread = 0;<\/strong>\n\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        struct sched_param param;\n        struct timeval tv;\n\n        param.sched_priority = 10;\n        if (pthread_setschedparam(pthread_self(), SCHED_RR, &amp; param) != 0) {\n                perror(\"setschedparam\");\n                exit(EXIT_FAILURE);\n        }\n\n        while (1) {\n                pthread_mutex_lock(&amp; mutex);\n                if (<strong>dernier_thread != numero<\/strong>) {\n                        <strong>gettimeofday(&amp; tv, NULL);<\/strong>\n                        fprintf(stderr, \"%ld.%06ld : %d tient le mutexn\",\n                                tv.tv_sec, tv.tv_usec, numero);\n                        <strong>dernier_thread = numero<\/strong>;\n                }\n                pthread_mutex_unlock(&amp; mutex);\n        }\n        return NULL;\n}<\/pre>\n<p style=\"text-align: justify;\">En outre, pour permettre une sortie du programme, un appel-syst\u00e8me <strong>alarm()<\/strong> est ajout\u00e9 dans la fonction <code>main()<\/code>. Ainsi le noyau enverra au processus le signal <code>SIGALRM<\/code> au bout de quinze secondes. Ce signal n&rsquo;\u00e9tant pas g\u00e9r\u00e9, il va tuer notre processus.<\/p>\n<pre>int main(void)\n{\n        [...]\n        sleep(1);\n        <strong>alarm(15);<\/strong>\n        fprintf(stderr, \"Liberation initiale du mutexn\");\n        [...]<\/pre>\n<p>Les messages devront \u00eatre redirig\u00e9s dans un fichier pour \u00eatre consult\u00e9s tranquillement.<\/p>\n<pre># <strong>.\/mutex-06 2&gt; resultat.txt<\/strong>\nMinuterie d'alerte\n# cat resultats.txt\nLiberation initiale du mutex\n1319322465.281375 : 1 tient le mutex\n1319322466.479252 : 4 tient le mutex\n1319322467.479251 : 1 tient le mutex\n1319322467.779246 : 3 tient le mutex\n1319322468.779247 : 1 tient le mutex\n1319322468.879251 : 5 tient le mutex\n1319322469.879246 : 3 tient le mutex\n1319322469.979245 : 1 tient le mutex\n1319322470.079245 : 4 tient le mutex\n1319322470.579252 : 2 tient le mutex\n1319322471.579246 : 4 tient le mutex\n1319322471.679252 : 5 tient le mutex\n1319322471.779245 : 2 tient le mutex\n1319322471.979246 : 1 tient le mutex\n[...]\n#<\/pre>\n<p>Les r\u00e9sultats ne sont gu\u00e8re concluants. Nous aimerions que les threads alternent correctement leurs ex\u00e9cutions autour de ce mutex.<\/p>\n<h1>Solutions<\/h1>\n<h2>Mise en sommeil<\/h2>\n<p style=\"text-align: justify;\">La premi\u00e8re solution, qui vient \u00e0 l&rsquo;esprit des programmeurs habitu\u00e9s aux syst\u00e8mes embarqu\u00e9s temps-r\u00e9el \u00ab\u00a0classiques\u00a0\u00bb et \u00e0 la programmation sur micro-contr\u00f4leurs est d&rsquo;ajouter un petit d\u00e9lai entre le rel\u00e2chement du mutex et sa reprise par le m\u00eame thread.<\/p>\n<p style=\"text-align: justify;\">Typiquement on utilise un appel <code>usleep(10)<\/code> pour demander une mise en sommeil de 10 micro-secondes, voire un appel <code>usleep(0)<\/code> pour forcer un appel au scheduler dans une condition d\u00e9favorable au processus appelant. En voici un exemple.<\/p>\n<pre><strong>mutex-07.c:<\/strong>\n[...]\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        struct sched_param param;\n\n        param.sched_priority = 10;\n        if (pthread_setschedparam(pthread_self(), <strong>SCHED_FIFO<\/strong>, &amp; param) != 0) {\n                perror(\"setschedparam\");\n                exit(EXIT_FAILURE);\n        }\n\n        while (1) {\n                fprintf(stderr, \"[%d] demande le mutexn\", numero);\n                pthread_mutex_lock(&amp; mutex);\n                fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n                sleep(1);\n                fprintf(stderr, \"[%d] lache le mutexn\", numero);\n                pthread_mutex_unlock(&amp; mutex);\n                <strong>usleep(10)<\/strong>;\n        }\n        return NULL;\n}\n[...]<\/pre>\n<p style=\"text-align: justify;\">Cette fois l&rsquo;ordonnancement se fait en mode <em>Fifo<\/em>, afin d&rsquo;\u00e9viter toute pr\u00e9emption involontaire. Cette solution fonctionne, en voici une d\u00e9monstration.<\/p>\n<pre># <strong>.\/mutex-07 <\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [1] tient le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n[...]\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n  <strong>(<em>Contr\u00f4le-C<\/em>)<\/strong>\n#<\/pre>\n<h2>Appel explicite \u00e0 l&rsquo;ordonnanceur<\/h2>\n<p style=\"text-align: justify;\">La solution pr\u00e9c\u00e9dente fonctionne mais n&rsquo;est gu\u00e8re \u00e9l\u00e9gante ni efficace. Si le thread est le seul \u00e0 travailler il effectue incessamment des sommeils inutiles.<\/p>\n<p style=\"text-align: justify;\">Il existe pourtant un appel-syst\u00e8me <code>sched_yield()<\/code> qui permet de forcer un appel \u00e0 l&rsquo;ordonnanceur en demandant \u00e0 \u00eatre plac\u00e9 \u00e0 la fin de la liste des t\u00e2ches de la m\u00eame priorit\u00e9 que l&rsquo;appelant. Si aucune autre t\u00e2che de m\u00eame priorit\u00e9 n&rsquo;est pr\u00eate, le thread appelant est r\u00e9activ\u00e9 imm\u00e9diatement. Voici comment il faut l&rsquo;utiliser en cette circonstance&nbsp;:<\/p>\n<pre><strong>mutex-08.c:<\/strong>\n[...]\nvoid * fonction_thread (void * arg)\n{\n        int numero = (int) arg;\n        struct sched_param param;\n\n        param.sched_priority = 10;\n        if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &amp; param) != 0) {\n                perror(\"setschedparam\");\n                exit(EXIT_FAILURE);\n        }\n\n        while (1) {\n                fprintf(stderr, \"[%d] demande le mutexn\", numero);\n                pthread_mutex_lock(&amp; mutex);\n                fprintf(stderr, \"        [%d] tient le mutexn\", numero);\n                sleep(1);\n                fprintf(stderr, \"[%d] lache le mutexn\", numero);\n                pthread_mutex_unlock(&amp; mutex);\n                <strong>sched_yield();<\/strong>\n        }\n        return NULL;\n}\n[...]<\/pre>\n<p style=\"text-align: justify;\">L&rsquo;ex\u00e9cution confirme bien le comportement attendu.<\/p>\n<pre># <strong>.\/mutex-08<\/strong>\n[1] demande le mutex\n[2] demande le mutex\n[3] demande le mutex\n[4] demande le mutex\n[5] demande le mutex\nLiberation initiale du mutex\n        [1] tient le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[1] demande le mutex\n[2] lache le mutex\n        [3] tient le mutex\n[2] demande le mutex\n[3] lache le mutex\n        [4] tient le mutex\n[3] demande le mutex\n[4] lache le mutex\n        [5] tient le mutex\n[4] demande le mutex\n[5] lache le mutex\n        [1] tient le mutex\n[5] demande le mutex\n[1] lache le mutex\n        [2] tient le mutex\n[...]\n#<\/pre>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">La mise au point d&rsquo;une application temps-r\u00e9el est une chose complexe, et de nombreux \u00ab\u00a0pi\u00e8ges\u00a0\u00bb inattendus peuvent se pr\u00e9senter (<a title=\"\u00c9viter les inversions de priorit\u00e9 caus\u00e9es par des mutex\" href=\"http:\/\/www.blaess.fr\/christophe\/2011\/07\/29\/eviter-les-inversions-de-priorite-causees-par-des-mutex\/\" target=\"_blank\">inversion de priorit\u00e9s<\/a>&#8230;). La prise en boucle d&rsquo;un mutex peut s&rsquo;av\u00e9rer d\u00e9routante et difficilement pr\u00e9dictible.<\/p>\n<p style=\"text-align: justify;\">Le premier r\u00e9flexe est souvent d&rsquo;utiliser des d\u00e9lais, des attentes, pour synchroniser les op\u00e9rations. Cette m\u00e9thode, bien que fonctionnelle dans la plupart des cas, n&rsquo;est pas toujours optimale ni portable. En explorant l&rsquo;API disponible, le programmeur pourra souvent trouver de meilleures solutions (variables-conditions, de-<em>scheduling<\/em> volontaires, etc.). Je vous encourage \u00e0 \u00e9tudier les fonctionnalit\u00e9s d\u00e9crites dans les normes comme Posix ou <a title=\"Single Unix Specification 3\" href=\"http:\/\/pubs.opengroup.org\/onlinepubs\/009604499\/nfindex.html\" target=\"_blank\">SUSv3<\/a> pour tirer le meilleur parti des possibilit\u00e9s de votre syst\u00e8me.<\/p>","protected":false},"excerpt":{"rendered":"<p>Cet article est extrait de la version pr&eacute;paratoire de mon livre &agrave; venir &laquo;&nbsp;Solutions temps-r&eacute;el sous Linux&laquo;&nbsp;. Le sujet m&rsquo;en a &eacute;t&eacute; inspir&eacute; par des exp&eacute;riences r&eacute;alis&eacute;es lors d&rsquo;une r&eacute;cente session de formation (merci entre autres &agrave; Alejandro, Manuel et Sebastien pour m&rsquo;avoir encourag&eacute; et aid&eacute; &agrave; explorer ce sujet). &nbsp; Prise de mutex en [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,14],"tags":[],"class_list":["post-1212","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1212","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=1212"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1212\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1212"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1212"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1212"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}