Damned.
Mince.
C'est logique, ceci dit, puisque std::exit() ne retourne pas � l'appelant, et qu'on est pas encore sorti du scope. Ceci �tant dit, �a pose quand m�me un petit probl�me...
Version imprimable
Je r�ponds � moi-m�me.
Il y a trois solutions pour r�soudre le probl�me.
La premi�re solution consiste � cr�er deux classes process diff�rentes, selon qu'on souhaite terminer le processus fils ou non. C'est � mon sens une mauvaise chose, parce que le choix de la classe � utiliser sera probl�matique.
La seconde solution consiste � ajouter un argument au constructeur de std::process() - un �num�ration qui aurait des valeurs du style terminate_parent, terminate_child ou terminate_none (celui-l�, je ne vois pas trop comment l'utiliser...). Si on pr�f�re ajouter un constructeur plut�t que de modifier le constructeur suppl�mentaire, std::process(F,Args...) est �quivalent � std::process(terminate_child, F, Args...). Avec la d�l�gation de constructeur, on devrait obtenir un code final assez propre.
Enfin, la troisi�me solution : ajouter une fonction this_process::exit(), qui informe le process qu'on doit appeler exit() lorsque le callable a termin� son ex�cution. Il faut en outre pr�voir un m�canisme qui va appeler std::exit() si on est pas en train d'ex�cuter un callable dans le constructeur de std::process. C'est possible, mais je ne trouve pas �a tr�s propre... Une impl�mentation pourrait �tre (c'est du pseudo-code sale, pas du vrai code bien l�ch� ; le but est de montrer le principe):
On peut imaginer passer par un atomic<T*> si n�cessaire, mais je n'en vois pas l'utilit�.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 // variable globale std::process* __current_process = NULL; // dans le constructeur : process::process(F, Args...) { fork(); if (in_child()) { // puisqu'on a forké, le parent ne voit pas la mise à jour // de la variable globale (elle est privée au processus fils) __current_process = this; // on force le callable à s'exécuter dans un scope // particulier call_callable(F, Args...); F(Args...); if (this->__must_quit) std::exit(this->__exit_code); } } void process::call_callable(F, Args...) { // étant dans un scope particulier, on est sûr que // les objets en automatic storage vont être détruit // à la sortie du scope. F(Args...); } void std::this_process::exit(int exit_code) { if (__current_process) { // cas où on est dans un process __current_process->__exit_code = exit_code; __current_process->__must_exit = true; } else { std::exit(exit_code); } }
Le probl�me avec cette m�thode, c'est que, tout � coup, appeler this_process::exit() n'a pas du tout l'effet escompt� si on l'appelle depuis un thread qui n'est pas un thread principal d'un processus fils cr�� avec std::process. On s'attendrait � quitter le processus, mais non.
Le probl�me peut �tre r�gl� en choisissant un meilleur nom pour la fonction.
Au niveau design, j'avoue ne pas �tre vraiment tranquille lorsque j'�crit une fonction qui modifie un �tat global sans que �a ait d'effet visible imm�diat. De plus, le comportement de la fonction change selon qu'elle est appel�e dans le constructeur de std::process ou autre part, et �a, �a le chagrine aussi (note: m�me si on enl�ve l'appel � exit() : this_process est cens� contenir des fonctions qui sont valables quel que soit le processus consid�r�, et pas uniquement un processus cr�� avec std::process).
Si vous voyez une solution que je n'ai pas vu, je suis preneur - dans le cas contraire, je penche pour la seconde solution (un param�tre suppl�mentaire sur un second constructeur).
Je ne suis pas sur que ces solutions r�pondent bien au besoin.
Dans l'id�al, il faudrait que ces 3 lignes donnent le m�me r�sultat :Du coup, peut �tre faire quelques chose du genre pour le ctor de processCode:
1
2
3 std::process p0([=](){ std::exit(0); }); std::process p1([=](){ return 0 }); std::process p2([=](){ }); // return 0 par défaut
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 private: template <class T, class C> struct _exec { static void foo(C c) { c(); exit(EXIT_SUCCESS); } }; template <class C> struct _exec<int, C> { static void foo(C c) { exit(c()); } }; public: template <typename _F, typename... _Args> explicit process(_F&& __f, _Args&&... __args) { auto __callable = bind(forward<_F>(__f), forward<_Args>(__args)...); native_handle_type __h = __bits::__process_impl<__itag>::fork(); if (__h == 0) { _exec<decltype(__callable()), decltype(__callable)>::foo(__callable); } else if (__h < 0) { __bits::__throw(errc::resource_unavailable_try_again, "failed to fork() the process"); } else { _M_id = process::id(__h); } }
Car en faisant un std::exit, on est courant que les destructeurs ne seront pas appel�s, du coup pas besoin d'essayer de passer outre ce fonctionnement. (enfin je pense)
edit: en relisant je dis des conneries, il faut bien appeler le destructeur des mutex (et autres objets de synchronisation pour les lib�rer si besoin)
Utiliser atexit() pour inscrire le destructeur (ou une fonction qui se charge de lib�rer les mutex) � la cr�ation d'un mutex (donc dans le constructeur) pourrait aussi �tre possible :question:
Sur les trois solutions propos�es, la solution 2 me plait bien :)
Dans le principe, oui. Dans mon dernier code, push� il y a quelques minutes, c'est le cas - modulo le fait que je fais syst�matiquement un exit(0), m�me si le callable fait un return 1.
Par contre, je peux aussi �crire :
Je peux vouloir tuer le parent sans tuer le fils (par exemple pour faire un daemon).Code:
1
2
3
4
5
6
7 std::process p0([](){}); // le child fait exit(0) std::process p1([](){ return 1; }); // le child fait exit(0), il devrait faire exit(1) ? std::process p2(terminate_flag::terminate_child, [](){}); // le child fait exit(0) std::process p3(terminate_flag::terminate_parent, [](){}); // le parent fait exit(0) std::process p4(terminate_flag::terminate_both, [](){}); // le parent et le child font exit(0) std::process p4(terminate_flag::terminate_none, [](){}); // tout le monde continue
(note: le nom terminate_flag ne me plait gu�re, mais �a ira pour l'instant).
C'est int�ressant. Ceci dit, �a n'est pas un miroir de ce qui est fait dans std::thread, on la valeur de retour du callable est purement et simplement ignor�e. Dans la mesure du possible, je pr�f�rerais �viter de trop grandes diff�rences.
Le truc bien, c'est que je peux quand m�me int�grer cette possibilit�; au cas o�.
Dans mon cas : les std::named_mutex ont besoin d'appeler shm_unlink() dans le destructeur, de mani�re � pr�venir le syst�me que le mutex nomm� doit �tre d�truit. Je n'ai pas encore �crit le code pour Windows, donc je ne sais pas ce que �a peut donner, mais il n'est pas impossible qu'on ait un probl�me similaire (� moins que l'OS ne garde un refcount sur le mutex nomm�).
atexit() doit �tre r�serv� � l'utilisateur, car c'est lui qui contr�le ce qui se passe. S'il a une fonction enregistr�e qui g�n�re une exception, std::terminate() est appel�e et les autres fonctions enregistr�es peuvent ne pas �tre appel�e (on se retrouve alors dans le cas initial).
J'ai impl�ment� cette fonctionnalit� (pr�sente dans la r�vision 63aa0c489ea104ac0f1019af8fdadb1e49d1d477 de la branche master sur https://code.google.com/p/edt-proces...source/browse/).
J'ai jou� un peu avec, et je suis partag�.
D'un cot�, obtenir le code d'erreur en sortie d'un programme est int�ressant. Ca permet de valider que le programme s'est ex�cut� correctement - dans la plupart des cas.
D'un autre cot�, �a impose d'�crire quelque chose comme
alors que cette �criture ne se fait pas avec std::thread. De plus, certains programmes n'utilisent pas le code de sortie de mani�re correcte (notamment sous Windows, et aussi un peu sous Unix), ce qui rends le traitement un peu hasardeux (dans le cas o� on fait un this_process::exec).Code:int r = p.join();
Du coup, trois solutions l� aussi :
1) on �limine la notion de code de retour - il est ignor�, comme le code de retour des threads.
2) process::join() retourne le code de sortie du process qui s'est ex�cut�.
3) on ajoute une fonction suppl�mentaire int process::exit_code() const noexcep, qui renvoie le code de retour apr�s un appel � join() (la fonction throw si le process est joinable() ? le probl�me se pose maintenant avec detach() et le constructeur par d�faut de process).
J'avoue que je n'ai pas de bonne solution ici. Je suis tr�s, tr�s tent� par (1), histoire de pr�server une interface tr�s similaire � std::thread.
Est-ce que tu consideres le code actuel utilisable en production?
EDIT 1> Ah non, certaines features utilisees ne sont pas accessible a VS sans le CTP...
EDIT 2>
Je ne suis pas sur de comprendre ce comportement:
Le terminate, c'est celui du parent, pas de l'enfant, si?Citation:
~process()
{
if (joinable())
terminate();
}
Les fonctionnalit�s C++11 suivantes sont utilis�es
* template variadique
* decltype
* enum struct
* auto
* d�l�gation de constructeur
* std::move, std::forward
* rrvalue reference
* exception system_error
Je crois que c'est � peu pr�s tout. Est-ce que le CTP est capable de prendre en charge tout �a ?
Effectivement.
Le principe est le m�me que pour les thread : si on d�truit un thread non d�tach� ou non "join�", alors on se prends std::terminate() dans le thread qui ex�cute le destructeur. Les raisons pour ce comportement sur thread sont duales :
* si on fait un join() dans le destructeur, alors on se prends un probl�me de performance non contr�lable par l'utilisateur.
* si on fait le detach(), alors on se prend un probl�me de comportement �trange (possiblement un bug)
Vu que le probl�me est ind�cidable, on force soit un detach(), soit un join() explicite en ex�cutant std::terminate() si le thread ou le process est joinable() au moment o� le destructeur de l'objet s'ex�cute.
Je ne suis pas sur. De toutes facons le CTP n'est pas utilisable en production:
- il est bugge;
- officiellement, il n'y a pas de support puisque c'est une preview;
- la STL n'utilise pas ses features;
Du coup je ne peu pas l'utiliser. Je pense que c'est pareil pour n'importe qui qui veut sortir son produit cette annee.
Oui je comprends bien la similarite avec le std::thread, et cote parent c'est pas mal, mais si j'ai bien suivi, le processus enfant n'est pas detruit avec le parent quand celui ci se termine, si?Citation:
Le principe est le m�me que pour les thread : si on d�truit un thread non d�tach� ou non "join�", alors on se prends std::terminate() dans le thread qui ex�cute le destructeur. Les raisons pour ce comportement sur thread sont duales :
* si on fait un join() dans le destructeur, alors on se prends un probl�me de performance non contr�lable par l'utilisateur.
* si on fait le detach(), alors on se prend un probl�me de comportement �trange (possiblement un bug)
Vu que le probl�me est ind�cidable, on force soit un detach(), soit un join() explicite en ex�cutant std::terminate() si le thread ou le process est joinable() au moment o� le destructeur de l'objet s'ex�cute.
C'est different des threads qui sont embarques tous ensemble dans le processus.
Hors cela ferais une sorte de leak de processus. Tandis que l'interet ici serait d'etre sur que les processus enfants sont toujours dependants du processus parent.
Comme je ne sais pas bien comment marche fork, peut etre que les details m'echappent, mais pour this_process::exec() tout de suite je me dis que ca serait bien que le processus cree ait une vie gere par sont parent.
Aie.
C'est le but. Il est tout � fait possible d'�crire
Ce qui cr�e un daemon (tr�s minimaliste...).Code:
1
2
3
4
5
6
7
8 std::process p(std::terminate_flag::terminate_parent, [](){ setup_daemon(); while (1) { wait_and_process_request(); } });
fork() a deux buts :
1) la cr�ation de process fils afin d'ex�cuter une t�che particuli�re dont le parent � besoin. Ceux l� sont termin�s (on attends la fin par un appel syst�me waitpid()). S'ils ne se terminent pas avant la fin du process parent, c'est un bug.
2) la cr�ation de process fils d�tach�s, dont le fonctionnement est autonome, et qui ne sont donc pas contr�l�s par le parent. Ils peuvent continuer � fonctionner m�me si le parent meure. Il y a deux sous-groupes :
2.1) les daemon - ce sont des processus qui n'ont pas acc�s � la console (similaires aux services sous Windows). On communique g�n�ralement avec eux via des IPC (souvent des sockets). On s'en sert pour impl�menter des serveurs (web, ftp...) ou des sous-syst�me de contr�le (udevd sous Linux).
2.2) des processus normaux, qui ont leur t�che � faire, et dont le parent se moque royalement. Un processus lanc� par crond (lancement de t�ches � heure fixe) est libre de faire ce qu'il veut, y compris rester en arri�re plan jusqu'� l'extinction de la machine.
Du coup, on ne peut pas vraiment parler de leak de processus dans le cadre d'un fonctionnement normal : leur domaine de fonctionnement est diff�rent des threads.
Ceci dit, �a ne veut pas dire que les leaks de processus n'existent pas : la possibilit� de est m�me bien r�elle r�elle. Il s'agit en fait de processus zombie (ils sont cr��s par un parent, mais ce parent n'attends pas leur fin ; du coup, ils restent dans le syst�me). Les zombies (ainsi appel�s parce qu'on ne peut pas les tuer, vu qu'ils sont d�j� morts) peuvent poser de multiples probl�mes sur un syst�me, et notamment emp�cher la cr�ation de nouveaux processus s'il y a trop de zombies.
Dans les syst�mes POSIX, on peut mitiger ce probl�me en jouant avec les signaux (le processus parent re�oit un signal SIGCHLD lorsqu'un de ses enfants meure). Ce n'est pas g�nial dans le cadre de la librairie standard, parce que �a veut dire que la lib standard doit mettre en place une callback pour traiter ce signal. Hors signaux et threads ne se mixent pas tr�s tr�s bien (merci POSIX).
A noter que ce comportement provient d'une erreur de programmation. Du coup, je pense que c'est � l'utilisateur de mettre en place le syst�me qui permet de contourner ce probl�me (je n'ai pas encore trouv� une solution architecturale propre � int�grer dans le proposal, mais �a vient).
Apr�s ces explications, la solution d'ajouter un param�tre (enum) au constructeur prend son sens, j'avais du mal � voir l'utilit�.
Je reviens la dessus. Actuellement il est possible de faireC'est une �criture plut�t lourde pour le simple lancement d'un processus "externe" (dont le code n'est pas dans l'exe).Code:
1
2
3 std::process p([]() { std::this_process::exec("ls"); });
Serait plus l�ger et permettrait des optimisations (vfork + execve sous unix, j'ai pas l'�quivalent sous la main pour Windows, mais �a se se fait en un appel syst�me).Code:
1
2
3 std::process p = std::process::exec("ls"); // ou std::process p("ls");
Est-ce envisageable ? Ou �a s'�loigne trop de std::thread ?
Sous Windows, on utiliserait directement CreateProcess() (au lieu d'une pseudo-fonction fork() qui, elle, utiliserait ZwCreateProcess()). C'est un chemin optimis�.
Sur la seconde version : conceptuellement, exec() est une fonction - je dois pouvoir l'utiliser sans utiliser std::process. Par exemple pour �crire ce code, dans un binaire qui se compile en /sbin/preinit :
La fonction est donc, selon moi, n�cessaire - car elle est utile par elle-m�me.Code:
1
2
3
4
5
6 int main() { ... // on continue sur l'init Système V std::this_process::exec("/sbin/init"); }
Un constructeur std::process(std::string, Args...) est selon moi confus : il n'est pas explicite sur ce qu'il fait. Il ne dit pas, entre autre, si le programme s'ex�cute dans un processus fils ou dans le processus courant. Le fait d'avoir la dualit� exec/constructeur additionnel de std::process() n'est pas vraiment pour
Quand � la premi�re notation, elle suppose que std::exec renvoie un std::process - la fonction fait donc deux choses (cr�ation d'un process + ex�cution d'un binaire). Elle ne permet plus d'�crire le code que je propose, et de plus elle est une source de probl�me non n�gligeable : est-ce que la fonction doit faire le join() (et donc �tre bloquante) ? Ou le detach() ? Ou rien, mais on doit n�cessairement r�cup�rer la valeur de retour pour faire le detach ou le join ? (un cas que la librairie standard n'impl�mente pas : une valeur de retour non r�cup�r�e n'est jamais fatale dans la librairie standard).
Ca me semble plus compliqu� et plus sensible que la notion actuelle, certes un peu plus lourde (mais quand m�me pas trop) et qui a le m�rite d'�tre compl�tement explicite.
Je ne proposais pas de remplacer std::this_process::exec, mais d'ajouter une fa�on d'ex�cuter directement un exe sur un processus fils. (pour �viter les fork sur Windows quand c'est possible)
exec = exec au sens POSIX
exec = std::this_process::exec
Je vois pas comment, si exec appelle CreateProcess, il y a cr�ation d'un 2eme fils (sur lequel on � aucun contr�le car on ne r�cup�re pas le std::process correspondant :question:)
Si on sait qu'il n'y a pas de code avant exec alors on peut faire un CreateProcess, sinon fork + exec obligatoire.
Dans ce cas l�, il est impossible de savoir si il y a du code avant le execIl est donc impossible de faire �a autrement que par un fork + exec. :question:Code:
1
2
3 std::process p([]() { std::this_process::exec("ls"); });
Si on utilise exec() dans deux sens diff�rents peut-�tre une meilleure id�e serait-elle d'utiliser un autre nom (.Net utilise Process:Start(), qui retourne un Process).
C'est justement le fait de retourner un process qui pose probl�me : si une fonction start() fait �a, alors elle introduit un dr�le de pr�c�dent dans la librairie standard, � savoir que cette valeur de retour, si elle n'est pas r�cup�r�e, va provoquer un crash de l'application qui l'utilise. Il n'y a aucun autre exemple dans la librairie standard d'un tel comportement : toute valeur de retour d'une fonction ou d'une m�thode peut �tre ignor�e.
Apr�s, peut �tre qu'une fonction start_wait et/ou une fonction start_detach pourrait avoir un int�r�t (et permettrait au vendeur d'impl�menter un fastpath pour ces cas particuliers).
Je vais rajouter ces fonctions (peut-�tre nomm�es diff�remment) dans le proposal.
Ah, c'est � cause des histoires de zombitude d'*n*x, c'est �a? Le fait qu'un processus p�re doive syst�matiquement acquitter de la fin d'un fils?Citation:
cette valeur de retour, si elle n'est pas r�cup�r�e, va provoquer un crash de l'application qui l'utilise.
Pas vraiment. En fait, l'argument est semblable � celui qui a pr�sid� au design de std::thread et de son destructeur : si le process est joinable au moment o� le destructeur de l'objet process s'ex�cute, on a deux possibilit�s :
* soit le destructeur fait un join() - ce qui est bloquant et peu potentiellement ne jamais terminer
* soit le destructeur fait un detach(), et la t�che continue sans qu'on ait de contr�le dessus - ce qui peut (potentiellement) introduire un bug dans le sens o� la t�che fille peut communiquer avec nous (via un syst�me IPC quelconque ; on se retrouverais dans ce cas � continuer � discuter avec une t�che qui, de notre point de vue, n'est plus l�).
De plus, quel que soit la solution choisie, il existe une possibilit� pour que cette solution ne soit pas ce que l'utilisateur souhaite faire dans tous les cas.
Le comit� de normalisation a r�solu ce dilemme de la mani�re la plus simple possible pour std::thread : il est interdit de d�truire un thread joinable() - on se prends un std::terminate() dans la face.
Vu qu'on a exactement le m�me dilemme, il parait int�ressant d'utiliser la m�me solution - c'est ce que j'ai fait.
Du coup, si une fonction foobar() renvoie un std::process, alors il est n�cessaire de r�cup�rer la valeur de retour pour faire soit un join(), soit un detach() - sans quoi on se prendra un std::terminate(). Il faut donc �crire :
ouCode:
1
2 std::process p = foobar(...); p.join();
Dans tous les cas, le code de retour de foobar ne peut pas �tre ignor�.Code:foobar(...).detach();
J'ai ajout� (et d�fini) les fonctions std::this_process::spawn() et std::this_process::spawn_wait() dans mon proposal et dans le code. Le code lui-m�me n'est pas optimal (je continue d'utiliser fork() au lieu de vfork() ; mais le code sous Windows peut directement tirer partie de CreateProcess()).
J'avais annonc� il y a peu avoir ajout� les named_mutex (l'ai-je fait ? je n'en suis m�me plus s�r). Quoi qu'il en soit, je les ait impl�ment�.
Sous Linux, les named_mutex sont impl�ment� vie le syst�me standard de gestion de m�moire partag�e et gr�ce � l'appel syst�me futex(2). L'impl�mentation a �t� test�e, elle semble fonctionner correctement (mais je ne suis pas � l'abris d'une boulette). Elle utilise aussi les intrinsics d'acc�s atomique aux variables propos�es par gcc (__sync_fetch_and_or(), __sync_val_compare_and_swap()). Son point faible : la n�cessit� d'utiliser des fonctions qui doivent �tre ajout�e � la libstdc++ (pour l'impl�mentation test, je cr�e une librairie statique � part).
Les named_mutex sont des mutex. Ils peuvent donc �tre utilis�s comme la classe std::mutex existante. La seule diff�rence (de taille) est que les named_mutex n�cessitent un nom - de fait, la classe n'a pas de constructeur par d�faut - ce qui n'est pas catastrophique en soi.
La suite du d�veloppement (et de la d�finition du proposal) va se faire sur les autres classes de mutex nomm�s - named_recursive_mutex, named_timed_mutex et named_recursive_timed_mutex. Ensuite viendra la classe named_semaphore - gestion des s�maphores nomm�s (dont l'impl�mentation reste tr�s simple). J'en profiterais peut-�tre pour faire une passe sur une classe semaphore (un proposal ind�pendant, � priori).
La gestion de la m�moire partag�e va poser probl�me. Dans l'id�e, la m�moire partag�e est juste une zone de m�moire comme les autres, mais c'est une tr�s mauvaise id�e d'y stocker des pointeurs - pour la simple et bonne raison qu'elle n'est pas n�cessairement mapp�e sur la m�me adresse virtuelle dans les processus l'utilisant. Ca pose un soucis de conversion ptr <--> offset, conversion qui dans l'id�al doit �tre transparente. Par exemple, j'apr�cierais un objet shm_ptr<> qui permet de d�r�f�rencer la valeur, mais on ne peut pas stocker cet objet dans la zone de m�moire partag�e elle m�me. L'objet est (selon moi) n�cessairement param�tr� via un objet "shm" (� d�finir), ce qui lui permet avec un offset + l'adresse de base de la zone de m�moire partag�e de cr�er un pointeur. En gros, on aurait (en pseudo-C++ bien �crit, vu que c'est tr�s mal �crit dans cet exemple...):
Mais du coup, on ne peut pas utiliser cet objet dans un autre objet qui sera stock� en shm :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 template <class T1, class T2> offptr compute_offset(T1 *from, T2 *to) { return reinterpret_cast<char*>(to) - reinterpret_cast<char*>(from); } template <class T> class shm_ptr { private: some_shm_object *m_shm; ptroff_t m_offset; public: shm_ptr(some_shm_object *shm, T* p) : m_shm(shm), m_offset(compute_offset(shm->base(), p) { } const T* operator*() const { return reintrepret_cast<const T*>(m_shm->base() + m_offset); } T* operator*() { return reintrepret_cast<T*>(m_shm->base() + m_offset); } };
La solution consisterait � ne pas proposer une telle classe, et (sans les interdire) � pr�venir que stocker un pointeur li� � un process dans une shm et l'utiliser dans un autre process est un undefined behavior.Code:
1
2
3
4
5
6
7
8
9
10
11
12 // impossible de faire un vector<my_object,shm_allocator<my_object>> // parce que l'objet contient un shm_ptr<> qui fait référence à la shm utilisée // (différente pour chaque process) class my_object { private: shm_ptr<object> object_ptr; int k; float q; public: ... };
Tout �a pour dire que les zones de m�moire partag�e, ce n'est pas forc�ment ais� � designer. Si vous avez une id�e, n'h�sitez pas � m'en faire part. Comme vous avez vu depuis l'ouverture de ce fil de discussion, je prends volontiers vos id�es en compte.
J'ai �t� ennuy� par la gestion de la m�moire partag�e (et pas que ; je suis toujours en train de chercher comment impl�menter des mutex r�cursifs ; mais c'est une autre histoire), mais je crois que finalement, je tiens le bon bout.
La m�moire partag�e a ceci de particulier qu'elle peut �tre mapp�e par plusieurs process � des adresses diff�rentes. Du coup, elle ne peut pas stocker de pointeur, parce qu'un pointeur valide pour un process sera probablement invalide pour d'autres (linux permet de dire � l'OS � quelle adresse il faut mapper la zone m�moire ; c'est dangereux). Alors que je r�fl�chissait � un moyen de mitiger le probl�me, j'ai eu un �clair (non, pas de g�nie. Un vrai �clair).
Prenons le cas d'un programme qui veut stocker des donn�es dans la m�moire partag�e. Ce programme en a besoin pour communiquer avec un autre programme. De quelle mani�re va-t-il se servir de cette zone de m�moire partag�e ? Comment va-t-il impl�menter la lecture et l'�criture ?
Deux types d'acc�s sont possibles :
* acc�s al�atoires : il va adresser directement une zone particuli�re de la m�moire partag�e, de mani�re � y lire ou y �crire directement.
* acc�s s�quentiel : il va utiliser la m�moire partag�e comme un flux.
Les acc�s al�atoires sont probl�matique car ils encouragent le stockage direct d'informations - mais on peut trouver des pointeurs parmi ces informations. Du coup, avoir une solution qui rends ce type d'acc�s plus complexe devrait permettre de limiter les probl�mes.
Dans ma premi�re vision, j'avais pens� � un allocateur qui serait utilis� pour stocker des collections d'objets (par exemple un std::vector<> ou un std::list<>). Mais ce faisant, on cache si bien les particularit�s de la m�moire partag�e qu'on finit par autoriser des choses horribles. Sans compter qu'un tel allocateur devra n�cessairement prendre en compte des probl�mes comme la fragmentation de la m�moire ou la r�utilisation des blocs lib�r�s. Des algorithmes qui, s'ils ne sont pas trop complexes (OK ; la d�fragmentation g�n�rique avec heuristique de d�couverte des pointeurs commence � �tre un peu plus complexe), restent co�teux en terme de temps CPU. Cette prise en compte est n�cessaire, dans le sens o� un allocateur dans la librairie standard doit pouvoir �tre utilis� avec tous les types de la librairie standard. J'ai donc abandonn� cette id�e - elle me semble trop hasardeuse.
Ma seconde vision est plus simple : une classe shared_object<T> permet de stocker un seul objet de type T dans une zone de m�moire partag�e. Le nommage est certainement � revoir, parce que �a fait trop penser � shared_ptr<> sans pour autant proposer la m�me s�mantique (conceptuellement, c'est un objet encapsul�, pas un pointeur intelligent sur un objet, encore que l'interface soit au final assez similaire). On reste avec le m�me type de probl�me : il est ais� de stocker des choses qui ne doivent pas y �tre stock�e (c'est � dire des pointeurs). Il y a un second probl�me, presque r�dhibitoire.
Si un programme cr�e un grand nombre de ces objets, alors une page m�moire sera allou�e � chaque objet, m�me si cet objet est de petite taille. Hors les pages m�moires sont une quantit� limit�es (pas en nombre, mais parce que chaque page est associ�e � une entr�e dans la MMU ; plus le nombre d'entr�es est important, plus le syst�me et le processeurs doivent travailler pour les g�rer). Du coup, alors que je veux utiliser 1000x20o octets de m�moire, j'en utilise en fait 1000x4096 (en 32 bits ; les pages m�moire en 64 bits sont souvent plus grosses). Et en plus, je ralenti l'OS. Cette solution, si elle semble � peu pr�t int�ressante sur le papier, n'est pas une bonne id�e dans la pratique. J'abandonne donc aussi.
Reste une troisi�me solution, qui a l'avantage de se baser sur un m�canisme d�j� connu et largement utilis� en C++ : une classe de stream.
Une zone de m�moire partag�e est cr�e de la m�me mani�re qu'un fichier sur la plupart des OS (au moins sous Windows et sur les OS POSIX, ce qui repr�sente d�j� une grosse partie du march�). Il fait donc sens de la repr�senter aussi sous la forme d'un fichier au niveau C++. Un tel fichier propose une interface permettant un acc�s al�atoire (seek, read, write), et c'est aussi un flux (donc operator<< + operator>>). On a donc l� quelque chose qui correspond conceptuellement � ce qu'est la m�moire partag�e vu de l'OS et qui propose en outre les outils dont on a besoin pour acc�der � cette m�moire partag�e. De plus, l'acc�s par flux d�courage tr�s nettement l'utilisation de pointeurs (s�rialisation), ce qui est un bon point. Pour ceux qui veulent faire des choses �tranges, les m�thodes read/write restent disponibles, m�me si la s�mantique de ces fonctions est l�g�rement diff�rente de celle d'un fichier (vu qu'une zone de m�moire partag�e est cr��e avec une taille fixe).
Voil�. Qu'en pensez-vous ? Est-ce que vous avez d'autres id�es, ou des arguments qui pourrait faire que mon id�e n'est pas viable ?
Ta seconde solution me fait un peu penser aux interior_ptr<> de .Net, malgr� des diff�rences.J'ai rien dit, j'ai mal lu.
Ta troisi�me solution, n'est-ce pas ainsi que marche la m�moire partag�e POSIX?