TP Systèmes d`exploitation - Linux : Les threads

Transcription

TP Systèmes d`exploitation - Linux : Les threads
TP Systèmes d'exploitation - Linux : Les threads
1
Description
D'après http://mtodorovic.developpez.com/linux/programmation-avancee/?page=page₄
Thread = processus léger
Un thread peut en créer d'autres, tous ces threads exécutent alors le même programme, mais chaque
thread peut exécuter une partie différente du programme à un instant donné.
Contrairement à la création d'un processus fils à partir d'un processus père (avec recopie de toutes
les ressources du père), un thread va partager les ressources (espace mémoire, descripteurs de
fichiers, ..) du thread qui l'a créé (donc attention si un thread ferme un fichier, les autres threads
enfants ou parents ne pourront plus lire et écrire dans ce fichier!). De même si un thread appelle une
des fonctions « exec » alors tous les autres threads se terminent.
Synchronisation de threads : il existe une fonction « join » qui permet de dire au processus qui crée
les threads d'attendre leur fin avant de se terminer.
2
Thread - bibliothèque <thread>
D'après tramonta :http://www.cplusplus.com/reference/
#include <thread>
thread(); //constructeur par défaut
thread(Fn&& fn, Args&&... args) ; où fn est un pointeur sur la fonction à exécuter, et args les
arguments passés à l'appel de la fonction fn.
join(); le « programme » appelant se termine quand l'exécution du thread qui appelle join est
terminé. Après ça le thread appelant devient non-joinable et peut-être détruit sans danger.
3
Les fonctions de la librairie « semaphore.h »
#include <semaphore.h>
int sem_init(sem_t *, int, unsigned);
int sem_destroy(sem_t *) ;
int sem_wait(sem_t *);
int sem_post(sem_t *);
sem_t *sem_open(const char *, int, ...);
int sem_close(sem_t *);
L'utilisation de sémaphores va permettre aux threads de se bloquer en attente de nouvelles tâches.
Un sémaphore est un compteur qui peut être utilisé pour synchroniser plusieurs threads. Chaque
sémaphore dispose d'une valeur de compteur, qui est un entier positif ou nul. 2 opérations de base :
sem_wait :
permet d'attendre en décrémentant la valeur du sémaphore d'une unité. Si la valeur est déjà à 0
l'opération est bloquante jusqu'à ce que la valeur du sémaphore redevienne positive (en raison d'une
opération de la part d'un autre thread). Lorsque la valeur du sémaphore devient positive, elle est
décrémentée d'une unité et l'opération d'attente se termine.
Il existe d'autres fonctions qui permette de ne pas bloquer forcément le processus mais de retourner
un message d'erreur quand la valeur du sémaphore est nulle (sem_trywait()), ou de bloquer le
processus un temps (sem_timedwait())
sem_post:
permet de réveiller en incrémentant la valeur du sémaphore d'une unité. Si le sémaphore était
précédemment à zéro et que d'autres threads étaient en attente sur ce même sémaphore, un de ces
threads est débloqué et son attente se termine (ce qui ramène la valeur du sémaphore à zéro).
sem_init:
permet d'initialiser la variable sem_t représentant le sémaphore utilisé, le 2e paramètre doit être mis
à 0 et le dernier paramètre représente la valeur initiale du sémaphore.
Il est conseillé de détruire un sémaphore qui n'est plus utilisé par la fonction sem_destroy().
Exemple :
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <semaphore.h>
#include <thread>
#include <functional> //std::ref
using namespace std ;
/* PAS FORCEMENT UTILE POUR LES SEMAPHORES */
#define TRUE 1
#define FALSE 0
#define COUNT_DONE 10
int cpt=0;
/* UTILE POUR LES SEMAPHORES */
sem_t mutex0, mutex1;
/* UTILE POUR LES THREADS */
void functionAffiche0()
{
while (TRUE)
{
sem_wait(&mutex0);
cout << "Je suis le thread " << pthread_self() << endl;
sem_post(&mutex1);
/* Pour s'arrêter */
cpt++;
cout << pthread_self() << ">> cpt = " << cpt << endl;
if (cpt > COUNT_DONE) break;
}
}
void functionAffiche1(int arg)
{
int i, result, p;
p = (int) arg;
while (TRUE)
{
sem_wait(&mutex1);
cout << p << ">>Je suis le thread " << pthread_self()<< endl;
for (i=0;i<=5;i++) {
cout << "\t" << i;
}
cout << endl;
sem_post(&mutex0);
/* Pour s'arrêter */
cpt++;
cout << pthread_self() << ">> cpt = " << cpt << endl;
if (cpt > COUNT_DONE) break;
}
}
int main(void)
{
int i=1;
/* initialisation des sémaphores */
sem_init(&mutex0,0,1); //initialisation de mutex0 à 1
sem_init(&mutex1, 0, 0); //initialisation de mutex1 à 0
/* création des threads avec fonction à exécuter */
thread thread0(functionAffiche0);
thread thread1(functionAffiche1,std::ref(i));
/* attente exécution de thread0 et thread1 */
thread0.join();
thread1.join();
/* destruction des sémaphores */
sem_destroy(&mutex0);
sem_destroy(&mutex1);
return 0;
}
Exercice 1 – exercice affichage à l'aide de threads.
Vous reprendrez l'exercice 2 du TP précédent sur les fork(), et créerez 3 processus ou threads qui, à
partir du fichier listeNbPremiers, affichent l'un les 10 premiers nombres, puis l'autre les 10 suivants,
puis le dernier affiche les 10 suivants. Vous utiliserez des sémaphores et les fonctions sem_wait et
sem_post.
4
Mutex - bibliothèque <mutex>
Il existe 5 types de mutex en C++.
Mutex de base
On l'utilise pour une exclusion mutuelle de base, où seulement un thread pourra verrouiller le mutex
et donc pourra accéder aux données partagées, les autres threads seront alors supprimés ou bloqués.
mutex() ; //constructeur de base
~mutex() ; //destructeur
void lock() ; //pour verrouiller les autres threads et si bloqué quand un autre thread l'a déjà bloqué
void try_lock() ; //pour verrouiller les autres threads et si terminé si un autre thread l'a déjà bloqué
void unlock() ; //pour déverrouiller
Mutex avec minuterie
Permet de verrouiller en plus avec un temps imparti, a les comportements de mutex de base, mais
peut aussi se terminer parce que le temps est dépassé.
timed_mutex() ; //constructeur
~timed_mutex() ; //destructeur
lock(), try_lock(), unlock() ; //idem mutex
bool try_lock_for( const std::chrono::duration<Rep,Period>& t ) ; //verrouillé jusqu'à ce que le
temps t soit écoulé (retourne Faux), soit qu'il ait obtenu le verrou (retourne Vrai)
Exemple : http://en.cppreference.com/w/cpp/thread/timed_mutex/try_lock_for
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <sstream>
std::mutex cout_mutex; // control access to std::cout
std::timed_mutex mutex;
void job(int id)
{
using Ms = std::chrono::milliseconds;
std::ostringstream stream;
for (int i = 0; i < 3; ++i) {
if (mutex.try_lock_for(Ms(100))) {
stream << "success ";
std::this_thread::sleep_for(Ms(100));
mutex.unlock();
} else {
stream << "failed ";
}
std::this_thread::sleep_for(Ms(100));
}
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "[" << id << "] " << stream.str() << "\n";
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back(job, i);
for (auto& i: threads)
i.join();
}
Dans les exercices suivants, vous n'oublierez pas d'afficher des messages indiquant les numéros
des threads et leur nom afin de pouvoir vérifier que vos programmes réalisent correctement ce
que vous désirez. Ex : vous donnerez pour chaque lecteur et chaque rédacteur le numéro passé à
chacun d'eux ainsi que le nombre de lecteurs dans la base de données pour chaque lecteur.
Exercice 2 – problème classique : Lecteurs/Rédacteurs
Vous écrirez les programmes permettant de simuler ce problème classique vus en cours et TD, à
l'aide dans un premier temps des sémaphores (fonctions sem_wait et sem_post), puis dans un
deuxième temps à l'aide de mutex simples.
Exercice 3 – problème classique : Producteurs/Consommateurs
Vous écrirez les programmes permettant de simuler ce problème classique vus en cours et TD, à
l'aide dans un premier temps des sémaphores, puis dans un deuxième temps à l'aide de mutex
simples.
Autres types de mutex :
Il existe aussi des mutex qui peuvent être :
•
récursifs « recursive_mutex »: un même thread appelle un tel mutex, va pouvoir le
verrouiller plusieurs fois, mais aucun autre thread ne pourra accéder à la même section
critique tant qu'il n'aura pas appelé la fonction de déverrouillage autant de fois que celle de
verrouillage. On leur applique les même méthodes que le mutex basique. De même on peut
avoir un mutex récursif temporel « recursive_timed_mutex »
•
partagés « shared_time_mutex » : on peut du coup verrouiller ce mutex
◦ soit de manière exclusive (« *lock*() ») et ça revient à verrouiller un type mutex
simple),
◦ soit de manière partagée (« *lock_shared*() ») et les threads verrouillant le mutex de
cette manière peuvent accéder simultanément (en accès partagé) à la section critique,
chaque thread appelant ce mutex doit relâcher le verrou partagé par la méthode
«unlock_shared() » (sinon on ne pourra plus redonner la main au mutex de manière
exclusive).
Exercice 4 – problème classique : Lecteurs/Rédacteurs
Vous réécrirez le programme permettant de simuler ce problème à l'aide de mutex partagé(s).

Documents pareils