Déclarer et utiliser des variables globales dans la rouille peut être délicat. En règle générale, pour cette langue, Rust garantit la robustesse en nous forçant à être très explicites.
Dans cet article, je vais discuter des pièges dont le compilateur de rouille veut nous sauver. Ensuite, je vais vous montrer les meilleures solutions disponibles pour différents scénarios.
Il existe de nombreuses options pour implémenter l'état mondial dans la rouille. Si vous êtes pressé, voici un aperçu rapide de mes recommandations.
Vous pouvez passer à des sections spécifiques de cet article via les liens suivants:
Commençons par un exemple de la façon de ne pas utiliser les variables globales. Supposons que je souhaite stocker l'heure de début du programme dans une chaîne mondiale. Plus tard, je souhaite accéder à la valeur à partir de plusieurs threads.
Un débutant de la rouille pourrait être tenté de déclarer une variable globale exactement comme toute autre variable de rouille, en utilisant LET. Le programme complet pourrait alors ressembler à ceci:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Essayez-le par vous-même sur le terrain de jeu!
Il s'agit d'une syntaxe non valide pour la rouille. Le mot-clé LET ne peut pas être utilisé dans la portée globale. Nous ne pouvons utiliser que statique ou const. Ce dernier déclare une véritable constante, pas une variable. Seule statique nous donne une variable globale.
Le raisonnement derrière cela est que LET alloue une variable sur la pile, au moment de l'exécution. Notez que cela reste vrai lors de l'allocation sur le tas, comme dans let t = box :: new ();. Dans le code machine généré, il y a toujours un pointeur dans le tas qui est stocké sur la pile.
Les variables globales sont stockées dans le segment de données du programme. Ils ont une adresse fixe qui ne change pas pendant l'exécution. Par conséquent, le segment de code peut inclure des adresses constantes et ne nécessite aucun espace sur la pile.
D'accord, nous pouvons donc comprendre pourquoi nous avons besoin d'une syntaxe différente. La rouille, en tant que langage de programmation de systèmes modernes, veut être très explicite sur la gestion de la mémoire.
réessayons avec statique:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Le compilateur n'est pas encore content:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
HM, donc la valeur d'initialisation d'une variable statique ne peut pas être calculée au moment de l'exécution. Alors peut-être que ce soit non initialisé?
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
Cela donne une nouvelle erreur:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
donc cela ne fonctionne pas non plus! Toutes les valeurs statiques doivent être entièrement initialisées et valides avant que tout code utilisateur s'exécute.
Si vous venez rouiller d'une autre langue, comme JavaScript ou Python, cela peut sembler inutilement restrictif. Mais tout gourou de C peut vous raconter des histoires sur le fiasco d'ordre d'initialisation statique, ce qui peut conduire à un ordre d'initialisation non défini si nous ne faisons pas attention.
Par exemple, imaginez quelque chose comme ceci:
<span>Compiling playground v0.0.1 (/playground) </span>error<span>: free static item without body </span> <span>--> src/main.rs:21:1 </span> <span>| </span><span>3 | static START_TIME; </span> <span>| ^^^^^^^^^^^^^^^^^- </span> <span>| | </span> <span>| help: provide a definition for the static: `= <expr>;` </span>
Dans cet extrait de code, il n'y a pas d'ordre d'initialisation sûr, en raison des dépendances circulaires.
Si c'était C, qui ne se soucie pas de la sécurité, le résultat serait un: 1 b: 1 C: 2. Dans chaque unité de compilation.
Au moins, il est défini quel est le résultat. Cependant, le «fiasco» démarre lorsque les variables statiques proviennent de différents fichiers .cpp, et donc différentes unités de compilation. Alors l'ordre n'est pas défini et dépend généralement de l'ordre des fichiers dans la ligne de commande de compilation.
Dans la rouille, l'initialisation zéro n'est pas une chose. Après tout, zéro est une valeur non valide pour de nombreux types, comme la boîte. De plus, à Rust, nous n'acceptons pas les problèmes de commande étranges. Tant que nous restons à l'écart de dangereux, le compilateur ne devrait que nous permettre d'écrire du code sain d'esprit. Et c'est pourquoi le compilateur nous empêche d'utiliser l'initialisation de l'exécution simple.
Mais puis-je contourner l'initialisation en utilisant aucun, l'équivalent d'un pointeur nul? Au moins, tout cela est conforme au système de type rouille. Je peux sûrement déplacer l'initialisation en haut de la fonction principale, non?
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Ah, eh bien, l'erreur que nous obtenons est…
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
À ce stade, je pouvais l'envelopper dans un bloc {...} dangereux et cela fonctionnerait. Parfois, il s'agit d'une stratégie valide. Peut-être pour tester si le reste du code fonctionne comme prévu. Mais ce n'est pas la solution idiomatique que je veux vous montrer. Explorons donc les solutions qui sont garanties pour être sûres par le compilateur.
Vous avez peut-être déjà remarqué que cet exemple ne nécessite pas du tout des variables globales. Et le plus souvent, si nous pouvons penser à une solution sans variables globales, nous devons les éviter.
L'idée ici est de mettre la déclaration à l'intérieur de la fonction principale:
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
Le seul problème est l'emprunt-vérificateur:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Cette erreur n'est pas exactement évidente. Le compilateur nous dit que le thread engendré peut vivre plus longtemps que la valeur start_time, qui vit dans le cadre de pile de la fonction principale.
Techniquement, nous pouvons voir que c'est impossible. Les fils sont joints, donc le thread principal ne sortira pas avant la fin des fils d'enfants.
mais le compilateur n'est pas assez intelligent pour comprendre ce cas particulier. En général, lorsqu'un nouveau fil est engendré, la fermeture fournie ne peut emprunter les articles qu'avec une durée de vie statique. En d'autres termes, les valeurs empruntées doivent être vivantes pour toute la durée de vie du programme.
Pour quiconque apprend juste sur la rouille, cela pourrait être le point où vous voulez atteindre les variables mondiales. Mais il y a au moins deux solutions beaucoup plus faciles que cela. Le plus simple est de cloner la valeur de la chaîne, puis de déplacer la propriété des chaînes dans les fermetures. Bien sûr, cela nécessite une allocation supplémentaire et un peu de mémoire supplémentaire. Mais dans ce cas, c'est juste une courte chaîne et rien de critique.
Mais que se passe-t-il si c'était un objet beaucoup plus grand à partager? Si vous ne voulez pas le cloner, enveloppez-le derrière un pointeur intelligent à compte de référence. RC est le type à compte de référence unique. L'arc est la version atomique qui peut partager en toute sécurité les valeurs entre les threads.
Donc, pour satisfaire le compilateur, nous pouvons utiliser l'arc comme suit:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Essayez-le par vous-même sur le terrain de jeu!
Cela a été un aperçu rapide de la façon de partager l'état entre les threads tout en évitant les variables globales. Au-delà de ce que je vous ai montré jusqu'à présent, vous pourriez également avoir besoin de mutabilité intérieure pour modifier l'état partagé. La couverture complète de la mutabilité intérieure est en dehors du cadre de cet article. Mais dans cet exemple particulier, je choisirais l'arc
D'après mon expérience, les cas d'utilisation les plus courants pour l'état mondial ne sont pas des variables mais des constantes. En rouille, ils viennent en deux saveurs:
Les deux peuvent être initialisés avec des constantes de compilation-temps. Ce pourraient être des valeurs simples, telles que 42 ou "Hello World". Ou il pourrait s'agir d'une expression impliquant plusieurs autres constantes et fonctions de temps de compilation marquées comme const. Tant que nous évitons les dépendances circulaires. (Vous pouvez trouver plus de détails sur les expressions constantes dans la référence de la rouille.)
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Habituellement, const est le meilleur choix - à moins que vous ayez besoin de mutabilité intérieure, ou que vous souhaitiez spécifiquement à éviter l'inclinaison.
Si vous avez besoin de mutabilité intérieure, il existe plusieurs options. Pour la plupart des primitives, il existe une variante atomique correspondante disponible en std :: sync :: atomic. Ils fournissent une API propre pour charger, stocker et mettre à jour les valeurs atomiquement.
En l'absence d'atomique, le choix habituel est un verrou. La bibliothèque standard de Rust propose une serrure en lecture-écriture (RWLOCK) et un serrure d'exclusion mutuelle (mutex).
Cependant, si vous devez calculer la valeur à l'exécution ou avoir besoin d'allocation de tas, alors const et statique ne sont d'aucune aide.
La plupart des applications que j'écris n'ont qu'un seul thread. Dans ce cas, un mécanisme de verrouillage n'est pas nécessaire.
Cependant, nous ne devons pas utiliser STATIC MUT directement et envelopper les accès en dangere, simplement parce qu'il n'y a qu'un seul fil. De cette façon, nous pourrions nous retrouver avec une grave corruption de la mémoire.
Par exemple, l'emprunt dangereux à la variable globale pourrait nous donner plusieurs références mutables simultanément. Ensuite, nous pourrions utiliser l'un d'eux pour itérer sur un vecteur et un autre pour supprimer les valeurs du même vecteur. L'itérateur pourrait alors aller au-delà de la limite de mémoire valide, un accident potentiel que la rouille sûre aurait empêché.
mais la bibliothèque standard a un moyen de stocker des valeurs «globalement» pour un accès sûr dans un seul thread. Je parle des locaux de fil. En présence de nombreux threads, chaque thread obtient une copie indépendante de la variable. Mais dans notre cas, avec un seul fil, il n'y en a qu'une seule copie.
Les locaux de thread sont créés avec le thread_local! macro. L'accès à eux nécessite l'utilisation d'une fermeture, comme indiqué dans l'exemple suivant:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Ce n'est pas la plus simple de toutes les solutions. Mais cela nous permet d'effectuer un code d'initialisation arbitraire, qui s'exécutera juste à temps lorsque le premier accès à la valeur se produit.
Les filetages sont vraiment bons en ce qui concerne la mutabilité intérieure. Contrairement à toutes les autres solutions, cela ne nécessite pas de synchronisation. Cela permet d'utiliser RefCell pour la mutabilité intérieure, ce qui évite la surcharge de verrouillage de Mutex.
Les performances absolues des thread-locaux dépendent fortement de la plate-forme. Mais j'ai fait des tests rapides sur mon propre PC en le comparant à la mutabilité intérieure en s'appuyant sur des serrures et je l'ai trouvé 10 fois plus rapide. Je ne m'attends pas à ce que le résultat soit retourné sur n'importe quelle plate-forme, mais assurez-vous d'exécuter vos propres repères si vous vous souciez profondément des performances.
Voici un exemple de la façon d'utiliser la refcell pour la mutabilité intérieure:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Essayez-le par vous-même sur le terrain de jeu!
En tant que note latérale, même si les threads dans WebAssembly sont différents des threads sur une plate-forme X86_64, ce modèle avec Thread_Local! Refcell est également applicable lors de la compilation de la rouille pour fonctionner dans le navigateur. L'utilisation d'une approche qui est sûre pour le code multithread serait exagérée dans ce cas. (Si l'idée de courir de la rouille à l'intérieur du navigateur est nouveau pour vous, n'hésitez pas à lire un article précédent que j'ai écrit intitulé «Rust Tutorial: An Introduction to Rust for JavaScript Devs».)
Une mise en garde sur Thread-Locals est que leur implémentation dépend de la plate-forme. Habituellement, ce n'est rien que vous remarquez, mais sachez que les sémères de drop dépendent de la plate-forme à cause de cela.
Cela dit, les solutions pour les globaux multithreads fonctionnent évidemment également pour les cas unique. Et sans mutabilité intérieure, celles-ci semblent aussi rapides que les filetages.
Alors, jetons un coup d'œil à cela.
Variables globales multithreads avec initialisation d'exécutionL'exemple de la documentation officielle est un bon point de départ. Si vous avez également besoin de mutabilité intérieure, vous devez combiner cette approche avec un verrou à lecture ou un mutex. Voici à quoi cela pourrait ressembler:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Si vous cherchez quelque chose de plus simple, je peux fortement recommander l'une des deux caisses, dont je discuterai dans la section suivante.
Sur la base de la popularité et du goût personnel, je veux recommander deux bibliothèques qui, je pense, sont le meilleur choix pour les variables mondiales faciles à la rouille, à partir de 2021.
Une fois que la cellule est actuellement prise en compte pour la bibliothèque standard. (Voir ce problème de suivi.) Si vous êtes sur un compilateur nocturne, vous pouvez déjà utiliser l'API Unstable pour cela en ajoutant #! [Fonctionnalité (Once_Cell)] à Main.rs.Rs.
de votre projetVoici un exemple utilisant une fois_cell sur un compilateur stable, avec la dépendance supplémentaire:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Essayez-le par vous-même sur le terrain de jeu!
Enfin, il y a aussi paresseux statique, actuellement la caisse la plus populaire pour l'initialisation des variables globales. Il utilise une macro avec une petite extension de syntaxe (Réf. Statique) pour définir les variables globales.
voici à nouveau le même exemple, traduit de moderne_cell à lazy_static:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Essayez-le par vous-même sur le terrain de jeu!
La décision entre une fois_cell et lazy_static se résume essentiellement à la syntaxe que vous aimez mieux.
également, les deux prennent en charge la mutabilité intérieure. Enveloppez simplement la chaîne dans un mutex ou un rwlock.
Ce sont toutes les façons (sensées) de mettre en œuvre des variables globales de rouille dont je connais. Je souhaite que ce soit plus simple. Mais l'état mondial est intrinsèquement complexe. En combinaison avec les garanties de sécurité de la mémoire de Rust, une simple solution de slog-them-tout semble impossible. Mais j'espère que cet article vous a aidé à voir à travers la pléthore des options disponibles.
En général, la communauté de la rouille a tendance à donner une puissance maximale à l'utilisateur - ce qui rend les choses plus compliquées comme un effet secondaire.
Il peut être difficile de suivre tous les détails. En conséquence, je passe une grande partie de mon temps libre à jouer avec des fonctionnalités de rouille pour explorer les possibilités. Dans le processus, je mets généralement en œuvre des projets de passe-temps plus petits ou plus grands - tels que les jeux vidéo - et les télécharge sur mon profil GitHub. Ensuite, si je trouve quelque chose d'intéressant dans mon expérimentation avec la langue, j'écris à ce sujet sur mon blog privé. Vérifiez cela si vous souhaitez lire plus de contenu de rouille approfondi!
Dans la rouille, les statiques et constants sont utilisées pour déclarer des variables globales, mais elles ont des caractéristiques différentes. Un const de rouille est une valeur constante, ce qui signifie que c'est une valeur qui ne va pas changer. Il est similaire à une variable, mais sa valeur est constante et ne peut pas être modifiée. D'un autre côté, une variable statique de Rust est une variable globale stockée dans la section de données en lecture seule du binaire d'un programme. C'est une variable qui est disponible dans l'ensemble du programme, pas seulement la portée dans laquelle il a été déclaré. Contrairement à const, les variables statiques ont une adresse fixe en mémoire.
Dans Rust, vous pouvez déclarer une variable globale en utilisant le mot clé statique. Voici un exemple:
Global statique: i32 = 10;
Dans cet exemple, Global est une variable globale de type i32 et est initialisée avec la valeur 10. N'oubliez pas que les variables statiques sont lues seulement et vous ne pouvez pas les modifier.
Non, vous ne pouvez pas modifier directement une variable globale en rouille car ils sont immuables par défaut. Il s'agit d'une caractéristique de sécurité de la rouille pour empêcher les courses de données au moment de la compilation. Cependant, vous pouvez utiliser Mutex ou RWLOCK dans le module STD :: Sync pour réaliser des variables globales mutables.
Comment puis-je utiliser le 'lazy_static!' Un exemple de la façon dont vous pouvez utiliser la macro 'lazy_static!
}
Dans cet exemple, Global est une variable globale de type Mutex
Dans la rouille, «statique» est utilisée pour déclarer une variable globale en lecture seule, tandis que «Mut statique» est utilisé pour déclarer une variable globale qui est mutable. Cependant, le «mut statique» est dangereux car il peut provoquer un comportement non défini si deux threads accèdent à la variable en même temps.
Comment puis-je accéder en toute sécurité à une variable «mut statique» dans Rust?
Vous pouvez accéder en toute sécurité à une variable «mut statique» en rouille en utilisant le mot-clé «dangereux». Voici un exemple:
statique Mut Global: i32 = 10;
println! ("{} ", Global);
} Dans cet exemple, le mot-clé« dangereux »est utilisé pour indiquer que le code peut effectuer des opérations qui ne seraient pas définies dans le code sûr.
Quelle est la durée de vie d'une variable globale dans la rouille?
La durée de vie d'une variable globale dans la rouille est toute la durée du programme. Cela signifie qu'une variable globale est créée lorsque le programme démarre et est détruit à la fin du programme.
Puis-je utiliser des variables globales dans les fonctions de rouille?
Oui, vous pouvez utiliser des variables globales dans la rouille fonctions. Cependant, vous devez être prudent lorsque vous les utilisez car ils peuvent conduire à des courses de données s'ils ne sont pas utilisés correctement. Il est généralement recommandé d'utiliser des variables locales au lieu des variables globales chaque fois que possible.
Si vous souhaitez partager des données entre les différentes parties de votre programme de rouille, vous pouvez utiliser plusieurs alternatives aux variables globales. Il s'agit notamment de passer des données comme arguments de fonction, de renvoyer les données des fonctions, d'utiliser des structures de données telles que les structures et les énumérations, et l'utilisation de primitives de concurrence comme les canaux et les verrous.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!