Section courante

A propos

Section administrative du site

 Langage  Installation  Elément  Tutoriel  Programmation  Bibliothèque  Cadre d'application  API  GUI  Projet  Composante  IDE  Outils  Annexe  Aide 
ABAP/4
Ada
Assembleur
Assembly & bytecode
ASP (Active Server Pages)
Basic
C
C++
C# (C Sharp)
Cobol
ColdFusion
Fortran
HTML
Java
JavaScript
LISP
Logo
LotusScript
Oberon
Pascal
Perl
PHP
PL/1
Prolog
Python
Rebol
REXX
Ruby
Rust
SAS
NoSQL
SQL
Swift
X++ (Axapta)
GNAT
SMALLAda
VHDL
Assembleur 370
Assembleur 1802
Assembleur 4004
Assembleur 6502
Assembleur 6800
Assembleur 68000
Assembleur 8080 et 8085
Assembleur 8089
Assembleur 80x86
Assembleur AGC4
Assembleur ARM
Assembleur DPS 8000
Assembleur i860
Assembleur Itanium
Assembleur MIPS
Assembleur PDP-11
Assembleur PowerPC
Assembleur RISC-V
Assembleur SPARC
Assembleur SuperH
Assembleur UNIVAC I
Assembleur VAX
Assembleur Z80
Assembleur Z8000
Assembleur z/Architecture
ASSEMBLER/MONITOR 64
Micol Assembler
GFA Assembler
A86
MASM (Macro Assembler)
TASM (Turbo Assembler)
CIL
Jasmin
LLVM
MSIL
Parrot
P-Code (PCode)
SWEET16
G-Pascal
ASP 1.0
ASP 2.0
ASP 3.0
ASP.NET
ASP.NET Core
ABasiC (Amiga)
Adam SmartBASIC
Altair BASIC
AmigaBASIC (Amiga)
AMOS Basic (Amiga)
Atari Basic (Atari 400, 600 XL, 800, 800XL)
Basic Apple II (Integer BASIC/APPLESOFT)
Basic Commodore 64 (CBM-BASIC)
Basic Commodore 128 (BASIC 7.0)
Basic Commodore VIC-20 (CBM-BASIC 2.0)
Basic Coco 1 (Color Basic)
Basic Coco 2 (Extended Color Basic)
Basic Coco 3 (Extended Color Basic 2.0)
BASICA (PC DOS)
Basic Pro
BBC BASIC
Blitz BASIC (Amiga)
DarkBASIC
Dartmouth BASIC
GFA-Basic (Atari ST/Amiga)
GWBASIC (MS-DOS)
Liberty BASIC
Locomotive BASIC (Amstrad CPC)
MSX-Basic
Omikron Basic (Atari ST)
Oric Extended Basic
Power Basic
Quick Basic/QBasic (MS-DOS)
Sinclair BASIC (ZX80, ZX81, ZX Spectrum)
ST BASIC (Atari ST)
Turbo Basic
Vintage BASIC
VBScript
Visual Basic (VB)
Visual Basic .NET (VB .NET)
Visual Basic pour DOS
Yabasic
BeckerBASIC
SIMONS' BASIC
Basic09 d'OS-9
Disk Extended Color Basic
Basic09 d'OS-9
Disk Extended Color Basic
Access
Excel
Visual Basic pour Windows
Visual Basic .NET pour Windows
C Shell Unix (csh)
C pour Amiga
C pour Atari ST
C pour DOS
C pour Falcon030
C pour GEMDOS (Atari ST)
C pour Linux
C pour PowerTV OS
C pour OS/2
C pour Unix
C pour Windows
Aztec C
CoCo-C
GNU C
HiSoft C
IBM C/2
Introl-C
Lattice C
Microsoft C
MinGW C
MSX-C
Open Watcom C
OS-9 C Compiler
Pure C
Quick C
Turbo C
HiSoft C for Atari ST
HiSoft C for CP/M (Amstrad CPC)
C++ pour OS/2
C++ pour Windows
Borland C++
C++Builder
IBM VisualAge C++
Intel C++
MinGW C++
Open Watcom C++
Symantec C++
Turbo C++
Visual C++
Visual C++ .NET
Watcom C++
Zortech C++
C# (C Sharp) pour Windows
Apple III Cobol
Microsoft Cobol
BlueDragon
Lucee
OpenBD
Railo
Smith Project
Microsoft Fortran
WATFOR-77
CSS
FBML
Open Graph
SVG
XML
XSL/XSLT
LESS
SASS
GCJ (GNU)
JSP
Jython
Visual J++
Node.js
TypeScript
AutoLISP
ACSLogo
LotusScript pour Windows
Amiga Oberon
Oberon .NET
Apple Pascal
Delphi/Kylix/Lazarus
Free Pascal
GNU Pascal
HighSpeed Pascal
IBM Personal Computer Pascal
Lisa Pascal
Maxon Pascal
MPW Pascal
OS-9 Pascal
OSS Personal Pascal
Pascal-86
Pascal du Cray Research
Pascal/VS
Pascal-XT
PURE Pascal
QuickPascal
RemObjets Chrome
Sun Pascal
THINK Pascal
Tiny Pascal (TRS-80)
Turbo Pascal
UCSD Pascal
VAX Pascal
Virtual Pascal
Turbo Pascal for CP/M-80
Turbo Pascal for DOS
Turbo Pascal for Macintosh
Turbo Pascal for Windows
CodeIgniter (Cadre d'application)
Drupal (Projet)
Joomla! (Projet)
Phalanger (PHP .NET)
phpBB (Projet)
Smarty (balise)
Twig (balise)
Symfony (Cadre d'application)
WordPress (Projet)
Zend (Cadre d'application)
PL360
PL/M-80
PL/M-86
Turbo Prolog
CPython
IronPython
Jython
PyPy
AREXX
Regina REXX
JMP
Btrieve
Cassandra
Clipper
CouchDB
dBASE
Hbase
Hypertable
MongoDB
Redis
Access
BigQuery
DB2
H2
Interbase
MySQL
Oracle
PostgreSQL
SAP HANA
SQL Server
Sybase
U-SQL
Installation de Free Pascal
Installation de CodeTyphon Studio
Installation de Lazarus
Installation de Pascal XE
Introduction
Les remarques
Les opérateurs
Les instructions conditionnelles
Les instructions de boucles
Les instructions d'exceptions
Type de données élémentaires
Référence des unités
Référence de mots réservés (mots clefs)
Référence de procédures et fonctions
Référence des directives de compilation
Référence de classes
BASEUNIX
CLASSES
CMEM
CRT
CTHREADS
CWSTRING
GRAPH
MATH
SYSTEM
Référence de procédures et fonctions avec prototype
Les jetons
Les constantes
Directives du compilateur
Utiliser des conditions, des messages et des macros
Utilisation du langage de programmation assembleur
Code généré
Prise en charge d'Intel MMX
Problèmes de code
Problèmes de liaison
Problèmes de mémoire
Chaînes de caractères de ressources
Programmation de processus léger
Optimisations
Programmation de bibliothèques partagées
Utiliser les ressources Windows
Bonjour
Affichage
Astronomie
Biochimie
Électricité
Fichiers
Finance
Géographie
Géométrie
Histoire
Jeux & stratégies
Mathématique
Médicale
Météorologie
Océanographie
Sport
Temps
Tri
Trigonométrie
Validation
«DRAW» du BASIC
Phase lunaire
Calcul du calcium corrigé
Calcul le taux d'alcoolémie
Comparaison de séquence de polypeptides
Tarif d'une piscine pour 1 mois
Texte séquentiel
Liste des fichiers
IPaymt/Interet
NPer
PPaymt/Principal
Distance en Km entre deux longitudes et latitudes
Aire d'un cercle
Aire d'une surface de prisme rectangulaire
Aire d'un triangle
Distance entre deux points
Treillis
Chiffre romain
Pac-Man
Tetris
Tours d'Hanois
Ackermann
Exp
Factoriel
Fibonacci
Log
Nombre premier
Odd
Random
Sqrt
Triangle Pascal
Hauteur utérine
Unité de mesure
Fréquence des vagues
Hockey
Année bissextile
Calendrier
Date de la Pâque
Heure courante
Horloge à aiguille
FirstDayOfMonth
Tri à bulle (Bubble Sort)
Tri Shell Sort
ArcCos
ArcSin
Atn/ATan/ArcTan
Cos
Sin
Courriel
Extension de nom de domaine
Ararat Synapse
CryptoLib4Pascal
FBLib (Firebird Pascal Library)
FCL (Free Component Library)
GraphiX
HashLib4Pascal
INDY (Internet Direct)
LCL (Lazarus Component Library)
QuickLib
QuickLogger
WinGraph
ZeosLib
Daraja (cadre d'application pour Web)
Fano Framework
Free Spider
Horse
Synopse mORMot
tiOPF
CAI NEURAL API
tensorflowforpascal
fpGUI
MOS
Corail
Double Commander
LDAP Admin for Linux
MSDOS-0
Transmission Remote GUI
UNIX-0
Python for Delphi (P4D)
CodeTyphon Studio
Free Pascal IDE
Lazarus
Pascal XE
GNU Debugger (GDB)
Delp
FPCMake
H2Pas
PPDep
PPUDump
PPUMove
PtoP
Références des codes d'erreur
Anatomie d'un fichier unité
Structure de l'arborescence des sources du compilateur et RTL
Limites du compilateur
Modes du compilateur
Utiliser fpcmake
Compilation du compilateur
Compilateur définit lors de la compilation
Préface
Notes légal
Dictionnaire
Recherche

Programmation de processus léger

Le Free Pascal prend en charge la programmation des processus léger : il existe une construction de langage disponible pour l'entreposage local des processus légers (ThreadVar), et des routines de processus léger de bas niveau multiplateformes sont disponibles pour les systèmes d'exploitation prenant en charge les processus léger.

Toutes les routines de processus léger sont disponibles dans l'unité centrale, sous la forme d'un gestionnaire de processus léger. Un gestionnaire de processus léger doit implémenter certaines routines de base dont le RTL a besoin pour pouvoir prendre en charge le processus léger. Pour Windows, un gestionnaire de processus léger par défaut est intégré à l'unité centrale. Pour les autres plateformes, un gestionnaire de processus légers doit être inclus explicitement par le programmeur. Sur les systèmes où les processus légers posix sont disponibles, l'unité cthreads implémente un gestionnaire de processus légers utilisant la bibliothèque de processus léger du C de POSIX. Aucune bibliothèque de processus léger Pascal native n'existe pour de tels systèmes.

Bien que cela ne soit pas interdit, il n'est pas recommandé d'utiliser des routines de processus léger spécifiques au système : le support du langage pour les programmes multi-processus léger ne sera pas activé, ce qui signifie que les ThreadVar ne fonctionneront pas, le gestionnaire de mémoire de tas sera confus, ce qui peut entraîner de graves problèmes et des erreurs de programme.

Si aucun support de processus léger n'est présent dans le binaire, l'utilisation de routines de processus léger ou la création d'un processus léger entraînera une exception ou une erreur d'exécution 232.

Pour Linux (et autres Unix), le gestionnaire de processus léger C peut être activé en insérant l'unité cthreads dans la clause Unit du programme. Sans cela, les programmes de processus léger généreront une erreur au démarrage. Il est impératif que l'unité soit insérée le plus tôt possible dans la clause d'utilisation. Ultérieurement, un gestionnaire de processus léger système peut être implémenté pour implémenter des processus légers sans prise en charge de Libc.

Les sections suivantes montrent comment programmer des processus léger et comment protéger l'accès aux données communes à tous les processus léger à l'aide de sections critiques (multiplateformes). Enfin, le gestionnaire de processus léger est expliqué plus en détail.

Processus léger de programmation

Pour démarrer un nouveau processus léger, la fonction BeginThread doit être utilisée. Il a un paramètre obligatoire : la fonction étant exécutée dans le nouveau processus léger. Le résultat de la fonction est le résultat de sortie du processus léger. La fonction de processus léger peut recevoir un pointeur, pouvant être utilisé pour accéder aux données d'initialisation : le programmeur doit s'assurer que les données sont accessibles à partir du processus léger et ne sortent pas de la portée avant que le processus léger n'y ait accédé.

  1. Type
  2.  TThreadFunc=Function(Parameter:Pointer):PtrInt;
  3.  
  4. Function BeginThread(SA:Pointer;StackSize:SizeUInt;ThreadFunction:TThreadFunc;P:Pointer;CreationFlags:DWord;Var ThreadId:TThreadID):TThreadID;

Cette forme complète plutôt compliquée de la fonction se présente également sous des formes plus simplifiées :

  1. Function BeginThread(ThreadFunction:TThreadFunc):TThreadID;
  2. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer):TThreadID;
  3. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer;Var ThreadId:TThreadID):TThreadID;
  4. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer;Var ThreadId:TThreadID;Const StackSize:SizeUInt):TThreadID;

Les paramètres ont la signification suivante :

Paramètre Description
ThreadFunction Est la fonction devant être exécutée dans le processus léger.
P S'il est présent, le pointeur p sera passé à la fonction de processus léger lors de son démarrage. Si p n'est pas spécifié, Nil est transmis.
ThreadID Si ThreadID est présent, l'identificateur du processus léger y sera entreposé.
StackSize S'il est présent, ce paramètre spécifie la taille de la pile utilisée pour le processus léger.
SA Signaler une action. Important pour Linux uniquement.
CreationFlags Ce sont des drapeaux de création spécifiques au système. Important pour Windows et OS/2 uniquement.

Le processus léger nouvellement démarré s'exécutera jusqu'à ce que la ThreadFunction quitte, ou jusqu'à ce qu'il appelle explicitement la fonction EndThread :

  1. {$mode objfpc}
  2.  
  3. Uses SysUtils {$IFDEF unix},cthreads{$ENDIF};
  4.  
  5. Const
  6.  ThreadCount=100;
  7.  StringLen=10000;
  8.  
  9. Var
  10.  Finished:LongInt;
  11.  
  12. Threadvar
  13.  Thri:PtrInt;
  14.  
  15. Function F(P:Pointer):PtrInt;
  16. Var
  17.  S:AnsiString;
  18. Begin
  19.  Writeln('Processus léger ',LongInt(P),' démarré');
  20.  Thri:=0;
  21.  While(Thri<StringLen)do Begin
  22.   S:=S+'1';
  23.   Inc(Thri);
  24.  End;
  25.  Writeln('Processus léger ',LongInt(P),' arrêté');
  26.  InterLockedIncrement(finished);
  27.  F:=0;
  28. End;
  29.  
  30. Var
  31.  I:LongInt;
  32.  
  33. BEGIN
  34.  Finished:=0;
  35.  For I:=1 to ThreadCount do BeginThread(@f,Pointer(I));
  36.  while Finished<ThreadCount do;
  37.  WriteLn(Finished);
  38. END.

InterLockedIncrement est une version thread-safe de la fonction Inc standard.

Pour fournir une prise en charge indépendante du système pour la programmation des processus léger, certaines fonctions utilitaires sont implémentées pour manipuler les processus légers. Pour utiliser ces fonctions, l'identificateur du processus léger doit avoir été récupéré au démarrage du processus léger, car la plupart des fonctions nécessitent l'identificateur pour identifier le processus léger sur lequel elles doivent agir :

  1. Function SuspendThread(ThreadHandle:TThreadID):DWord;
  2. Function ResumeThread(ThreadHandle:TThreadID):DWord;
  3. Function KillThread(ThreadHandle:TThreadID):DWord;
  4. Function WaitForThreadTerminate(ThreadHandle:TThreadID;TimeoutMs:LongInt):DWord;
  5. Function ThreadSetPriority(ThreadHandle:TThreadID;Prio:LongInt):Boolean;
  6. Function ThreadGetPriority(ThreadHandle:TThreadID):Integer;
  7. Function GetCurrentThreadId:DWord;
  8. Procedure ThreadSwitch;

La signification de ces fonctions doit être claire :

Fonction Description
SuspendThread Suspend l'exécution du processus léger.
ResumeThread Reprend l'exécution d'un processus léger suspendu.
KillThread Tue le processus léger : le processus léger est supprimé de la mémoire.
WaitForThreadTerminate Attend que le processus léger se termine. La fonction est renvoyée lorsque le processus léger a fini de s'exécuter ou lorsque le délai d'attente a expiré.
ThreadSetPriority Définit la priorité d'exécution du processus léger. Cet appel n'est pas toujours autorisé : votre processus peut ne pas disposer des autorisations nécessaires pour le faire.
ThreadGetPriority Renvoie la priorité d'exécution actuelle du processus léger.
GetCurrentThreadId Renvoie l'identificateur du processus léger actuel.
ThreadSwitch Permet à d'autres processus léger de s'exécuter à ce stade. Cela signifie que cela peut provoquer un changement de processus léger, mais cela n'est pas garanti, cela dépend du système d'exploitation et du nombre de processeurs.

Sections critiques

Lors de la programmation de processus léger, il est parfois nécessaire d'éviter l'accès simultané à certaines ressources, ou d'éviter qu'une certaine routine soit exécutée par deux processus léger. Cela peut être fait en utilisant une section critique. Le gestionnaire de mémoire de tas FPC utilise des sections critiques lorsque le multi-processus léger est activé.

Le type TRTLCriticalSection est un type Opaque; cela dépend du système d'exploitation sur lequel le code est exécuté. Il doit être initialisé avant sa première utilisation et doit être éliminé lorsqu'il n'est plus nécessaire.

Pour protéger un morceau de code, un appel à EnterCriticalSection doit être effectué : lorsque cet appel revient, il est garanti que le processus léger actuel est le seul à exécuter le code suivant. L'appel peut avoir suspendu le processus léger en cours pour une durée indéterminée pour garantir cela.

Lorsque le code protégé est terminé, LeaveCriticalSection doit être appelé : cela permettra à d'autres processus léger de commencer à exécuter le code protégé. Pour minimiser le temps d'attente des processus léger, il est important de garder le bloc protégé aussi petit que possible.

La définition de ces appels est la suivante :

  1. Procedure InitCriticalSection(Var CS:TRTLCriticalSection);
  2. Procedure DoneCriticalSection(Var CS:TRTLCriticalSection);
  3. Procedure EnterCriticalSection(Var CS:TRTLCriticalSection);
  4. Procedure LeaveCriticalSection(Var CS:TRTLCriticalSection);

La signification de ces appels est encore une fois presque évidente :

Procédure Description
InitCriticalSection Initialise une section critique. Cet appel doit être effectué avant l'utilisation de EnterCriticalSection ou LeaveCriticalSection.
DoneCriticalSection Libère les ressources associées à une section critique. Après cet appel, EnterCriticalSection ou LeaveCriticalSection peuvent être utilisés.
EnterCriticalSection Lorsque cet appel est renvoyé, le processus léger appelant est le seul processus léger exécutant le code entre l'appel EnterCriticalSection et l'appel LeaveCriticalsection suivant.
LeaveCriticalSection Signale que le code protégé peut être exécuté par d'autres processus léger.

Notez que l'appel LeaveCriticalsection doit être exécuté. Ne pas le faire empêchera tous les autres processus léger d'exécuter le code dans la section critique. Il est donc recommandé de placer la section critique dans un bloc Try..Finally. Généralement, le code ressemblera à ceci :

  1. Var
  2.  MyCS:TRTLCriticalSection;
  3.  
  4. Procedure CriticalProc;Begin
  5.  EnterCriticalSection(MyCS);
  6.  Try
  7.   { Code protégé }
  8.  Finally
  9.   LeaveCriticalSection(MyCS);
  10.  End;
  11. End;
  12.  
  13. Procedure ThreadProcedure;Begin
  14.   { Code exécuté dans les processus léger... }
  15.  CriticalProc;
  16.   { Plus de code exécuté dans les processus léger... }
  17. End;
  18.  
  19. BEGIN
  20.  InitCriticalSection(MyCS);
  21.  { Code pour démarrer les processus léger. }
  22.  DoneCriticalSection(MyCS);
  23. END.

Le gestionnaire de processus léger

Tout comme la mémoire de tas est implémenté à l'aide d'un gestionnaire de mémoire de tas et que la gestion des chaînes de caractères Unicode est laissée à un gestionnaire de chaînes de caractères Unicode, les processus léger ont été implémentés à l'aide d'un gestionnaire de processus léger. Cela signifie qu'il existe un enregistrement contenant des champs de type procédural pour toutes les fonctions possibles utilisées dans les routines de processus léger. Les routines de processus léger utilisent ces champs pour effectuer le travail réel.

Les routines de processus léger installent un gestionnaire de processus léger système spécifique à chaque système. Sous Windows, les routines Windows normales sont utilisées pour implémenter les fonctions du gestionnaire de processus léger. Sous Linux et autres Unix, le gestionnaire de processus léger système ne fait rien : il générera une erreur lorsque des routines de processus léger seront utilisées. La raison est que les routines de gestion des processus léger se trouvent dans la bibliothèque C. L'implémentation du gestionnaire de processus léger système rendrait le RTL dépendant de la bibliothèque C, ce qui n'est pas souhaitable. Pour éviter la dépendance à la bibliothèque C, le Thread Manager est implémenté dans une unité distincte (cthreads). Le code d'initialisation de cette unité définit le gestionnaire de processus léger sur un enregistrement de gestionnaire de processus léger utilisant les routines C (pthreads).

L'enregistrement du gestionnaire de processus léger peut être récupéré et défini comme l'enregistrement du gestionnaire de mémoire de tas. Le dossier se présente (actuellement) comme suit :

  1. Type
  2.  TThreadManager=Record
  3.   InitManager:Function:Boolean;
  4.   DoneManager:Function:Boolean;
  5.   BeginThread:TBeginThreadHandler;
  6.   EndThread:TEndThreadHandler;
  7.   SuspendThread:TThreadHandler;
  8.   ResumeThread:TThreadHandler;
  9.   KillThread:TThreadHandler;
  10.   ThreadSwitch:TThreadSwitchHandler;
  11.   WaitForThreadTerminate:TWaitForThreadTerminateHandler;
  12.   ThreadSetPriority:TThreadSetPriorityHandler;
  13.   ThreadGetPriority:TThreadGetPriorityHandler;
  14.   GetCurrentThreadId:TGetCurrentThreadIdHandler;
  15.   InitCriticalSection:TCriticalSectionHandler;
  16.   DoneCriticalSection:TCriticalSectionHandler;
  17.   EnterCriticalSection:TCriticalSectionHandler;
  18.   LeaveCriticalSection:TCriticalSectionHandler;
  19.   InitThreadVar:TInitThreadVarHandler;
  20.   RelocateThreadVar:TRelocateThreadVarHandler;
  21.   AllocateThreadVars:TAllocateThreadVarsHandler;
  22.   ReleaseThreadVars:TReleaseThreadVarsHandler;
  23.  End;

La signification de la plupart de ces fonctions devrait ressortir clairement des descriptions des sections précédentes.

InitManager et DoneManager sont appelés lorsque le gestionnaire de processus léger est défini (InitManager) ou lorsqu'il n'est pas défini (DoneManager). Ils peuvent être utilisés pour initialiser le gestionnaire de processus léger ou pour nettoyer une fois l'opération terminée. Si l'un d'eux renvoie False, l'opération échoue.

Il y a quelques entrées spéciales dans l'enregistrement, liées à la gestion des variables de processus léger :

Champ Description
InitThreadVar Ce champ est appelé lorsqu'une variable de processus léger doit être initialisée. Sa syntaxe est du genre :

TInitThreadVarHandler=Procedure(Var Offset:DWord;Size:DWord);

Le paramètre Offset indique le déplacement dans le bloc de variables de processus léger : Toutes les variables de processus léger sont situées dans un seul bloc, les unes après les autres. Le paramètre Size indique la taille de la variable thread. Cette fonction sera appelée une fois pour toutes les variables de processus léger du programme.
RelocateThreadVar Ce champ est appelé à chaque démarrage d'un processus léger, et une fois pour le processus léger principal. Il est du type :

TRelocateThreadVarHandler=Function(Offset:DWord):Pointer;

Il doit renvoyer le nouvel emplacement de la variable locale du processus léger.
AllocateThreadVars Ce champ est appelé lorsque de l'espace doit être alloué à toutes les variables de processus léger pour un nouveau processus léger. C'est une procédure simple, sans paramètres. La taille totale des ThreadVars est entreposée par le compilateur dans la variable globale ThreadVarBlockSize. Le gestionnaire de mémoire de tas ne peut pas être utilisé dans cette procédure : le gestionnaire de mémoire de tas lui-même utilise des ThreadVars n'ayant pas encore été alloués.
ReleaseThreadVars Cette procédure (sans paramètres) est appelée lorsqu'un processus léger se termine et toute la mémoire allouée doit être à nouveau libérée.


PARTAGER CETTE PAGE SUR
Dernière mise à jour : Dimanche, le 27 août 2023