Serveur d'entrées/ sorties non-bloquantes

Dans un système temps réel (STR), il arrive fréquemment qu'un thread assujetti à des contraintes TR doive réaliser des entrées/ sorties. Il existe des mécanismes pour y arriver, surtout quand un programme s'exécute sur un système d'exploitation TR, mais dans bien des cas, il est nécessaire pour le thread TR de déléguer cette responsabilité à une entité tierce, s'exécutant en parallèle avec lui, pour éviter que le caractère a priori imprévisible des accès aux médias de masse ne vienne empêcher le thread TR de bien faire son travail.

Ce qui suit est un petit exemple simple d'un serveur d'entrées/ sorties non-bloquantes. Les explications viendront.

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <deque>
#include <iterator>
#include <string_view>
#include <mutex>
#include <thread>
#include <atomic>
#include <utility>
#include <optional>
#include <algorithm>
using namespace std;

class ServeurES {
public:
   using jeton = int;
private:
   ServeurES() : oper{ [&] {
      while (!fini) {
         jeton j;
         string nom;
         if (lock_guard _{ m_in }; !a_faire.empty()) {
            auto p = a_faire.front();
            a_faire.pop_front();
            j = p.first;
            nom = p.second;
         }
         if (nom.empty()) continue;
         ifstream in{ nom };
         if (!in) continue; // faudrait améliorer le traitement des erreurs
         vector<char> v{ istreambuf_iterator<char>{in}, istreambuf_iterator<char>{} };
         lock_guard _{ m_out };
         fait.emplace_back(j, std::move(v));
      }
   } } {
   }
   jeton jeton_courant{};
   atomic<bool> fini{ false };
   deque<pair<jeton, string>> a_faire;
   deque<pair<jeton, vector<char>>> fait;
   mutable mutex m_in, m_out;
   thread oper;
public:
   ~ServeurES() {
      fini = true;
      oper.join();
   }
   bool empty() const {
      scoped_lock _{ m_in, m_out };
      return a_faire.empty() && fait.empty();
   }
   auto ajouter_tache(string_view nom) {
      lock_guard _{ m_in };
      a_faire.emplace_back(jeton_courant, string{ begin(nom), end(nom) });
      return jeton_courant++;
   }
   optional<vector<char>> obtenir_resultat(jeton j) {
      lock_guard _{ m_out };
      auto p = find_if(begin(fait), end(fait), [j](auto && p) { return p.first == j; });
      if (p == end(fait)) return {};
      auto v = p->second;
      fait.erase(p);
      return { v };
   }
   ServeurES(const ServeurES&) = delete;
   ServeurES& operator=(const ServeurES&) = delete;
   static auto &get() {
      static ServeurES serveur;
      return serveur;
   }
};

#include <random>
int main() {
   auto & srv = ServeurES::get();
   vector<ServeurES::jeton> v;
   for (int i = 0; i != 10; ++i)
      v.emplace_back(srv.ajouter_tache("principal.cpp"));
   // pour que ce soit plus amusant
   shuffle(begin(v), end(v), mt19937{ random_device{}() });
   // la boucle qui suit ne rend pas justice au modèle : l'idée est de faire des
   // tâches pertinentes, puis de constater à l'occasion la complétion de certaines
   // opérations d'entrée / sortie
   while (!srv.empty())
      for (auto j : v)
         if (auto res = srv.obtenir_resultat(j); res)
            cout << string(70, '-') << "\nJeton " << j << ", fichier:\n" << string_view{ res->data(), res->size() } << endl;
}

En espérant que le tout vous soit utile...


Valid XHTML 1.0 Transitional

CSS Valide !