2009-02-04 8 views
8

Les variables «booléennes» sont-elles sûres pour la lecture et l'écriture à partir de n'importe quel thread? J'ai vu des références à des groupes de discussion pour dire qu'elles le sont. D'autres types de données sont-ils disponibles? (Des types énumérés, des raccourcis peut-être?)Liste des types de données Delphi avec des opérations de lecture/écriture 'thread-safe'?

Il serait bien d'avoir une liste de tous les types de données qui peuvent être lus en toute sécurité à partir de n'importe quel thread et une autre liste qui peut également être écrite en toute sécurité sans devoir recourir à à diverses méthodes de synchronisation.

Répondre

7

Veuillez noter que vous pouvez faire essentiellement tout dans delphi unthreadsafe. Alors que d'autres mentionnent des problèmes d'alignement sur booléen, cela cache en quelque sorte le vrai problème.

Oui, vous pouvez lire un booléen dans n'importe quel thread et écrire dans un booléen dans n'importe quel thread s'il est correctement aligné. Mais la lecture d'un booléen que vous changez n'est pas nécessairement "thread safe" de toute façon. Supposons que vous ayez défini un booléen sur true lorsque vous avez mis à jour un nombre afin qu'un autre thread lise le nombre.

if NumberUpdated then 
begin 
    LocalNumber = TheNumber; 
end; 

En raison de l'optimisation du processeur fait leNombre peut être lu avant NumberUpdated est lu, donc vous pouvez obtenir l'ancienne valeur de leNombre eventhough vous mis à jour NumberUpdated dernier.

Aka, votre code peut devenir:

temp = TheNumber; 
if NumberUpdated the 
begin 
    LocalNumber = temp; 
end; 

IMHO, une règle de base:
"Reads sont thread-safe Rédige ne sont pas thread-safe."
Donc, si vous allez faire une écriture protéger les données avec la synchronisation partout vous lisez la valeur tandis qu'une écriture peut se produire potentiellement se produire. En revanche, si vous ne lisez et n'écrivez qu'une valeur dans un thread, le thread est sûr. Vous pouvez donc faire une grande partie de l'écriture dans un emplacement temporaire, puis synchroniser une mise à jour des données de l'application.

Bonus texte de présentation:

La VCL est pas thread-safe. Gardez toutes les modifications de choses ui dans le fil principal. Gardez la création de toutes les choses ui dans le fil principal aussi.

De nombreuses fonctions ne sont pas sécurisées par les threads, alors que d'autres le sont, cela dépend souvent des appels winapi sous-jacents.

Je ne pense pas qu'une "liste" serait utile car "thread safe" peut signifier beaucoup de choses.

+0

+1, très bons points. Pour tous ceux qui sont intéressés, consultez par exemple http://en.wikipedia.org/wiki/Memory_barrier et les informations liées. – mghie

5

Sur l'architecture 32 bits, seuls les types de données 32 bits correctement alignés doivent être considérés comme atomiques. Les valeurs de 32 bits doivent être alignées sur 4 (l'adresse des données doit être divisible par quatre). Vous ne courriez probablement pas dans l'entrelacement à un tel niveau, mais théoriquement, vous pourriez avoir une écriture double, Int64 ou non-atomique étendue.

+0

Depuis très longtemps, la lecture/écriture 64 bits alignée est atomique sur le matériel x86, même en mode 32 bits –

+0

Depuis l'introduction des architectures multi-cœurs et multi-processeurs, l'alignement n'est plus une garantie. Une fois, je l'ai prouvé pour moi-même avec des fils très courts qui martelaient des données alignées de manière appropriée avec des écritures, alors que d'autres threads essayaient de les lire. Les lecteurs ont été codés pour soulever des exceptions lorsque les valeurs lues n'étaient pas dans un ensemble de valeurs que les rédacteurs écrivaient. –

7

Il ne s'agit pas d'une question de type de données thread-safe, mais c'est une question de ce que vous faites avec eux. Sans verrouillage, aucune opération n'est thread-safe qui implique de charger une valeur, puis de la modifier, puis de l'écrire: incrémenter ou décrémenter un nombre, effacer ou définir un élément dans un ensemble - ils ne sont pas thread-safe.

