Bonjour à tous.
Dans le cadre d'expérimentations personnelles, je me retrouve à écrire un petit framework UI pour tester certaines idées. Initialement, j'avais pensé le doter d'un patron parallèle classique où un objet UI est associé à un thread en particulier et ne peut être manipulé que depuis celui-ci sous peine de voir un flot d'insultes lancé à la face du développeur. Il en existe quelques variantes : tous les contrôles associés au même thread, un thread par fenêtre, etc. Dans tous les cas on se retrouve avec des agents asynchrones pilotant des objets ayant une affinité avec un thread.
Pour permettre un certain parallélisme, j'avais envisagé d'autoriser certains contrôles ou fenêtres à se déclarer indépendants et à avoir leur propre thread pour eux et leurs descendants. Cela dit, je ne suis pas satisfait. D'abord cela introduirait soit une certaine latence soit un comportement désagréable pour l'UI : si on a une UI divisée en deux parties et que l'une d'entre des deux est indépendante et dirigée par un thread enfant, une action qui provoquerait un redimensionnement de cette partie et la mise à jour de l'autre nécessitera l'invalidation des deux threads. Si on choisit d'attendre que le thread enfant ait fini son layout, on perd l'intérêt du multithreading. Si on choisit de repeindre d'abord la partie de droite en attendant que le thread enfant ait terminé et fourni ses nouvelles mesures on va avoir deux mises à jours successives de l'UI qui auront l'air désagréables.
Ensuite, le code est assez inélégant, avec plusieurs structures telles que "si cet enfant a son propre acteur, ajouter tel callback dans la file d'ordres du thread de cet enfant". Même avec un langage offrant une bonne syntaxe pour ce genre de choses, je trouve ça peut satisfaisant. Or, l'utilisateur aurait lui aussi besoin de faire ce genre de choses pour certaines actions, s'il veut traverser l'arbre par exemple.
Du coup, j'hésite à revenir à de bons vieux verrous, un par contrôle. Il m'est toujours possible de déverser des flots d'insultes sur le développeur si celui-ci a oublié d'utiliser le verrou avant d'accéder aux membres du contrôle. Qui plus est, les performances sont acceptables : 5 à 10ns pour acquérir et relâcher un verrou dans un contexte où je dispose de 166ns par contrôle pour l'intégralité de la mise en page (je me suis fixé la contrainte de pouvoir animer une UI de 100k primitives à 60fps). Enfin, cela autorise un parallélisme plus fin puisque l'utilisateur pourrait, sur un panneau "pile verticale de contrôles", modifier une propriété pour que ce dernier traite les enfants en parallèle. Sur les futures machines à moult coeurs on pourrait ainsi réaliser des ui complexes avec des éléments lourds pour peu d'efforts.
Cela dit, je suis un peu inquiet de la façon dont les utilisateurs pourraient mal utiliser le système. Aujourd'hui, à regarder les forums, où chaque jour des utilisateurs débarquent en se demandant pourquoi leurs contrôles les insultent et ce que ça peut bien vouloir dire, il est clair qu'il n'est pas rare de voir des programmeurs ne rien comprendre au parallélisme. Du coup, certains pourraient être tentés de simplement mettre un verrou autour de leur contrôle sans réaliser que les opérations qu'ils sont en train de réaliser vont empêcher l'UI de faire son job. Ou encore mal utiliser les verrous.
Resterait aussi les problèmes de deadlocking mais ceux-ci peuvent être évités en hiérarchisant les verrous : chaque contrôle est numéroté à sa création et, pour obtenir un verrou sur i, il faut d'abord abandonner ceux sur les j > i, quitte à les réacquérir ensuite. Tout ceci peut être totalement masqué pour l'utilisateur afin de ne lui présenter que deux méthodes : Lock et Release. EDIT: Erf, non, masquer ça pour réacquérir automatiquement un verrou abandonné est problématique. Du coup le paradigme est un poil imbitable pour l'utilisateur moyen.
Qu'en pensez-vous ? Connaîtriez-vous éventuellement d'autres modèles ?
Partager