IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Delphi Discussion :

Synchronize vraiment threadsafe ?


Sujet :

Delphi

  1. #1
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 830
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 830
    Points : 13 574
    Points
    13 574
    Par défaut Synchronize vraiment threadsafe ?
    Bonjour à tous !

    Lorsque dans un thread (plusieurs instances) on a un code comme celui-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    Synchronize(procedure
                begin
                  inc(SyncCount);
                  // SyncCount = 1 ?
                  Dec(SyncCount);
                end);
    on s'attend à ce que SyncCount soit toujours à 1, que l'appel en cours se termine avant la prochaine synchronisation.

    Sauf que si dans la procédure synchronisée on procède à la destruction d'un TThread, ce dernier attend la fin effective de sa tâche par un WaitFor qui relance un CheckSynchronize !
    Résultat, un deuxième thread peut être autorisé à accéder au principal alors que la synchronisation précédente n'est pas terminée (TMonitor mal placé/utilisé ?) !

    Voici une démo minimale pour illustrer cela. Ne la laissez pas tourner pendant des heures, le nombre de threads augmentant rapidement. Ctr+F2 pour aborter.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    unit Unit1;
     
    interface
     
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
     
    type
      TWorkerThread = class(TThread)
      protected
        procedure Execute; override;
      end;
     
      TSyncThread = class(TThread)
      protected
        procedure Execute; override;
      end;
     
      TForm1 = class(TForm)
        Label1: TLabel;
        procedure FormCreate(Sender: TObject);
      public
        WorkerThread :TWorkerThread;
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
    var SyncCount :integer;
     
    { TWorkerThread }
     
    procedure TWorkerThread.Execute;
    begin
      while not Terminated do
        Sleep(1000);
    end;
     
    { TSyncThread }
     
    procedure TSyncThread.Execute;
    begin
      while not Terminated do
        Synchronize(procedure
                    begin
                      inc(SyncCount);
     
                      try
                        Form1.Label1.Caption := 'SyncCount = ' +SyncCount.ToString;
                        Form1.Label1.Update;
     
                        FreeAndNil(Form1.WorkerThread);
                        Form1.WorkerThread := TWorkerThread.Create;
     
                      finally
                        Dec(SyncCount);
                      end;
                    end);
    end;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      WorkerThread := TWorkerThread.Create;
      TSyncThread.Create;
      TSyncThread.Create;
      TSyncThread.Create;
      TSyncThread.Create;
    end;
     
    end.
    Pour retrouver le comportement logique, il faut englober Synchronize dans une section critique

    Je suis en 10.4.2, est-ce que quelque sur un Delphi plus ancien observe le même comportement ou est-ce une régression ?

  2. #2
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 457
    Points
    28 457
    Par défaut
    tient, c'est un cas de figure intéressant.

    alors c'est "threadsafe" dans le sens, ou tu es dans un cas d'appel récursif ... bien qu'involontaire.

    tu pourrais par exemple tester la valeur de SyncCount sans avoir besoin d'une section critique, if Syncount > 0 then Exit, elle ne risque pas de changer vu que seul le thread principal y touche, mais en effet, quand tu détruits un Thread il y a appel à CheckSynchronize qui peut lancer le Synchronize d'un autre thread, et qui peut aboutir sur la même procédure.

    c'est un peu comme l'appel à Application.ProcessMessages, tu peux recliquer sur le bouton qui fait appel à cette méthode.

    Note que si tu mets une section critique tu vas tomber sur un deadlock

  3. #3
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 830
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 830
    Points : 13 574
    Points
    13 574
    Par défaut
    SyncCount est juste pour la démo à l'instar de la recréation du WorkerThread. Dans l'absolu, il s'agit de traiter des demandes par ordre d'arrivée (dont certaines peuvent entraîner une destruction) et il n'est pas question dans louper une.

    Oui c'est un appel récursif mais qui en soit ne poserait pas de problème s'il n'y avait qu'une SyncList unique. Mais puisque CheckSynchronize la fige et que chaque appel travaille sur la sienne, ça devient problématique.

    Citation Envoyé par Paul TOTH Voir le message
    Note que si tu mets une section critique tu vas tomber sur un deadlock
    A mettre hors Synchronize évidemment

  4. #4
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 457
    Points
    28 457
    Par défaut
    je n'ai sais pas comment est gérée ton affaire, mais normalement les Workers traitent des demandes, ou se mette en attente de demandes quand il n'y en a plus...mais il n'y a pas lieu d'en créer/supprimer des Workers en cours de route.

  5. #5
    Membre expert
    Avatar de pprem
    Homme Profil pro
    MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Inscrit en
    Juin 2013
    Messages
    1 876
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Loiret (Centre)

    Informations professionnelles :
    Activité : MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2013
    Messages : 1 876
    Points : 3 614
    Points
    3 614
    Par défaut
    Oulà, cas typique qui pose problème effectivement, la solution serait dans la suppression différée ou l'auto suppression.

    Le seul truc que nous assure le synchronize() c'est qu'on soit dans le thread principal au moment de son exécution, donc en cas normal un synchronize est le seul à s'exécuter et ils sont censés servir à manipuler les éléments de l'interface.

    Ici je vois plusieurs solutions :
    - faire un lock avant le synchronize, mais ce serait dommage
    - gérer une liste des threads à supprimer, qui pourrait être traitée par un autre thread
    - demander au worker de se supprimer plutôt que le supprimer dans l'autre (appel de terminale ou plus simplement changement de son tag qui pourrait être testé dans la boucle)

  6. #6
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 704
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 704
    Points : 25 574
    Points
    25 574
    Par défaut
    InterlockedIncrement et tu n'auras plus de soucis pour tes compteurs, selon mes tests d'Avril pour justement remplacer des CS, c'est 70x fois plus rapide.
    ça c'est si tu veux gérer un compteur entre plusieurs threads en dehors de ta démo, hors de Synchronize, hors de de CS mais je suppose que tes compteurs c'est juste pour montrer la ré-entrance, ton souhaite réel est de protéger ton Label1 ou de protéger une Thread Factory ?

    Et Synchronize, si je peux l'éviter celui-là je préfére, même si je dois passer par des variables tampons, des TThreadList et OnIdle, pour justement que le temps passé dans Synchronize ne bloque pas les threads qui l'utiliserait trop massivement.
    En gros je communique avec le MainThread qui si il était un thread lambda


    Quel est ton besoin réel ?
    C'est recréer des Threads ?

    J'utilise pour ma part Terminated et un TEvent + Compteur en InterlockedIncrement
    J'ai un moteur qui lance des threads, il augmente le compteur à la création, puis les threads à leur fin décrémente le compteur, le TEvent permet que le lanceur de threads puisse attendre avec un TimeOut + un Réveil forcé.

  7. #7
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 830
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 830
    Points : 13 574
    Points
    13 574
    Par défaut
    C'est du pilotage à distance. Les commandes arrivent par NamedPipe (le TSyncThread de mon exemple) et peuvent créer des éléments graphiques intégrant eux-mêmes leur propre tâche de mise à jour (le TWorkerThread). La suppression d'éléments entraîne logiquement la destruction de leur tâche.

    Mais ces éléments peuvent également n'être que des conteneurs remplis d'autres éléments, d'où le besoin de traiter les demandes dans l'ordre d'arrivée.

    Je vais super-synchroniser les opérations destructives.

    Merci à vous !

  8. #8
    Expert éminent Avatar de sergio_is_back
    Homme Profil pro
    Consultant informatique industrielle, développeur tout-terrain
    Inscrit en
    Juin 2004
    Messages
    1 167
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Consultant informatique industrielle, développeur tout-terrain
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 167
    Points : 6 096
    Points
    6 096
    Par défaut
    Citation Envoyé par pprem Voir le message
    - gérer une liste des threads à supprimer, qui pourrait être traitée par un autre thread
    C'est généralement comme ça que je procède...

    Soit les thread terminés, ferment toutes leurs ressources et montent un flag à True pour demander leur libération qui sera effectuée en décalé
    Soit, quand je le peux, je les utilisent avec FreeOnTerminate:=True (dans le cas d'un Thread "FireAndForget")

  9. #9
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 457
    Points
    28 457
    Par défaut
    Citation Envoyé par Andnotor Voir le message
    C'est du pilotage à distance. Les commandes arrivent par NamedPipe (le TSyncThread de mon exemple) et peuvent créer des éléments graphiques intégrant eux-mêmes leur propre tâche de mise à jour (le TWorkerThread). La suppression d'éléments entraîne logiquement la destruction de leur tâche.

    Mais ces éléments peuvent également n'être que des conteneurs remplis d'autres éléments, d'où le besoin de traiter les demandes dans l'ordre d'arrivée.

    Je vais super-synchroniser les opérations destructives.

    Merci à vous !
    il existe une solution simple sous Windows pour faire un post traitement.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    // dans le Synchronize
     ...
      PostMessage(Handle, WM_USER, 1, NativeInt(Worker));
     ...
     
    procedure TForm1.WMUser(var Msg: TMessage); // message WM_USER
    begin
      case Msg.wParam of
        1: TWorker(Msg.lParam).Free;
      end;
    end;
    dernièrement j'ai aussi utilisé une List Garbage, c'est un ObjetList dans lequel je colle les objets à détruire de façon asynchrone, et c'est fait dans l'évènement Application.OnIdle avec un Garbage.Clear
    [/code]

  10. #10
    Expert éminent Avatar de sergio_is_back
    Homme Profil pro
    Consultant informatique industrielle, développeur tout-terrain
    Inscrit en
    Juin 2004
    Messages
    1 167
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Consultant informatique industrielle, développeur tout-terrain
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 167
    Points : 6 096
    Points
    6 096
    Par défaut
    Citation Envoyé par Paul TOTH Voir le message
    il
    dernièrement j'ai aussi utilisé une List Garbage, c'est un ObjetList dans lequel je colle les objets à détruire de façon asynchrone, et c'est fait dans l'évènement Application.OnIdle avec un Garbage.Clear
    Je note cette astuce à laquelle je n'avais encore pensé !!!!

  11. #11
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 704
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 704
    Points : 25 574
    Points
    25 574
    Par défaut
    Citation Envoyé par pprem Voir le message
    - gérer une liste des threads à supprimer, qui pourrait être traitée par un autre thread
    Citation Envoyé par Paul TOTH Voir le message
    dernièrement j'ai aussi utilisé une List Garbage, c'est un ObjetList dans lequel je colle les objets à détruire de façon asynchrone, et c'est fait dans l'évènement Application.OnIdle avec un Garbage.Clear
    C'est effectivement une des approches que j'utilise sauf que j'utilise une TThreadList comme mentionnée ci-dessus.

    D'ailleurs, le Release qui existait encore dans Seattle permettant de mettre un objet dans le Purgatoire en vue d'une libération différée, par quoi est-il remplacé en Sydney puisque Déprécié

    Et l'on a déjà dit tout ça que le suicide d'objet était à faire avec précaution

  12. #12
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 830
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 830
    Points : 13 574
    Points
    13 574
    Par défaut
    Je vais retravailler mon truc de toute façon mais le fait que terminer un thread suspende la procédure en cours pour exécuter les synchros en attente m'a surpris (pour ne pas dire plus).

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Rsync et synchrone dans les 2 sens vraiment
    Par bigs3232 dans le forum Shell et commandes GNU
    Réponses: 2
    Dernier message: 31/08/2011, 16h34
  2. My.Settings vraiment threadSafe ?
    Par MaelstroeM dans le forum VB.NET
    Réponses: 5
    Dernier message: 20/11/2008, 14h37
  3. Pas de JOIN sous Oracle (vraiment dommage...)
    Par Isildur dans le forum Langage SQL
    Réponses: 7
    Dernier message: 15/03/2007, 12h28
  4. [Thread] Synchronize
    Par Pedro dans le forum Langage
    Réponses: 9
    Dernier message: 06/07/2004, 14h30
  5. Réponses: 6
    Dernier message: 25/03/2002, 22h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo