Table des matières du kit Noyau | Index du kit du Noyau |
Les sections suivantes fournissent des exemples d'usage typique de sémaphore. Pour toute la lumière sur les sémaphores, consultez Sémaphores.
L'usage le plus typique d'un sémaphore est la protection d'un morceau de code qui ne peut être exécuté que par un thread à la fois. Le sémaphore agit comme un verrou; acquire_sem() verrouille le code, release_sem() le libère. Les sémaphores qui sont utilisés comme verrous sont (presque toujours) créés avec un compteur (de thread) à 1.
Comme exemple simple, disons que vous conservez une valeur maximum ainsi :
/* max_val est une variable globale. */ uint32 max_val = 0; ... /* bump_max() met à jour la valeur maxi, si nécessaire. */ void bump_max(uint32 new_value) { if (new_value > max_value) max_value = new_value; }
bump_max() n'est pas sûre (thread safe) en cas de multi-threads; Il y a une possible compétition d'exécution entre la comparaison et l'affectation. Donc nous la protegeons avec un sémaphore :
sem_id max_sem; uint32 max_val = 0; ... /* Initialize le sémaphore dans une routine de démarrage. */ status_t init() { if ((max_sem = create_sem(1, "max_sem")) < B_NO_ERROR) return B_ERROR; ... } void bump_max(uint32 new_value) { if (acquire_sem(max_sem) != B_NO_ERROR) return; if (new_value > max_value) max_value = new_value; release_sem(); }
Un "benaphore" est une combinaison d'une variable atomique (variable dont l'unicité d'accès est garantie) et un sémaphore qui peut améliorer l'efficacité d'un verrouillage. Si vous utilisez un sémaphore comme montré dans l'exemple précédent, vous devriez considerer l'usage d'un benaphore à la place (si vous pouvez).
L'exemple précédent ré-écrit pour utiliser un benaphore :
sem_id max_sem; uint32 max_val = 0; int32 ben_val = 0; status_t init() { /* Cette fois on initialize le sémaphore à 0 */ if ((max_sem = create_sem(0, "max_sem")) < B_NO_ERROR) return B_ERROR; ... } void bump_max(uint32 new_value) { int32 previous = atomic_add(&ben_val, 1); if (previous >= 1) if (acquire_sem(max_sem) != B_NO_ERROR) goto get_out; if (new_value > max_value) max_value = new_value; get_out: previous = atomic_add(&ben_val, -1); if (previous > 1) release_sem(max_sem); }
Le point, ici, est que acquire_sem() est appellée uniquement si l'on sait (en vérifiant la valeur précédente de ben_val) qu'un autre thread est au milieu de la section critique du code. Du côté libération, release_sem() n'est appellée uniquement si un autre thread est depuis entré dans la fonction (et est maintenant bloqué dans l'appel à acquire_sem()). Un point important, ici, est que la sémaphore est initialisée à 0.
Les sémaphores peuvent aussi servir à coordonner des threads réalisant des opérations séparées, mais nécessitant de réaliser ces opérations dans un ordre particulier. Dans l'exemple suivant, nous avons un buffer global qui est accèdé par des fonctions de lecture et d'écriture. En outre, nous voulons que les écritures et les lectures alternent, avec une écriture en premier.
Nous pouvons verrouiller le buffer entier avec un seul sémaphore, mais pour forcer l'alternance nous avons besoin de deux sémaphores :
sem_id write_sem, read_sem; char buffer[1024]; /* Initialize les sémaphores */ status_t init() { if ((write_sem = create_sem(1, "write")) < B_NO_ERROR) { return; if ((read_sem = create_sem(0, "read")) < B_NO_ERROR) { delete_sem(write_sem); return; } } status_t write_buffer(const char *src) { if (acquire_sem(write_sem) != B_NO_ERROR) return B_ERROR; strncpy(buffer, src, 1024); release_sem(read_sem); } status_t read_buffer(char *dest, size_t len) { if (acquire_sem(read_sem) != B_NO_ERROR) return B_ERROR; strncpy(dest, buffer, len); release_sem(write_sem); }
Les valeurs initiales des compteurs permettent de s'assurer que le buffer sera d'abord écrit avant d'être lu : si une lecture arrive avant une écriture, la lecture bloquera jusqu'à ce que l'écriture libère le sémaphore read_sem.
Table des matières du kit Noyau | Index du kit du Noyau |