Il existe un certain nombre de fonctions qui permettent des opérations atomiques: incrément inter-verrouillé, décrément inter-verrouillé et échange inter-verrouillé. C'est un concept commun, rien de spécifique à Windows, x86 ou Delphi. Pour Delphi, vous pouvez utiliser les fonctions InterlockedFoo() de l'API Windows, il y a plusieurs wrappers autour de ceux-ci. Ou écrivez le vôtre. Les fonctions fonctionnent sur des entiers, de sorte que vous pouvez avoir un incrément atomique, décrément et échange d'entiers (32 bits) avec eux.

Vous pouvez également utiliser les opérations d'assembleur et de préfixe avec le préfixe de verrouillage.

Pour plus d'informations, voir également la question this StackOverflow.

0

Le code Indy contient certains types de données de sécurité atomique/de fil à IdThreadSafe.pas:

  • TIdThreadSafeInteger
  • TIdThreadSafeBoolean
  • TIdThreadSafeString
  • TIdThreadSafeStringList et un peu plus ...
1

Avec le traitement RISC multi-core et le c mal mémoire étant dans le mélange d'un processeur moderne, il n'est plus le cas que toute construction de lecture ou d'écriture de langage de haut niveau "trivial" (ou d'ailleurs de nombreuses instructions d'assemblage 'atomiques' 8086 '' une fois) peut être considéré comme atomique. En effet, à moins qu'une instruction d'assembleur ne soit spécifiquement conçue pour être atomique, elle n'est probablement pas atomique - et cela inclut la plupart des mécanismes de lecture de mémoire. Même un entier long lu au niveau de l'assembleur peut être corrompu par une écriture simultanée à partir d'un autre noyau de processeur qui partage la même mémoire et en utilisant des actions de mise à jour de cache asynchrones au niveau du processeur RISC. Rappelez-vous que sur un processeur comprenant plusieurs cœurs RISC, même les instructions en langage assembleur ne sont en fait que des instructions de code "de niveau supérieur"! Vous ne savez jamais vraiment comment ils sont implémentés au niveau du bit, et ce n'est peut-être pas ce à quoi vous vous attendiez si vous lisiez un ancien manuel de l'assembleur 8086 (single-core). Windows fournit des opérateurs atomiques compatibles avec le système natif, et il serait judicieux de les utiliser plutôt que de faire des hypothèses de base sur les opérations atomiques.

Pourquoi utiliser les opérateurs Windows? Parce que l'une des premières choses que Windows fait est d'établir ce que la machine est en cours d'exécution. L'un des aspects clés pour s'assurer que tout se passe bien est ce que sont les opérations atomiques et comment elles fonctionneront. Si vous voulez que votre code fonctionne bien dans le futur sur n'importe quel futur processeur, vous pouvez dupliquer (et constamment mettre à jour) tous ces efforts dans votre propre code, ou vous pouvez utiliser le fait que Windows l'a déjà fait au démarrage. Il a ensuite incorporé le code nécessaire dans son API lors de l'exécution.

Lisez les pages MSDN sur les opérations atomiques. L'API Windows les présente pour vous. Ils peuvent parfois sembler maladroits ou maladroits - mais ils sont à l'épreuve du temps et ils fonctionneront toujours exactement comme ils le disent sur la boîte.

Comment puis-je le savoir? Eh bien, parce que si ce n'était pas le cas, vous ne pourriez pas utiliser Windows. Arrêt complet. Peu importe d'exécuter votre propre code.

Chaque fois que vous écrivez du code, il est toujours bon de comprendre Parsimony et de considérer Occam's razor. En d'autres termes, si Windows le fait déjà, et que votre code a besoin de Windows pour s'exécuter, utilisez ce que Windows fait déjà, plutôt que d'essayer de nombreuses solutions hypothétiques alternatives et de plus en plus complexes qui peuvent ou non fonctionner. Faire autre chose n'est qu'une perte de temps (à moins bien sûr que c'est ce que vous êtes en train de faire).