Des explications suivront quand j'aurai un peu plus de temps. Notez qu'il serait possible d'aller plus loin en définissant une liste sans verrous.
Notez que j'ai utilisé du code C++ 17 pour alléger l'écriture en profitant de CTAD; si votre compilateur n'est pas à jour, remplacez lock_guard et unique_lock par lock_guard<mutex> et unique_lock<mutex> dans les exemples.
Cette version ne laisse circuler que des chaînes de caractères.
#include <string>
#include <mutex>
class zone_transit {
public:
using value_type = std::string;
private:
value_type texte;
std::mutex m;
public:
void ajouter(const value_type &s) {
using namespace std;
lock_guard _ { m };
texte += s;
}
value_type extraire() {
value_type s;
using namespace std;
{
lock_guard _ { m };
s.swap(texte);
}
return s;
}
};
Si on tient compte de la qualification volatile, on a :
#include <string>
#include <mutex>
class zone_transit {
public:
using value_type = std::string;
private:
value_type texte;
std::mutex m;
auto verrouiller() const volatile {
return std::unique_lock { const_cast<mutex&>(m) };
}
public:
void ajouter(const value_type &s) volatile {
auto verrou = verrouiller();
const_cast<zone_transit&>(*this).ajouter(s);
}
void ajouter(const value_type &s) {
texte += s;
}
value_type extraire() {
value_type s;
s.swap(texte);
return s;
}
value_type extraire() volatile {
auto verrou = verrouiller();
return const_cast<zone_transit&>(*this).extraire();
}
};
Cette version fait circuler des instances d'un certain type T.
#include <mutex>
#include <vector>
template <class T>
class zone_transit {
public:
using value_type = T;
private:
std::vector<T> data;
std::mutex m;
public:
template <class It>
void ajouter(It debut, It fin) {
using namespace std;
lock_guard _ { m };
data.insert(data.end(), debut, fin);
}
auto extraire() {
using namespace std;
vector<T> vals;
{
lock_guard _ { m };
vals.swap(data);
}
return vals;
}
};
Voilà.
Un équivalent approximatif en C# pourrait être comme suit (mais prudence avec les risques d'alias sur des objets mutables, possibles aussi en C++ mais plus fréquemment rencontrés dans un langage comme C# où les objets ne sont accédés qu'indirectement). Pour une version naïve qui accepte l'insertion d'un T à la fois :
using System.Collections.Generic;
class ZoneTransit<T>
{
List<T> Data { get; set; } = new();
object Mutex { get; } = new();
public void Ajouter(T src)
{
lock(Mutex)
Data.Add(src);
}
public List<T> Extraire()
{
List<T> rés;
lock(Mutex)
{
rés = Data;
Data = new();
}
return rés;
}
}
Pour une version naïve qui accepte l'insertion de plusieurs T d'un coup :
using System.Collections.Generic;
class ZoneTransit<T>
{
List<T> Data { get; set; } = new();
object Mutex { get; } = new();
public void Ajouter(List<T> src)
{
lock(Mutex)
{
foreach(T obj in src)
Data.Add(obj);
}
}
public List<T> Extraire()
{
List<T> rés;
lock(Mutex)
{
rés = Data;
Data = new();
}
return rés;
}
}
... et pour une version moins naïve :
using System.Collections.Generic;
static class Algos
{
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
class ZoneTransit<T>
{
List<T> data = new();
object Mutex { get; } = new();
public void Ajouter(List<T> src)
{
lock(Mutex)
data.AddRange(src);
}
public List<T> Extraire()
{
List<T> rés = new();
lock(Mutex)
Algos.Swap(ref data, ref rés);
return rés;
}
}