Limites sur les collections sans verrouillage?

10

David Rodríguez - dribeas a écrit dans un commentaire sur StackOverflow que "toutes les collections ne peuvent pas être implémentées sans verrous". Je ne suis pas sûr que ce soit vrai, et je ne trouve aucune preuve de toute façon.

Cette déclaration n'est pas très précise, mais permettez-moi d'essayer de la reformuler de manière légèrement plus formelle: pour chaque type de collection C, il existe un type de collection sans verrou CLF qui offre le même ensemble d'opérations, et où chaque opération sur CLF a la même complexité big-O que l'opération correspondante C.

Au fait, je ne m'attends pas à une transformation.

MSalters
la source
1
En tant que non-expert, je me demande si «sans verrou» peut être défini de manière rigoureuse.
Tsuyoshi Ito du
1
@Suresh: Peut-être un synonyme de «structure de données»?
Tsuyoshi Ito du
2
Et si vous preniez simplement une implémentation sans verrouillage de STM (mémoire transactionnelle logicielle) et implémentiez des structures de données en plus?
Jukka Suomela
5
@Tsuyoshi: Je pense qu'il n'y a pas de définition formelle de verrouillage sans verrou. Informellement, cela signifie que vous n'utilisez pas l'instruction LOCK du CPU, qui est lente, et que vous vous en tenez à la comparaison et à l'échange plus rapide. Puisqu'un LOCK peut être simulé avec compare-and-swap, il est difficile de mettre une limite stricte entre "vous utilisez essentiellement compare-and-swap ici pour simuler un verrou (ou une transaction d'ailleurs)" et "oh, ceci est un une utilisation vraiment intelligente de la comparaison et de l'échange, et ne semble pas du tout comme s'il simule un fonctionnement de niveau supérieur que nous connaissons. "
Radu GRIGore
1
Pour autant que je le comprenne, le verrouillage sans clé est ici synonyme de non-blocage. Cela n'implique pas les LOCKinstructions du CPU mais le planificateur de threads, via mutexes / semaphores / etc.
MSalters

Réponses:

11

Comme j'étais moi-même quelque peu confus, je commence par clarifier quelques concepts de la question.

Collection . Je ne vois aucune raison de passer du temps à définir rigoureusement ce que signifie «collecte» alors que nous pouvons simplement demander ce qui se passe pour les structures de données en général. Une structure de données occupe un morceau de mémoire et comporte certaines opérations qui peuvent accéder à cette mémoire et qui peuvent être invoquées par les utilisateurs . Ces utilisateurs peuvent être des processeurs distincts ou simplement des threads différents, cela ne nous concerne pas. Tout ce qui compte, c'est qu'ils puissent exécuter des opérations en parallèle.

Sans verrou . Herlihy et Boss affirment qu'une structure de données est sans verrouillage lorsqu'un utilisateur en panne n'empêche pas d'autres utilisations de la structure de données. Par exemple, imaginez que l'on verse de l'eau sur un processeur qui est en train d'insérer un nœud dans un ensemble trié. Eh bien, si d'autres processeurs tentent plus tard de s'insérer dans cet ensemble trié, ils devraient réussir. ( Modifier: selon cette définition, il est vrai que si une structure de données utilise des verrous, elle n'est pas sans verrouillage, mais il n'est pas vrai que si une structure de données n'utilise pas de verrous, elle est sans verrouillage.)

Avec ces définitions, je pense que Herlihy et Boss disent essentiellement que la réponse est de transformer les régions critiques en transactions.

