27 novembre 2011
Ah, voilà.
Je viens de trouver le bouton d'édition dans l'interface.
En voici le résultat...
Finit la page d'accueil...
29 novembre 2011
Ok, I was able to edit the main page of this project.
With front pages, readme files, the wiki and doc directory of the project, there are pages flying everywhere. I don't really know how the whole thing is supposed to be used. Also, I'm finding myself repeating things over and over.
Le problème de la langue est également épineux. Dois je écrire en mauvais français, ou dans un anglais encore pire ?
La nature internationale des projets en source libre incite à adopter l'anglais...
Je suppose que j'utiliserai le français pour le wiki et l'anglais pour tout ce qui ressemble un peu à de la documentation.
Je suis navré pour les francophiles mais, pour le moment, la langue naturelle de l'informatique semble vraiment être l'anglais.
----
Je travaille en ce moment sur les fibers et autres threads.
Les coroutines (fibers) offrent un moyen trés intéressant de structurer un programme.
Les coroutines ont leur propre programme principal qui travaille avec sa propre pile.
Normalement, elles partagent également leur espace d'addressage, et les variables qui y sont stockées.
Cela signifie qu'un pointeur passé d'une coroutine à l'autre est utilisable tel quel, ce qui est souvent pratique.
Mes projets sont architecturés de manière à ce qu'il n'y ait si possible qu'une variable globale: la pile. Son status 'global' est juste là parce qu'elle sert de paramètre implicite à passer à toutes les fonctions.
Dans le cadre des fibres, ce n'est pas désirable: on ne souhaite pas que les données provenants de différents composants du programme viennent s'entremêler de manière incontrôlée. Suivant l'implémentation, on résout le problème de manière différente:
* Pour les coroutines purement logicielles: elles sont gérées de manière purement séquentielle (pas de vrai paraléllisme). Le passage d'une coroutine à l'autre est explicite (nous fournissons la routine !). On peut se permettre d'échanger la pile au moment de ce passage.
* Pour les "threads": Il y a un véritable parallélisme. On fait appel à une bibliothèque externe et/ou à l'OS. Avec la norme C1x du C, une bibliothéque standard sera sans doute fournie. Pour le moment, on s'appuie sur la bibliothèque de l'OS.
Le changement de thread peut s'opérer à tout moment, de manière caché. Et parfois, les threads avancent simultanément.
Je pensais être coincé, heureusement mes recherches sur internet ont portés leur fruits. Apparement tout ceux qui ont travaillé avec des threads semblent être tombé sur des problèmes similaires.
A tel point qu'un mécanisme a été directement intégré dans la définition du C. On peut désormais déclarer une variable globale avec le spécificateur __thread. Ce spécificateur indique que cette variable sera différente pour chaque thread. Au niveau du compilateur C, cela signifie qu'un registre du microprocesseur sera réservé pour pointer sur la zone de donnée 'threads'.
Par exemple, pour les architectures de type i86, un registre de segment fera l'affaire (le registre GS semble avoir été spécialement prévu pour).
Autant dire que cela trivialise mon problème. Il me suffit de déclarer mes pointeurs globaux de pile comme étant de porté __thread.
J'ai essayé différentes interfaces pour la définition des threads. Au final, ce qui l'a emporté:
* Un objet de passage d'une thread à une autre. "ThdThread" offre simplement la méthode "Switch()" qui ne prend et ne retourne aucun paramètre. L'objet est partagé entre deux threads. Ce que fait la méthode Switch() est de passer la main à l'autre thread, et d'attendre qu'il appelle à son tour la méthode Switch() avant de continuer.
* Un objet d'interface. "ThdItf" qui doit être définit par l'utilisateur. On impose simplement à cet objet de fournir la méthode "Main()". La méthode 'Main()' sera appelée avec en paramètre un ThdThread, qui pourra être utilisé pour rendre la main au thread appelant. Les échanges de données entre les deux threads (y compris les données d'initialisation, messages, paramètres...) sont sensées se faire par l'intermédiaire des données privées de l'interface.
Il est de la responsabilité de l'utilisateur d'établir le protocole de communication entre les deux threads. Il ne devrait pas y avoir de problème du côté de l'appelé, puisque la fonction Main() est une méthode de l'interface; par contre, l'appelant ne pourra souvent pas se contenter d'un pointeur sur "ThdItf". La méthode constructeur de "ThdItf" devra donc souvent l'englober dans un objet au comportement plus élaboré.
* Les primitives de lancement des threads. Ces primitives varient en fonction du type de thread que l'on souhaite utiliser: Coroutines, Thread ou Processus. Elles prennent en paramètre un objet de type "ThdItf", et peut-être des options comme une taille de pile.
Elles créent un objet de synchronisation de type ThdThread; appellent directement (ou non) la méthode Main() avec cet objet, et retournent l'objet de synchronisation à l'appelant.
C'est à peu prés tout ce que nous avons besoin de définir. Au final, toute la notion de Threads tourne autour de la primitive "Switch()".
Bien que cette réalisation ne peut pas l'imposer en elle même, nous imposons une stricte discipline lors de l'appel des threads:
* Les threads doivent être strictement organisées suivant une arborescence.
* Une thread ne peut avoir qu'un ancêtre direct. (la thread qui l'a lancée).
* Une thread peut avoir ces propres sous threads (enfants).
* Une thread n'a le droit de se synchroniser qu'avec son ancêtre direct ou un de ses enfants directs.
* En particulier, une thread n'a pas le droit de se synchroniser directement avec une de ses soeurs. Pour se synchroniser, il faut qu'elles passent par l'intermédiaire de leur parent, qui doit jouer le rôle d'arbitre.
30 Novembre
Je continue sur cette page car cela a encore un rapport avec les Threads.
Finalement j'ai quelque chose de suffisant pour être intégrer.
Il n'y a pas encore d'implémentation avec des processus. Je suppose que les processus attendront un petit moment encore. Le truc des processus, c'est qu'étant donnée la manière dont ils communiquent entre eux (fichiers et pipes), il faut établir tout un protocol de communication.
Au final, j'ai du revoir mon brouillon. La simple méthode "Switch()" ne permet de véritable parallélisme, car elle impose que tout les calculs se déroulent suivant une séquence stricte.
Pour du véritable parallélisme, il y a besoin d'une méthode qui dise "Continue pendant que je fais des choses de mon côté.". Alors que "Switch()" dit simplement "A toi de faire, préviens moi quand ce sera mon tour".
Pour cette raison, j'ai du découper la primitive switch() en deux:
Le comportement global est bien plus complexe qu'un simple switch(). A l'évidence on perd beaucoup en prédictabilité, ce qui est normal vu que c'est sensé introduir du véritable parallélisme.
La fonction 'Ack()' a globalement le même sens, mais a un comportement différent suivant les implémentation. Le sens global est 'j'ai bien recu le message, fait ce que tu veux pendant que je continue de mon côté'. Toute l'ambiguité vient bien sur de 'fait ce que tu veux'.
Voici ce que les différents types de threads font: