Pour un tampon circulaire traditionnel d'un bloc, je pense que cela ne peut pas être fait en toute sécurité avec des opérations atomiques. Vous devez faire tellement en une seule lecture. Supposons que vous avez une structure qui a ceci:
uint8_t* buf;
unsigned int size; // Actual max. buffer size
unsigned int length; // Actual stored data length (suppose in write prohibited from being > size)
unsigned int offset; // Start of current stored data
Sur une lecture que vous devez faire ce qui suit (ce qui est la façon dont je mis en œuvre de toute façon, vous pouvez échanger quelques pas comme je vais ensuite discuter):
- Vérifiez si la longueur de lecture ne dépasse pas la longueur enregistrée
- Vérifiez si la longueur offset + lecture ne dépassent pas les limites des tampons
- données lues
- Augmenter offset, diminuer sa longueur
Que devriez-vous certainement faire synchronisé (si atomique) pour que cela fonctionne? En fait, combiner les étapes 1 et 4 en une seule étape atomique, ou de préciser: faire synchronisé:
- chèque read_length, cela peut être STH comme
read_length=min(read_length,length);
- longueur diminuer avec read_length:
length-=read_length
- obtenir une copie locale de l'offset
unsigned int local_offset = offset
- augmentation compensée par read_length:
offset+=read_length
Ensuite, vous pouvez simplement faire un memcpy (ou w hatever) à partir de votre local_offset, vérifiez si votre lecture dépasse la taille du buffer circulaire (scindée en 2 memcpy's), .... Ceci est "assez" sécurisé, votre méthode d'écriture pourrait encore écrire sur la mémoire que vous lisez, donc assurez-vous que votre tampon est vraiment assez grand pour minimiser cette possibilité. Maintenant, bien que j'imagine que vous pouvez combiner 3 et 4 (je suppose que c'est ce qu'ils font dans le cas de la liste liée) ou même 1 et 2 dans les opérations atomiques, je ne peux pas vous voir faire tout ça en une seule fois. opération :).
Vous pouvez cependant essayer de laisser tomber la vérification de la longueur si vos consommateurs sont très intelligents et sauront toujours quoi lire. Vous auriez également besoin d'une nouvelle variable woffset, car l'ancienne méthode de (offset + length)% size pour déterminer le décalage d'écriture ne fonctionnerait plus. Notez que ceci est proche du cas d'une liste chaînée, où vous lisez toujours un élément (= fixe, taille connue) de la liste.Ici aussi, si vous en faites une liste circulaire, vous pouvez lire beaucoup ou écrire dans une position que vous lisez à ce moment-là! Enfin: mon conseil, il suffit d'aller avec des verrous, j'utilise une classe CircularBuffer, complètement sûr pour lire & écriture) pour un streamer vidéo en temps réel 720p60 et je n'ai pas de problèmes de vitesse du tout de verrouillage.
tampon limitée taille signifie que le producteur peut échouer s'il n'y a pas d'espace vide en elle. Est-ce acceptable pour vous? – doublep
Notez également qu'en C++ vous pouvez fournir votre propre allocateur à 'std :: list'. Puisque vous n'avez qu'un seul producteur, cet allocateur n'a pas besoin d'être synchronisé. Par exemple, il peut «allouer» des nœuds de liste à partir d'un tampon pré-alloué et, lorsqu'il manque d'espace, allouer un nouveau tampon avec un allocateur global «malloc()» synchrone, comme l'allocateur «réel». Ce qui signifie qu'il utilisera la synchronisation, disons 1% des appels seulement. – doublep
tcmalloc est une excellente bibliothèque à regarder si vous cherchez à optimiser l'utilisation de la mémoire pour les threads. Comme il gère les pools de mémoire pour chaque thread, il évite probablement le problème de sérialisation de la routine de mémoire. –