Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

La concurrence extensible avec Send et Sync

Fait intéressant, presque toutes les fonctionnalités de concurrence dont nous avons parlé jusqu’à présent dans ce chapitre font partie de la bibliothèque standard, pas du langage lui-même. Vos options pour gérer la concurrence ne se limitent pas au langage ou à la bibliothèque standard ; vous pouvez écrire vos propres fonctionnalités de concurrence ou utiliser celles écrites par d’autres.

Cependant, parmi les concepts clés de concurrence qui sont intégrés dans le langage plutôt que dans la bibliothèque standard, on trouve les traits marqueurs std::marker Send et Sync.

Transférer la possession entre les threads

Le trait marqueur Send indique que la possession des valeurs du type implémentant Send peut être transférée entre les threads. Presque tous les types Rust implémentent Send, mais il existe quelques exceptions, notamment Rc<T> : celui-ci ne peut pas implémenter Send car si vous cloniez une valeur Rc<T> et tentiez de transférer la possession du clone à un autre thread, les deux threads pourraient mettre à jour le compteur de références en même temps. Pour cette raison, Rc<T> est conçu pour être utilisé dans des situations mono-thread où vous ne voulez pas payer la pénalité de performance liée à la sécurité des threads.

Par conséquent, le système de types de Rust et les contraintes de traits garantissent que vous ne pouvez jamais envoyer accidentellement une valeur Rc<T> entre les threads de manière non sûre. Lorsque nous avons essayé de le faire dans l’encart 16-14, nous avons obtenu l’erreur the trait `Send` is not implemented for `Rc<Mutex<i32>>`. Lorsque nous sommes passés à Arc<T>, qui implémente bien Send, le code a compilé.

Tout type composé entièrement de types Send est automatiquement marqué comme Send également. Presque tous les types primitifs sont Send, à l’exception des pointeurs bruts, dont nous parlerons au chapitre 20.

Accéder depuis plusieurs threads

Le trait marqueur Sync indique qu’il est sûr pour le type implémentant Sync d’être référencé depuis plusieurs threads. En d’autres termes, tout type T implémente Sync si &T (une référence immuable vers T) implémente Send, ce qui signifie que la référence peut être envoyée en toute sécurité vers un autre thread. De manière similaire à Send, les types primitifs implémentent tous Sync, et les types composés entièrement de types qui implémentent Sync implémentent également Sync.

Le pointeur intelligent Rc<T> n’implémente pas non plus Sync pour les mêmes raisons qu’il n’implémente pas Send. Le type RefCell<T> (dont nous avons parlé au chapitre 15) et la famille de types Cell<T> associés n’implémentent pas Sync. L’implémentation de la vérification des emprunts que RefCell<T> effectue à l’exécution n’est pas sûre pour les threads. Le pointeur intelligent Mutex<T> implémente Sync et peut être utilisé pour partager l’accès avec plusieurs threads, comme vous l’avez vu dans [“Accès partagé à Mutex<T>”][shared-access].

Implémenter Send et Sync manuellement n’est pas sûr

Comme les types composés entièrement d’autres types qui implémentent les traits Send et Sync implémentent aussi automatiquement Send et Sync, nous n’avons pas besoin d’implémenter ces traits manuellement. En tant que traits marqueurs, ils n’ont même pas de méthodes à implémenter. Ils sont simplement utiles pour appliquer des invariants liés à la concurrence.

Implémenter ces traits manuellement implique d’écrire du code Rust non sûr (unsafe). Nous parlerons de l’utilisation du code Rust unsafe au chapitre 20 ; pour l’instant, l’information importante est que construire de nouveaux types concurrents qui ne sont pas composés de parties Send et Sync nécessite une réflexion approfondie pour respecter les garanties de sécurité. [“The Rustonomicon”][nomicon] contient plus d’informations sur ces garanties et comment les respecter.

Résumé

Ce n’est pas la dernière fois que vous verrez la concurrence dans ce livre : le prochain chapitre se concentre sur la programmation asynchrone, et le projet du chapitre 21 utilisera les concepts de ce chapitre dans une situation plus réaliste que les petits exemples abordés ici.

Comme mentionné précédemment, étant donné que très peu de la façon dont Rust gère la concurrence fait partie du langage, de nombreuses solutions de concurrence sont implémentées sous forme de crates. Celles-ci évoluent plus rapidement que la bibliothèque standard, alors n’hésitez pas à chercher en ligne les crates actuelles et à la pointe pour les situations multi-threads.

La bibliothèque standard de Rust fournit des canaux pour le passage de messages et des types de pointeurs intelligents, tels que Mutex<T> et Arc<T>, qui sont sûrs à utiliser dans des contextes concurrents. Le système de types et le vérificateur d’emprunts garantissent que le code utilisant ces solutions ne se retrouvera pas avec des courses de données ou des références invalides. Une fois que votre code compilé, vous pouvez être assuré qu’il fonctionnera sans problème sur plusieurs threads, sans les types de bogues difficiles à traquer qui sont courants dans d’autres langages. La programmation concurrente n’est plus un concept à craindre : allez de l’avant et rendez vos programmes concurrents, sans crainte !