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...