AccueilActualités informatiqueLangage de programmation : Swift 5.5 bouleverse la concurrence du langage

Langage de programmation : Swift 5.5 bouleverse la concurrence du langage

Le langage de programmation open source d’Apple, Swift, est sorti en version 5.5 et apporte de nombreuses innovations pour la programmation concurrente. En plus du modèle Async/Await, la version présente la Concurrence structurée, les séquences asynchrones et le modèle Acteur.

Sommaire

La version 5.4, sortie il y a cinq mois, contenait déjà les premiers préparatifs du nouveau modèle de concurrence, qui sont maintenant intégrés à Swift dans de nombreuses propositions. La proposition SE-0296 introduit le motif Async/Await pour mettre en œuvre des coroutines analogues aux langages de programmation tels que C#, Kotlin, C++20 ou JavaScript.

Le mot-clé async déclare une fonction comme asynchrone, et l’appel est effectué via le mot-clé await. La fonction peut interrompre son exécution à tout moment, par exemple pour ne pas bloquer le flux du programme pendant les transactions du réseau en attendant les données entrantes.

Le compilateur se charge de l’implémentation, de sorte que les développeurs peuvent écrire du code asynchrone analogue au code synchrone sans routines de vérification supplémentaires, comme dans l’exemple suivant tiré de la proposition :

func loadWebResource(_ path: String) async throws -> Resource
func 
 decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image

func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = 
    try await decodeImage(dataResource, imageResource)
  let imageResult   = try await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

L’implémentation réelle des programmes concurrents se trouve dans une autre nouvelle fonctionnalité de Swift 5.5 : la SE-0304 introduit la concurrence structurée. Il permet de diviser les processus séquentiels en structures parallèles. À titre d’exemple, la proposition évoque la préparation d’un dîner, où certaines étapes peuvent être mises en parallèle tandis que d’autres doivent attendre. Par exemple, une personne peut faire mariner la viande pendant qu’une autre coupe les légumes. Au plus tard, cependant, toutes les tâches doivent converger lorsqu’il s’agit de servir.

Les tâches constituent la base du traitement parallèle. Selon la proposition, dans Swift 5.5, une tâche se comporte comme une fonction asynchrone, comme un thread se comporte comme une fonction synchrone. Chaque tâche a l’un des trois états suivants : Il est en cours, suspendu ou terminé. Le site TaskPriority définit la priorité de la tâche respective comme high, medium, low ou background . Alternativement, les niveaux spécifiques à Apple userInitiated pour high et utility pour low utiliser

A TaskGroup peut regrouper plusieurs tâches en Tâches enfant. Le groupe ne termine son travail et ne revient que lorsque tous les enfants ont terminé leur exécution. Les enfants héritent de la priorité de leurs parents. En dehors de l’exécution régulière, une résiliation peut intervenir : L’appel de cancel() abandonne manuellement la tâche, et une erreur inattendue dans une tâche parentale entraîne l’abandon des tâches enfant non terminées.

Des méthodes supplémentaires vous permettent de contrôler une tâche : Utilisation de sleep() il se met en pause pendant une durée déterminée, et suspend() le met explicitement en pause pour donner à d’autres tâches le temps de s’exécuter.

En outre, la proposition SE-0298 introduit l’élément suivant AsyncSequence-qui correspond essentiellement au protocole synchrone de l’UE. Sequence-mais il est conçu pour des valeurs qui n’arrivent qu’au fil du temps. En raison du traitement asynchrone, entre autres, la next()-dans le cadre de la AsyncSequence comme async doivent être définis. L’itération sur la séquence est possible, par exemple, avec for try await n in MySequence() est possible.

Une autre proposition jette un pont vers les applications concurrentes en Objective-C : la SE-0297 définit la traduction des gestionnaires d’achèvement d’Objective-C en async-en Swift. À l’inverse, dans ce dernier langage de programmation, les méthodes asynchrones peuvent être traduites par l’intermédiaire de @objc comme des appels depuis l’Objective-C, en les passant comme des méthodes de gestion de l’achèvement.

La proposition SE-0306 introduit enfin le modèle Acteur dans Swift. Cette proposition a été formulée peu après la sortie de Swift 5.3. Il s’agit d’un concept permettant d’éviter les pièges typiques de la programmation parallèle, comme les courses de données. Dans le modèle, les actionneurs se comportent de la même manière que les classes, mais protègent les valeurs variables de l’accès externe. Les messages remplacent les appels de fonction directs, alors que l’accès direct n’est possible qu’à partir de self est autorisé. Le compilateur se charge de l’isolation des actionneurs.

L’acteur se charge du traitement dès qu’il le peut, sans conditions de course potentielles. Chaque acteur dispose de sa propre boîte aux lettres ou file d’attente de messages qui conserve tous les messages jusqu’à ce qu’il puisse les traiter. Les acteurs ont chacun leur propre boîte de réception et traitent les messages de manière séquentielle. Pour éviter les situations de concurrence, aucun traitement parallèle du code isolé des acteurs n’a lieu.

Les pièges de la programmation parallèle : conditions de course et courses de données

Dans la programmation concurrente, les conditions de course sont un problème typique de comportement non déterministe : si deux threads fonctionnant en parallèle effectuent des calculs sur une variable globale, par exemple, des erreurs peuvent se produire, mais pas toujours. Dans le cas simple où les deux threads lisent d’abord la valeur, puis la multiplient par 2 et enfin la réécrivent dans la même variable, le résultat correct pour la valeur initiale 1 est 4.

La condition préalable est qu’un premier thread lise la variable ayant la valeur 1, la multiplie par 2 et écrive le résultat 2, avant que le second thread ne lise ce 2, le multiplie à nouveau par 2 et écrive un 4. Si, par contre, le deuxième thread lit la valeur alors que le premier a déjà lu mais pas encore écrit le résultat, les deux calculeront avec la valeur initiale 1 et réécriront ensuite le 2 dans la variable comme un faux résultat final.

Les courses aux données décrivent comment un accès à la mémoire dans un thread est potentiellement affecté par une opération d’écriture dans la même zone de mémoire dans un autre thread d’une manière dangereuse ou perturbatrice pour le bon fonctionnement du programme. Dans certaines définitions, ils sont considérés comme un sous-ensemble des conditions de course, tandis que d’autres les considèrent comme un problème distinct.

Comme extension supplémentaire, SE-0316 introduit les acteurs globaux, où les acteurs sont disponibles directement à partir du fil principal du programme.

Swift 5.5 apporte quelques innovations supplémentaires, dont certaines étendent le nouveau modèle de concurrence, comme les valeurs locales des tâches (SE-0311), async let-Bindings (SE-0317), Propriétés effectives en lecture seule (SE-0310) ou AsyncStream et AsyncThrowingStream (SE-0314).

Au-delà de la concurrence, il convient de mentionner l’extension des enveloppes de propriétés introduites dans Swift 5.1 aux paramètres des fonctions et des fermetures (SE-0293) et la synthèse codable pour les Enums avec valeurs associées (SE-0295). En outre, le gestionnaire de paquets de Swift permet désormais de créer une liste de paquets et de métadonnées associées sous forme de collection de paquets (SE-0291).

La liste complète des nouvelles fonctionnalités se trouve sur le blog de Swift. Les binaires du langage de programmation open source sont disponibles sur la page de téléchargement. Une autre page renvoie aux dépôts GitHub individuels contenant le code source.


[rme)

Plus d'articles