Mais, demandez-vous, cela a-t-il la même complexité? Je ne suis pas sûr que la question ait un sens. Considérez push(x) { lock(); stack[size++] = x; unlock(); }. S'agit-il d'une opération à temps constant? Si vous ignorez l'opération de verrouillage et donc les autres utilisateurs, vous pouvez répondre OUI. Si vous ne souhaitez pas ignorer les autres utilisateurs, il n'y a vraiment aucun moyen de dire si le push s'exécutera en temps constant. Si vous montez d'un niveau et voyez comment la pile est utilisée par un algorithme particulier, vous pourrez peut-être dire que la poussée prendra toujours un temps constant (mesuré maintenant en termes de tout ce qui se trouve être l'entrée de votre algorithme parallèle). Mais c'est vraiment une propriété de votre algorithme, il n'est donc pas logique de dire que la poussée est une opération à temps constant.

En résumé, si vous ignorez combien un utilisateur exécutant une opération attend d'autres utilisateurs, l'utilisation de transactions au lieu de régions critiques répond à votre question par l'affirmative. Si vous n'ignorez pas le temps d'attente, vous devez regarder comment la structure de données est utilisée.

Radu GRIGore
la source
Je ne sais pas trop si vous pouvez réellement considérer que l' pushopération comme indiqué ci-dessus n'est pas une opération à temps constant. Pour un nombre fixe de processeurs, et une implémentation commune lockqui ne garantit pas de famine, l'opération ci-dessus (dans le pire des cas, pour un processeur donné prend N_proc * O (1), qui peut naïvement être supposé être O (1) ( nombre de processeurs pris en compte dans la constante cachée.)
David Rodríguez - dribeas
C'est une erreur étonnamment commune de fixer dans et de prétendre ensuite que est constant. L'autre jour, j'ai demandé à certains collègues s'ils pouvaient trouver un algorithme à temps constant pour la fonction de règle , et certains ont objecté que le nombre de bits dans un mot d'ordinateur est constant. (Par conséquent, je suppose que cela était implicite, tous les algorithmes de calcul de la fonction de règle sont à temps constant.) Pour répondre directement à votre commentaire, oui, c'est ce que je voulais dire par `` ignorer les autres utilisateurs '', que nous traitons cette variable comme une constante. f ( n ) fnf(n)f
Radu GRIGore
Eh bien, l'accès à la mémoire en est un exemple courant. La plupart des analyses d'algorithmes supposent que l'accès à la mémoire est O (1) indépendamment de la mémoire utilisée; les architectures de mémoire réelle (avec caches, etc.) sont mieux approchées par O (log N) où N est la mémoire utilisée.
MSalters
Bien que l'hypothèse que le nombre de processeurs soit une constante soit assez pratique, je l'éviterai. Ensuite, le problème est que la complexité ne peut pas être analysée de manière unidimensionnelle, car la taille du problème est appelée à augmenter à la fois dans la taille de l'entrée et le nombre de processeurs, qui sont tous deux des dimensions orthogonales. En supposant qu'un conteneur particulier dans la bibliothèque standard C ++ (j'en choisis évidemment un dur) l'une des exigences est que tous les éléments soient conservés dans un bloc de mémoire contigu.
David Rodríguez - dribeas
Maintenant, l'ajout d'un élément au vecteur est une opération à temps constant amorti (s'il ne tient pas dans le bloc précédemment alloué, l'appel prendra un temps linéaire sur le nombre d'éléments dans le conteneur, mais si le bloc de mémoire réservé est acquis suite à une séquence exponentielle le coût amorti est constant). Si vous implémentez un conteneur thread-safe, vous verrouillez puis effectuez la modification, le coût de l'opération est proportionnel au coût de verrouillage - ce que je ne sais pas vraiment ... mais dans une première approximation, vous pouvez considérer principalement constant
David Rodríguez - dribeas
3

Je pense que "COLLECTIONS" signifie "files d'attente, piles, listes liées, arbres, ..."

Sur http://www.cl.cam.ac.uk/research/srg/netos/lock-free/

Grâce à une conception et une implémentation minutieuses, il est possible de créer des structures de données qui peuvent être utilisées simultanément sans avoir à gérer les verrous ou les threads de bloc. Ces structures de données non bloquantes peuvent augmenter les performances en permettant une concurrence supplémentaire et peuvent améliorer la robustesse en évitant certains des problèmes causés par l'inversion de priorité dans les paramètres locaux ou les défaillances de machine et de liaison dans les systèmes distribués.

La meilleure introduction générale à nos algorithmes non bloquants est la programmation papier simultanée sans verrous, actuellement soumise, qui couvre nos conceptions de comparaison et d'échange de plusieurs mots, la mémoire transactionnelle logicielle basée sur des mots et la mémoire transactionnelle logicielle basée sur des objets.

Si "sans verrou" signifie "n'utilisez pas les sémaphores, mutex, moniteurs, ...", alors je pense (mais je ne suis pas un expert) que chaque collection peut être rendue sans verrou en utilisant la lecture-écriture atomique- modifier les primitives qui doivent être prises en charge par le matériel.

Une surcharge de calcul est nécessaire, mais je pense que pour les structures simples comme les listes, les arbres, les tables de hachage ... la complexité de calcul globale des recherches / insertions / suppressions ne change pas.O()

Une documentation exhaustive sur le sujet est disponible en ligne:

http://www.google.it/search?q=lock+free+algorithm+filetype%3Apdf

(... et autres références à la fin de chaque document)

Marzio De Biasi
la source