Sémaphore sur les événements C ++

Aujourd'hui, je vais parler brièvement de la façon dont j'ai implémenté un sémaphore basé sur l'objet de synchronisation "Event".

Parcourez d'abord les définitions.

1. Qu'est-ce que la synchronisation et pourquoi est-elle nécessaire?


Évidemment, nous pouvons effectuer un ensemble d'actions de plusieurs manières. Le plus simple - séquentiellement et en parallèle. L'implémentation parallèle de certaines actions peut être réalisée en exécutant différents threads. L'idée est simple: attribuer une action élémentaire (ou pas) à chaque thread et les exécuter dans un certain ordre. De manière générale, nous pouvons tous les lancer en même temps - bien sûr, nous gagnerons du temps. C'est compréhensible: c'est une chose de sortir 10 000 mots les uns après les autres, et une autre chose de sortir simultanément, par exemple, 100 mots. 100 fois plus de temps (plus ou moins, hors retards, etc.). Mais la tâche d'origine peut impliquer une séquence stricte d'actions.

Par exemple:

  • Ouvrir un fichier
  • Écrire du texte dans un fichier
  • Fermer le fichier

L'exemple de serre a été spécialement pris (il est clair qu'aucun parallélisme n'est nécessaire ici, tout peut simplement être fait de manière séquentielle), mais en tant que tâche de formation, cela fonctionnera complètement, et surtout, la nécessité d'une exécution cohérente est clairement visible sur son exemple. Ou voici un autre exemple, légèrement différent:

  • Générez trois séquences de nombres aléatoires
  • Affichez-les séquentiellement

Ici, le premier point peut être exécuté simultanément par trois threads différents, mais le dernier, conclusion, doit être fait séquentiellement, et seulement après avoir travaillé sur le premier point.

En général, les tâches de parallélisme peuvent être très différentes et un outil est nécessaire pour synchroniser les threads.

2. Outils pour la synchronisation des threads


Windows.h implémente un grand nombre d'outils de synchronisation réguliers (les soi-disant «objets de synchronisation»). Parmi les principaux sont: région critique, événement, mutex, sémaphore. Oui, pour le sémaphore, il existe déjà une implémentation dans windows.h. "Alors pourquoi le programmer?", Demandez-vous. Eh bien, tout d'abord, pour mieux comprendre comment cela fonctionne. Et, deuxièmement, la pratique supplémentaire de C ++ n'a encore arrêté personne :)

Étant donné que nous utiliserons des événements, nous expliquerons ce que c'est et comment l'utiliser.

Les événements sont utilisés pour notifier les threads en attente. C'est, en fait, c'est un signal pour le flux - il peut être déclenché ou pas encore. De la signification même de cet objet, il s'ensuit qu'il a un certain état de signal et la possibilité de l'ajuster (réinitialiser / «allumer»).

Ainsi, après avoir connecté windows.h, nous pouvons créer un événement avec:

HANDLE CreateEvent ( LPSECURITY_ATTRIBUTES lpEventAttributes, //   BOOL bManualReset, //  : TRUE -  BOOL bInitialState, //  : TRUE -  LPCTSTR lpName //   ); 

Si la fonction réussit, le descripteur d'événement sera renvoyé. Si l'objet n'a pas pu être créé, NULL sera retourné.

Pour changer le statut de l'événement à signaler, nous utilisons la fonction:

 BOOL SetEvent ( HANDLE hEvent //   ); 

En cas de succès, renvoie une valeur différente de zéro.

Maintenant sur le sémaphore. Le sémaphore est conçu pour contrôler le nombre de threads exécutés simultanément. Supposons que nous ayons 1000 threads, mais seulement 2 peuvent fonctionner à la fois. C'est le type d'ajustement qui se produit à l'aide d'un sémaphore. Et quelles fonctions sont implémentées pour fonctionner avec cet objet de synchronisation?

Pour créer un sémaphore, semblable aux événements:

 HANDLE CreateSemaphore ( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //   LONG lInitialCount, //     LONG lMaximumCount, //    LPCTSTR lpName //   ); 

En cas de succès, nous obtenons un pointeur sur le sémaphore, s'il échoue, nous obtenons NULL.

Le compteur du sémaphore est en constante évolution (le thread est en cours d'exécution et un emplacement vide apparaît), donc l'état du sémaphore doit être modifié périodiquement. Cela se fait en utilisant cette fonction:

 BOOL ReleaseSemaphore ( HANDLE hSemaphore, //    LONG lReleaseCount, //     LPLONG lpPreviousCount //   ); 

En cas de succès, la valeur de retour n'est pas nulle.

Il convient également de prêter attention à la fonction:

 DWORD WaitForSingleObject( HANDLE hHandle, //   ,     DWORD dwMilliseconds //     ); 

Parmi les valeurs renvoyées, nous sommes particulièrement intéressés par 2: WAIT_OBJECT_0 - signifie que l'état de notre objet est signal; WAIT_TIMEOUT - nous n'avons pas attendu l'état du signal de l'objet dans le temps imparti.

3. La tâche elle-même


Au total, notre tâche est d'écrire nos analogues sur les fonctions régulières. Nous ne compliquerons pas beaucoup la tâche, nous ferons «la mise en œuvre en première approximation». L'essentiel est de conserver les caractéristiques quantitatives du sémaphore standard. Le code avec des commentaires peut être trouvé sur GitHub .

En raison de la simplicité de la tâche elle-même, nous ne compliquerons pas particulièrement l'article, mais il peut être utile pour quelqu'un :)

Source: https://habr.com/ru/post/fr476940/


All Articles