Comprendre l'approche de Rust en matière de concurrence basée sur le concept de "concurrence intrépide".
La simultanéité est la capacité d'un programme à exécuter plusieurs tâches simultanément sur le même cœur de processeur. Les tâches simultanées s'exécutent et se terminent dans un temps qui se chevauche sans ordre spécifié, contrairement au parallélisme, où diverses tâches ou sous-tâches de la même tâche s'exécutent en même temps sur du matériel avec plusieurs processeurs.
Rust se distingue par ses performances et sa prise en charge de la simultanéité de manière sûre et efficace. L'approche de Rust en matière de concurrence est basée sur le concept de "concurrence sans peur" où le langage vise à faciliter l'écriture en toute sécurité. code concurrent via son système de propriété et d'emprunt qui applique des règles strictes au moment de la compilation pour empêcher les traces de données et assurer la mémoire sécurité.
Comprendre la concurrence dans Rust
Rust fournit plusieurs primitives de concurrence pour écrire des programmes concurrents, notamment les threads, la transmission de messages, les mutex, les types atomiques et async/wait pour la programmation asynchrone.
Voici un aperçu des primitives de concurrence de Rust :
- Fils: Rust fournit une std:: fil module dans sa bibliothèque standard pour créer et gérer les threads. Vous pouvez générer de nouveaux threads avec le fil:: spawn fonction. Le fil:: spawn prend une fermeture contenant le code pour l'exécution. Vous pouvez également exécuter des threads qui peuvent s'exécuter en parallèle, et Rust fournit des primitives de synchronisation pour coordonner leur exécution. Le vérificateur d'emprunt garantit que les références ne conduisent pas à des comportements inattendus.
- Transmission de messages: Le modèle de concurrence de Rust prend en charge la transmission de messages entre les threads. Vous utiliserez les canaux mis en œuvre via le std:: sync:: mpsc module de passage de messages. Un canal se compose d'un émetteur (Expéditeur) et un récepteur (Destinataire). Les threads peuvent envoyer des messages via l'émetteur et les recevoir via le récepteur. Cela fournit un moyen sûr et synchronisé de communiquer entre les threads.
- Mutex et types atomiques: Rust fournit des primitives de synchronisation, y compris des mutex (std:: sync:: Mutex) et les types atomiques (std:: sync:: atomique), afin de garantir un accès exclusif au partage des données. Les mutex permettent à plusieurs threads d'accéder simultanément aux données tout en empêchant les courses de données. Les types atomiques fournissent des opérations atomiques sur des données partagées, telles que l'incrémentation d'un compteur, sans nécessiter de verrouillage explicite.
- Asynchrone/Attente et Futures: Rouille asynchrone/attendre La syntaxe fournit des fonctionnalités pour écrire du code asynchrone que vous pouvez exécuter simultanément. Les programmes asynchrones gèrent efficacement les tâches liées aux E/S, ce qui permet aux programmes d'effectuer d'autres tâches en attendant d'autres opérations d'E/S. De la rouille asynchrone/attendre la syntaxe est basée sur les contrats à terme, et vous pouvez les alimenter avec le asynchrone-std ou Tokyo bibliothèques d'exécution.
Les threads de rouille sont légers et l'absence de surcharge d'exécution les rend bien adaptés aux applications hautes performances. Les primitives de concurrence de Rust s'intègrent de manière transparente à plusieurs bibliothèques et frameworks pour différents besoins de concurrence.
Comment utiliser les threads d'apparition dans Rust
Vous utiliserez le std:: fil module pour générer des threads. Le std:: thread:: spawn La fonction vous permet de créer un nouveau thread qui s'exécutera en même temps que le thread principal ou tout autre thread existant dans votre programme.
Voici comment vous pouvez générer un fil avec le std:: thread:: spawn fonction:
utiliser std:: fil ;
fnprincipal() {
// Crée un nouveau fil
laisser thread_handle = thread:: spawn(|| {
// Le code exécuté dans le nouveau thread va ici
imprimez !("Bonjour du nouveau fil !");
});// Attendre que le thread généré se termine
thread_handle.join().unwrap();
// Le code exécuté dans le thread principal continue ici
imprimez !("Bonjour du fil principal !");
}
Le principal La fonction crée un nouveau thread avec la fil:: spawn fonction en passant dans le thread une fermeture contenant le code de l'exécution (dans ce cas, la fermeture est une fonction anonyme). La fermeture imprime un message indiquant que le nouveau thread est en cours d'exécution.
Le rejoindre méthode sur la thread_handle permet au thread principal d'attendre que le thread généré termine son exécution. En appelant rejoindre, la fonction garantit que le thread principal attend que le thread généré se termine avant de continuer.
Vous pouvez générer plusieurs threads et utiliser une boucle ou tout autre Structure de contrôle de la rouille pour créer plusieurs fermetures et générer des threads pour chacune.
utiliser std:: fil ;
fnprincipal() {
laisser nombre_threads = 5;laissermuet thread_handles = vec![];
pour je dans0..num_threads {
laisser thread_handle = thread:: spawn(déplacer || {
imprimez !("Bonjour du fil {}", je);
});
thread_handles.push (thread_handle);
}pour gérer dans thread_handles {
handle.join().unwrap();
}
imprimez !("Tous les fils sont terminés !");
}
La boucle for génère cinq threads, chacun étant affecté à un identifiant unique je avec la variable de boucle. Les fermetures capturent la valeur de je avec le déplacer mot clé à éviter problèmes de propriété, et le thread_handles vector stocke les threads pour plus tard dans le rejoindre boucle.
Après avoir créé tous les threads, le principal la fonction itère sur la thread_handles vecteur, appels rejoindre sur chaque handle et attend que tous les threads s'exécutent.
Passer des messages à travers les canaux
Vous pouvez transmettre des messages via des fils de discussion avec des canaux. Rust fournit des fonctionnalités pour la transmission de messages dans le std:: sync:: mpsc module. Ici, mpsc signifie "producteur multiple, consommateur unique" et permet la communication entre plusieurs threads en envoyant et en recevant des messages via des canaux.
Voici comment implémenter le message passant par les canaux de communication inter-thread dans vos programmes :
utiliser std:: sync:: mpsc;
utiliser std:: fil ;fnprincipal() {
// Créer un canal
laisser (expéditeur, destinataire) = mpsc:: channel();// Génère un thread
fil:: spawn(déplacer || {
// Envoie un message via le canal
expéditeur.envoyer("Bonjour du fil !").déballer();
});
// Réception du message dans le thread principal
laisser message_reçu = récepteur.recv().unwrap();
imprimez !("Message reçu: {}", message_reçu);
}
Le principal fonction crée un canal avec mpsc:: canal() qui renvoie un expéditeur et un destinataire. Le expéditeur envoie des messages au destinataire qui reçoit les messages. Le principal la fonction procède à la création de threads et déplace la propriété du Expéditeur à la fermeture du fil. À l'intérieur de la fermeture filetée, le expéditeur.send() fonction envoie un message via le canal.
Le récepteur.recv() La fonction reçoit le message en interrompant l'exécution jusqu'à ce que le thread ait reçu le message. Le principal La fonction imprime le message sur la console après une réception réussie du message.
Notez que l'envoi d'un message via le canal consomme l'expéditeur. Si vous devez envoyer des messages à partir de plusieurs threads, vous pouvez cloner l'expéditeur avec le expéditeur.clone() fonction.
De plus, le mpsc module fournit d'autres méthodes comme try_recv(), qui essaie de recevoir un message sans blocage, et iter(), qui crée un itérateur sur les messages reçus.
La transmission de messages via des canaux offre un moyen sûr et pratique de communiquer entre les threads tout en évitant les courses de données et en garantissant une synchronisation correcte.
Le modèle de propriété et d'emprunt de Rust garantit la sécurité de la mémoire
Rust combine la propriété, l'emprunt et le vérificateur d'emprunt pour fournir un cadre de programmation robuste, sûr et simultané.
Le vérificateur d'emprunt agit comme un filet de sécurité, détectant les problèmes potentiels au moment de la compilation plutôt que de s'appuyer sur les vérifications d'exécution ou la récupération de place.