{"id":1868,"date":"2011-07-05T17:02:20","date_gmt":"2011-07-05T16:02:20","guid":{"rendered":"http:\/\/blog.capdata.fr\/?p=1868"},"modified":"2019-09-13T14:38:13","modified_gmt":"2019-09-13T13:38:13","slug":"io-asynchrones-episode-1","status":"publish","type":"post","link":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/","title":{"rendered":"I\/O asynchrones (\u00e9pisode 1)"},"content":{"rendered":"<a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox\" data-provider=\"twitter\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Twitter\" href=\"https:\/\/twitter.com\/intent\/tweet?url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"twitter\" title=\"Share on Twitter\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/twitter.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox\" data-provider=\"linkedin\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Linkedin\" href=\"https:\/\/www.linkedin.com\/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868&#038;title=I%2FO%20asynchrones%20%28%C3%A9pisode%201%29\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"linkedin\" title=\"Share on Linkedin\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/linkedin.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox\" data-provider=\"mail\" rel=\"nofollow\" title=\"Share by email\" href=\"mailto:?subject=I%2FO%20asynchrones%20%28%C3%A9pisode%201%29&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"mail\" title=\"Share by email\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/mail.png\" \/><\/a><p>Ce post est le premier d&#8217;une s\u00e9rie sur les API syst\u00e8mes impliqu\u00e9es au niveau de SQL Server:<\/p>\n<ol>\n<li><strong>Gestion des entr\u00e9es\/sorties<\/strong>: alignement, I\/Os asynchrones, alternate streams, scatter gather, I\/O completion ports, etc&#8230;<\/li>\n<li><strong>Le multithreading<\/strong>:\u00a0 synchronisation, events, mutexes, spinlocks, sections critiques&#8230;<\/li>\n<li><strong>La m\u00e9moire:<\/strong> allocation, fichiers mapp\u00e9s, AWE, large pages&#8230;<\/li>\n<\/ol>\n<p>Cette s\u00e9rie plut\u00f4t barbare va d\u00e9vier un peu des sujets SGBD traditionnels. Elle s&#8217;adresse \u00e0 toute personne souhaitant approfondir les m\u00e9canismes intrins\u00e8ques de SQL Server, pour en comprendre la logique et les contraintes. Ces articles seront class\u00e9s sous les sujets <a href=\"http:\/\/blog.capdata.fr\/index.php\/category\/operating-system\/\">Operating System<\/a> et <a href=\"http:\/\/blog.capdata.fr\/index.php\/category\/sqlserver\/\">SQL Server<\/a>. Leur lecture n\u00e9cessite que vous ayez des connaissances minimum sur C\/C++ et l&#8217;API win32.<\/p>\n<p>Dans un post <a href=\"http:\/\/blog.capdata.fr\/index.php\/consistence-des-ecritures-avec-sata\">pr\u00e9c\u00e9dent<\/a>, nous avions d\u00e9j\u00e0 effleur\u00e9 la question de l&#8217;\u00e9criture \u00e0 travers le cache, nous reprendrons le m\u00eame exemple pour \u00e9voquer le sujet du jour: les I\/Os asynchrones dans des fichiers. C&#8217;est une question large donc je propose d&#8217;\u00e9clater le sujet en trois \u00e9pisodes:<\/p>\n<ol>\n<li>Ce premier pour expliquer le principe et montrer \u00e0 travers un petit exemple simple\u00a0 les avantages, mais aussi les contraintes de faire des I\/Os asynchrones.<\/li>\n<li>Un second pour montrer comment g\u00e9rer plus efficacement\u00a0 la synchronisation des I\/Os asynchrones entre plusieurs threads, notamment avec l&#8217;utilisation d&#8217;I\/O completion ports.<\/li>\n<li>Un dernier pour montrer l&#8217;utilisation qui en est faite par SQL Server, ainsi que certaines probl\u00e9matiques particuli\u00e8res.<\/li>\n<\/ol>\n<h2>SYNCHRONE vs ASYNCHRONE:<\/h2>\n<p>Quand on \u00e9crit un programme sous windows, on doit t\u00f4t ou tard acc\u00e9der \u00e0 un device en lecture \/ \u00e9criture. La plupart des applications que l&#8217;on utilise n&#8217;ont pas besoin d&#8217;\u00e9crire tr\u00e8s souvent des\u00a0 donn\u00e9es durables. Souvent elles misent sur la bufferisation des entr\u00e9es \/ sorties, c&#8217;est \u00e0 dire l&#8217;\u00e9criture ou la lecture de donn\u00e9es en m\u00e9moire. Parfois cependant, elles devront lire des donn\u00e9es sur disque, par exemple lorsqu&#8217;elles d\u00e9marrent, ou bien \u00e9crire des informations dans un fichier de configuration lorsqu&#8217;elles s&#8217;arr\u00eatent. Le moyen de plus simple de lire ou d&#8217;\u00e9crire dans un fichier est alors de lancer une entr\u00e9e\/sortie <span style=\"color: #000000;\">synchrone <\/span>en utilisant ReadFile() ou WriteFile().<\/p>\n<p>On utilise le terme synchrone parce que le thread qui a initi\u00e9 cette \u00e9criture ou cette lecture doit attendre l\u2019acquittement ou le retour de l&#8217;entr\u00e9e\/sortie avant de pouvoir continuer son ex\u00e9cution. D\u00e8s que la demande de lecture ou d&#8217;\u00e9criture est lanc\u00e9e, il entre imm\u00e9diatement dans un \u00e9tat d&#8217;attente, il ne peut rien faire d&#8217;autre. De la sorte, s&#8217;il doit lancer de nombreuses op\u00e9rations d&#8217;entr\u00e9es sorties, il va devoir attendre que chacune soit termin\u00e9e avant de pouvoir ex\u00e9cuter la suivante, ce qui va limiter en quelque sorte sa capacit\u00e9 \u00e0 traiter un grand nombre d&#8217;op\u00e9rations par seconde.<\/p>\n<p>L&#8217;alternative est d&#8217;indiquer au thread de ne pas attendre le retour de l&#8217;I\/O pour continuer son ex\u00e9cution, en postant des I\/Os <span style=\"color: #000000;\">asynchrones <\/span>(ou overlapped IO). Il existe plusieurs contraintes associ\u00e9es \u00e0 l&#8217;utilisation des IO asynchrones, qui rendent la chose nettement plus compliqu\u00e9e \u00e0 g\u00e9rer:<\/p>\n<ul>\n<li>Lors d&#8217;une IO synchrone, le device object maintient un pointeur d&#8217;offset de sorte que si on lance deux lectures de 8K \u00e0 la suite par exemple, le kernel sait toujours l\u00e0 o\u00f9 il s&#8217;est arr\u00eat\u00e9 et l\u00e0 o\u00f9 il doit reprendre. D&#8217;ailleurs, si on regarde le prototype de <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa365467%28v=vs.85%29.aspx\">ReadFile()<\/a> ou <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa365747%28v=vs.85%29.aspx\">WriteFile()<\/a>, il n&#8217;y a pas d&#8217;information d&#8217;offset pass\u00e9es lors de l&#8217;appel. Avec une IO asynchrone, le pointeur d&#8217;offset est ignor\u00e9 par le driver donc il faut le g\u00e9rer dans le code. C&#8217;est une des diff\u00e9rences les plus caract\u00e9ristiques entre ces deux modes, et un vrai casse t\u00eate comme on va le voir dans un instant.<\/li>\n<li>Il y a de surcro\u00eet forc\u00e9ment un point dans le code o\u00f9 on ne\u00a0 peut plus avancer sans avoir le retour de ces IO. Il faut donc trouver un moyen de se synchroniser avec la fin des op\u00e9rations.<\/li>\n<\/ul>\n<p><strong>Dans la pratique, chaque thread\u00a0 peut se synchroniser de 4 mani\u00e8res diff\u00e9rentes:<\/strong><\/p>\n<ol>\n<li>Sur le fichier lui-m\u00eame, car le fichier est un handle comme un autre. Ce serait le plus simple, il n&#8217;y a pas d&#8217;autre objet \u00e0 cr\u00e9er. Le probl\u00e8me est que s&#8217;il y a plusieurs I\/Os en cours sur le m\u00eame fichier, on ne pourra pas d\u00e9terminer laquelle vient de se terminer car le handle est signal\u00e9 d\u00e8s que la premi\u00e8re I\/O est retourn\u00e9e.<\/li>\n<li>Sur un event associ\u00e9 \u00e0 l&#8217;I\/O. Comme nous allons le voir dans la section suivante, on utilise une structure comme traceur pour suivre chaque entr\u00e9e\/sortie asynchrone et cette structure contient par d\u00e9faut un event associ\u00e9, sur lequel le thread pourra se synchroniser. C&#8217;est la m\u00e9thode que l&#8217;on va aborder dans cet \u00e9pisode.<\/li>\n<li>En utilisant une routine APC. A chaque thread cr\u00e9\u00e9 avec beginthreadex() est associ\u00e9 une APC queue qui lui permet d&#8217;ex\u00e9cuter une routine de compl\u00e9tion lorsque sa tache est termin\u00e9e. On peut utiliser ces routines avec les primitives WriteFileEx() et ReadFileEx(). L&#8217;avantage principal de cette m\u00e9thode est que l&#8217;ex\u00e9cution de l&#8217;APC se fait en user mode, donc le programme conserve le contr\u00f4le pendant toute l&#8217;op\u00e9ration. Malgr\u00e9 ces quelques atouts, elle a une r\u00e9putation calamiteuse [1] et reste compliqu\u00e9e \u00e0 g\u00e9rer. Le simple fait de g\u00e9rer la completion dans une fonction s\u00e9par\u00e9e rend les choses beaucoup moins maintenables et d\u00e9bogables.<\/li>\n<li>Sur un outil de synchronisation plus perfectionn\u00e9, comme un I\/O completion port. SQL Server utilise un I\/O completion port par CPU-node pour g\u00e9rer les entr\u00e9es \/ sorties r\u00e9seau. Nous verrons l&#8217;utilisation d&#8217;un tel outil dans l&#8217;\u00e9pisode 2 pour g\u00e9rer des I\/O dans des fichiers.<\/li>\n<\/ol>\n<h2>Principes d&#8217;utilisation dans le code:<\/h2>\n<h3>La structure OVERLAPPED<\/h3>\n<p>Que l&#8217;on utilise des entr\u00e9es\/sorties synchrones ou asynchrones, les primitives utilis\u00e9es sont les m\u00eames [2], on leur passera simplement des param\u00e8tres diff\u00e9rents. Pour pouvoir pister nos entr\u00e9es \/ sorties asynchrones, on va utiliser un genre de traceur sous la forme d&#8217;une structure C appel\u00e9e\u00a0 <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms684342%28v=vs.85%29.aspx\"><strong>Overlapped<\/strong><\/a>. Si on regarde sa composition de plus pr\u00e8s:<\/p>\n<pre name=\"code\" class=\"cpp\">typedef struct _OVERLAPPED {\r\n  ULONG_PTR Internal;\r\n  ULONG_PTR InternalHigh;\r\n  union {\r\n    struct {\r\n      DWORD Offset;\r\n      DWORD OffsetHigh;\r\n    };\r\n    PVOID  Pointer;\r\n  };\r\n  HANDLE    hEvent;\r\n} OVERLAPPED, *LPOVERLAPPED;<\/pre>\n<p>Parmi les propri\u00e9t\u00e9s importantes pour le code utilisateur, <span style=\"color: #0000ff;\">Offset <\/span>et <span style=\"color: #0000ff;\">OffsetHigh <\/span>d\u00e9limitent une portion de 64 bits de donn\u00e9es et indiquent \u00e0 quel endroit on va commencer d&#8217;\u00e9crire dans le fichier. <span style=\"color: #0000ff;\">Offset <\/span>g\u00e8re les 32 premiers bits pour les fichiers &lt; 4Gb (souvenez-vous FAT 32&#8230;), et <span style=\"color: #0000ff;\">OffsetHigh <\/span>les 32 suivants pour les fichiers de plus de 4Gb. Si on ex\u00e9cutait deux IO synchrones, elles s&#8217;ex\u00e9cuteraient l&#8217;une derri\u00e8re l&#8217;autre et le kernel s&#8217;occupperait d&#8217;indiquer \u00e0 la seconde \u00e9criture o\u00f9 elle doit commencer, parce que la premi\u00e8re s&#8217;est termin\u00e9e et donc on sait o\u00f9 on en est. Mais dans le cas d&#8217;\u00e9critures asynchrones, on n&#8217;a pas cette information, donc si on ex\u00e9cute une seconde entr\u00e9e\/sortie avant que la premi\u00e8re ne soit termin\u00e9e, il faut pouvoir g\u00e9rer les offsets soi-m\u00eame. C&#8217;est le r\u00f4le de ces deux propri\u00e9t\u00e9s, mais c&#8217;est \u00e0 nous de les maintenir. Lors d&#8217;une premi\u00e8re ex\u00e9cution, ces deux valeurs doivent \u00eatre initalis\u00e9es (souvent \u00e0 0 pour marquer le premier offset en d\u00e9but de fichier):<\/p>\n<pre name=\"code\" class=\"cpp\">OVERLAPPED ov; \r\nov.Offset = 0; \r\nov.OffsetHigh = 0;<\/pre>\n<p>De son c\u00f4t\u00e9, <span style=\"color: #0000ff;\">hEvent <\/span>est l&#8217;event\u00a0 que l&#8217;on va pouvoir utiliser pour synchroniser le thread sur l&#8217;I\/O asynchrone. Si on utilise cette technique, alors il va falloir l&#8217;initialiser avec CreateEvent() en lui passant un event \u00e0 reset manuel et dans l&#8217;\u00e9tat non &#8211; signal\u00e9 (second et troisi\u00e8me param\u00e8tres, cf MSDN <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms682396%28v=vs.85%29.aspx\">CreateEvent()<\/a>)<\/p>\n<p>Une structure OVERLAPPED est associ\u00e9e \u00e0 chaque I\/O asynchrone que l&#8217;on va effectuer. Si on en lance 8 en m\u00eame temps, il faudra cr\u00e9er et initialiser 8 structures. On peut utiliser un OVERLAPPED [] pour stocker toutes nos instances de structure par exemple. La chose la plus importante au sujet d&#8217;OVERLAPPED, est qu&#8217;elle doit toujours \u00eatre accessible pendant toute la dur\u00e9e de vie de l&#8217;I\/O, donc pas question d&#8217;allouer le tableau d&#8217;OVERLAPPED[] sur la stack. Le cas d&#8217;erreur le plus courant est d&#8217;ex\u00e9cuter un appel \u00e0 ReadFile() ou WriteFile() dans une fonction et g\u00e9rer la compl\u00e9tion dans le main ou une autre fonction, par exemple:<\/p>\n<pre name=\"code\" class=\"cpp\">VOID ReadMyFile(HANDLE h)\r\n{\r\n    OVERLAPPED ov;\r\n    BYTE b[255];\r\n    ov.Offset=0;\r\n    ov.OffsetHigh=0;\r\n    ReadFile(h,b,255,NULL,&amp;ov);\r\n}<\/pre>\n<p>Lorsque la fonction sort la stack est vid\u00e9e alors que le driver pointe encore vers <span style=\"color: #0000ff;\">&amp;ov<\/span> qui n&#8217;est d\u00e9j\u00e0 plus valide.<\/p>\n<p>Pour indiquer \u00e0 Windows que l&#8217;on souhaite faire des I\/O asynchrones sur un fichier, on utilise un flag sp\u00e9cifique<strong> <\/strong><span style=\"color: #0000ff;\"><strong>FILE_FLAG_OVERLAPPED<\/strong><\/span>, que l&#8217;on passe \u00e0 CreateFile(), tel que:<\/p>\n<pre name=\"code\" class=\"cpp\">\/\/ Ouverture du fichier Writethrough + No Buffering + Overlapped ----------------------------\r\nHANDLE hOutputFile=CreateFile(argv[1]\r\n       ,GENERIC_READ\r\n       | GENERIC_WRITE\r\n       ,0\r\n       ,NULL\r\n       ,CREATE_ALWAYS\r\n       ,FILE_ATTRIBUTE_NORMAL\r\n       | FILE_FLAG_WRITE_THROUGH\r\n       | FILE_FLAG_NO_BUFFERING\r\n       | FILE_FLAG_OVERLAPPED\r\n       ,NULL);\r\n\r\n if (INVALID_HANDLE_VALUE==hOutputFile)\r\n {\r\n       printf(\"Unable to open file %s.  Last error=%d\\n\",argv[1],GetLastError());\r\n       return 1;\r\n }<\/pre>\n<p>A noter que cet exemple utilise aussi des I\/Os non bufferis\u00e9es comme nous l&#8217;avons d\u00e9j\u00e0 vu dans <a href=\"http:\/\/blog.capdata.fr\/index.php\/consistence-des-ecritures-avec-sata\/\">l&#8217;article sur SATA<\/a>. SQL Server utilise pr\u00e9cis\u00e9ment tous ces flags pour ouvrir les fichiers MDF, NDF et LDF. Il ne reste plus qu&#8217;\u00e0 appeler ReadFile() ou WriteFile() en passant la structure Overlapped initialis\u00e9e:<\/p>\n<pre name=\"code\" class=\"cpp\">WriteFile(hOutput,csBuffer,dg.BytesPerSector,&amp;dwBytesWritten,&amp;ov)<\/pre>\n<h2>Gestion de Offset et OffsetHigh<\/h2>\n<p>Si on ex\u00e9cute au moins deux appels \u00e0 la suite \u00e0 ReadFile() ou WriteFile() , Windows ne sait pas o\u00f9 le premier en est et o\u00f9 le second peut commencer \u00e0 lire ou \u00e0 \u00e9crire. C&#8217;est donc au programme utilisateur de comptabiliser les offsets exacts o\u00f9 chaque IO va devoir s&#8217;appliquer. On utilise pour cela les deux propri\u00e9t\u00e9s Offset et OffsetHigh de la structure Overlapped.<\/p>\n<p>Historiquement, le compilateur C qui supporte l&#8217;API win32 ne comporte pas de type natif sur 64 bits (ce n&#8217;est pas le cas du compilateur VC++). Donc on ne peut pas tracer des offsets d&#8217;une valeur sup\u00e9rieure \u00e0 4Gb. Et encore aujourd&#8217;hui, il faut jongler avec une construction de type <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa383713%28v=vs.85%29.aspx\">LARGE_INTEGER<\/a> pour arriver \u00e0 jouer avec des offsets au del\u00e0 de 2e32:<\/p>\n<pre name=\"code\" class=\"cpp\">typedef union LARGE_INTEGER {\r\n    struct {\r\n        DWORD LowPart;\r\n        LONG HighPart;\r\n       };\r\n    LONGLONG QuadPart;\r\n};<\/pre>\n<p>En fait le type LARGE_INTEGER est une union d&#8217;un DWORD et d&#8217;un LONG pour former un LONGLONG de 64 bits. Si on consid\u00e8re le sch\u00e9ma ci-dessous:<\/p>\n<p><a href=\"http:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-2678 alignleft\" title=\"large_integer\" src=\"http:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer.jpg\" alt=\"\" width=\"952\" height=\"157\" srcset=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer.jpg 952w, https:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer-300x49.jpg 300w, https:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer-768x127.jpg 768w\" sizes=\"auto, (max-width: 952px) 100vw, 952px\" \/><\/a><\/p>\n<p>On utilisera donc QuadPart pour allouer de nouvelles valeurs d&#8217;offsets sur 64 bits puis LowPart et HighPart pour les passer respectivement \u00e0 Offset et OffsetHigh, quelque chose comme :<\/p>\n<pre name=\"code\" class=\"cpp\">OffsethOutput.QuadPart=0;\r\nwhile(!EOF)\r\n{\r\n    ov.Offset        = OffsethOutput.LowPart;\r\n    ov.OffsetHigh    = OffsethOutput.HighPart;\r\n    BOOL b = Readfile (..., &amp;ov);\r\n    OffsethOutput.QuadPart += (LONGLONG) dg.BytesPerSector;\r\n}<\/pre>\n<h3>G\u00e9rer le retour de ReadFile() \/ WriteFile():<\/h3>\n<p>Voici un exemple d&#8217;appel \u00e0 WriteFile() avec une structure overlapped tel que cod\u00e9 dans l&#8217;exemple:<\/p>\n<pre name=\"code\" class=\"cpp\">if (!WriteFile(hOutput,csBuffer,dg.BytesPerSector,&amp;dwBytesWritten,&amp;ov))\r\n{\r\n     DWORD dwLastErr=GetLastError();            \r\n     if (ERROR_IO_PENDING == dwLastErr)\r\n     {    \r\n         isASYNCWRITE=TRUE;    \/\/ On a une IO asynchrone\r\n         OffsethOutput.QuadPart += (LONGLONG) dg.BytesPerSector;    \/\/ On d\u00e9cale Offset et OffsetHigh de la valeur de l'IO\r\n     }            \r\n     else\r\n     {\r\n          if (ERROR_SUCCESS == dwLastErr)        \/\/ C'est une IO synchrone\r\n          isASYNCWRITE=FALSE;\r\n          else\r\n          {\r\n              printf(\"Error when Writing to file %S. Last error=%d\\n\",argv[1],GetLastError());\r\n              return -1;\r\n          }\r\n      }\r\n}<\/pre>\n<p>D\u00e9j\u00e0 quelque chose doit vous interpeller \u00e0 ce niveau-l\u00e0 du code. Dans les faits, on n&#8217;a jamais la garantie \u00e0 100% d&#8217;obtenir du syst\u00e8me qu&#8217;il poursuive pour nous une IO asynchrone, car l&#8217;IO Manager a toujours le dernier mot. Il peut d\u00e9cider pour une raison ou pour une autre de transformer cette demande en IO synchrone. Donc il faut toujours tester le code retour de WriteFile() quand on lui passe une structure OVERLAPPED:<\/p>\n<ul>\n<li><strong>ERROR_SUCCESS <\/strong>=&gt;Le kernel d\u00e9cide de transformer l&#8217;appel en IO synchrone. Il peut y avoir plusieurs raisons, notamment lorsqu&#8217;on \u00e9crit dans un fichier encrypt\u00e9 NTFS, ou lorsque l&#8217;\u00e9criture d\u00e9clenche une augmentation de taille du fichier, cela doit vous rappeler certaines choses c\u00f4t\u00e9 sikouel&#8230;\u00a0 Une autre condition dans le cas d&#8217;un ReadFile() est que le kernel est capable d\u2019accommoder la lecture imm\u00e9diatement parce que la page demand\u00e9e se trouve d\u00e9j\u00e0 dans le cache NTFS, ce qui ne sera jamais notre cas puisqu&#8217;on utilise FILE_FLAG_NO_BUFFERING.<\/li>\n<li><strong>ERROR_IO_PENDING <\/strong> =&gt; dans ce cas l&#8217;IO est asynchrone. \u00c7a ne sert donc \u00e0 rien\u00a0 d&#8217;attendre de WriteFile() qu&#8217;il nous renvoie une valeur d&#8217;octets \u00e9crits puisque lorsqu&#8217;il retourne au caller, il ne le sait pas lui-m\u00eame. L&#8217;IO va se terminer quelque part dans le futur, donc pour l&#8217;instant on n&#8217;aura rien dans &amp;dwBytesWritten. Dans ce cas on indique la taille de cluster comme incr\u00e9ment au QuadPart pour la prochaine valeur d&#8217;Offset \u00e0 lire.<\/li>\n<li><strong>ERROR_HANDLE_EOF\u00a0 =&gt;<\/strong> peut \u00eatre retourn\u00e9e lorsqu&#8217;on a atteint la fin du fichier par exemple dans le cas d&#8217;une lecture, mais comme on \u00e9crit on ne le teste pas dans notre cas.<\/li>\n<li><strong>AUTRE <\/strong>=&gt; auquel cas c&#8217;est une erreur et il faut la traiter.<\/li>\n<\/ul>\n<h3>R\u00e9cup\u00e9rer le r\u00e9sultat de l&#8217;IO une fois termin\u00e9e avec GetOverlappedResult()<\/h3>\n<p>Utiliser des IO asynchrones, c&#8217;est un peu comme jongler avec plusieurs balles \u00e0 la fois: au bout d&#8217;un moment il va falloir les rattraper. Il faut donc choisir un point dans le code o\u00f9 on va devoir se synchroniser pour attendre le retour de ces IO avant de pouvoir continuer. On peut utiliser les primitives standard de synchronisation telles que <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms687032%28v=vs.85%29.aspx\">WaitForSingleObject()<\/a>, et dans notre cas puisqu&#8217;on attend le retour de plusieurs IO, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms687025%28v=vs.85%29.aspx\">WaitForMultipleObjects()<\/a>.On bloque en passant \u00e0 la primitive le ou les events qui attendent d&#8217;\u00eatre signal\u00e9s:<\/p>\n<pre name=\"code\" class=\"cpp\">dwEventSignPos = WaitForMultipleObjects(MAXOUTSTANDINGIO, hEvents, FALSE, INFINITE) - WAIT_OBJECT_0;<\/pre>\n<p>Lorsqu&#8217;un premier event est signal\u00e9 parce que son IO vient de se terminer, WaitForMultipleObjects() retourne WAIT_OBJECT_0, et le code peut se poursuivre. WAIT_OBJECT_0 repr\u00e9sente le premier event qui vient d&#8217;\u00eatre signal\u00e9, on va s&#8217;en servir pour d\u00e9terminer l&#8217;indice de cet event dans un tableau, comme dans notre exemple comment\u00e9 ci-dessous. On va ensuite devoir r\u00e9cup\u00e9rer le nombre d&#8217;octets \u00e9crits, et si l&#8217;on souhaite r\u00e9utiliser la structure Overlapped, r\u00e9initialiser son event, puis rebloquer en attendant le retour d&#8217;une autre IO:<\/p>\n<pre name=\"code\" class=\"cpp\">ResetEvent (hEvents[dwEventSignPos]);<\/pre>\n<p>Il existe plusieurs primitives win32 pour g\u00e9rer la fin d&#8217;une IO asynchrone:<\/p>\n<ol>\n<li>Utiliser <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms683209%28v=vs.85%29.aspx\">GetOverlappedResults()<\/a>: permet de r\u00e9cup\u00e9rer la quantit\u00e9 d&#8217;octets \u00e9crits ou lus, ainsi que l&#8217;event qui a \u00e9t\u00e9 signal\u00e9.<\/li>\n<li>Utiliser <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms683244%28v=vs.85%29.aspx\">HasOverlappedIOCompleted()<\/a>: renvoie vrai ou faux en fonction de la completion de l&#8217;IO. Les informations d&#8217;octets lus ou \u00e9crits sont disponibles dans la structure Overlapped (Internal contient le code retour et InternalHigh le nombre d&#8217;octets lus ou \u00e9crits). Dans ce cas on n&#8217;aurait pas besoin de bloquer avec WaitForMultipleObjects(), mais il faut pr\u00e9voir plusieurs points dans le code o\u00f9 faire r\u00e9guli\u00e8rement cette v\u00e9rification. C&#8217;est de cette mani\u00e8re que SQL Server proc\u00e8de, comme on le verra dans l&#8217;\u00e9pisode 3. Dans l&#8217;exemple:<\/li>\n<\/ol>\n<pre name=\"code\" class=\"cpp\">if (isASYNCWRITE)\r\n{\r\n    if (!GetOverlappedResult(hOutput,&amp;olwrite[dwEventSignPos],&amp;dwBytesWritten[dwEventSignPos],TRUE))\r\n    {\r\n        printf(\"Error getting pending write IO.  Last error=%d\\n\",GetLastError());\r\n        break;\r\n    }\r\n    else\r\n    {\r\n    \/\/ Action principale: on totalise chaque fin d'\u00e9criture async\r\n    llTotalBytesWritten+=dwBytesWritten[dwEventSignPos];\r\n    }\r\n}<\/pre>\n<p>Il est important de n&#8217;ex\u00e9cuter GetOverlappedResults() que si l&#8217;IO est bien asynchrone, c&#8217;est pourquoi on teste avec un bool\u00e9en d&#8217;abord.<\/p>\n<p>Voil\u00e0, on a fait le tour, place \u00e0 l&#8217;exemple comment\u00e9 !<\/p>\n<h2>Exemple comment\u00e9 d&#8217;utilisation des I\/Os asynchrones:<\/h2>\n<p>Cet exemple reprend le principe de ce qui avait \u00e9t\u00e9 utilis\u00e9 dans l&#8217;<a href=\"http:\/\/blog.capdata.fr\/index.php\/consistence-des-ecritures-avec-sata\/\">article sur SATA<\/a>. Il utilise simplement des \u00e9critures asynchrones en plus. \u00c9videmment \u00e7a n&#8217;a pas grand int\u00e9r\u00eat de faire des \u00e9critures asynchrones dans ce contexte l\u00e0 (mono-thread, on lance un nombre d&#8217;IO puis on bloque sur la completion de l&#8217;ensemble), mais il a l&#8217;avantage d&#8217;\u00eatre simple et de ne pas nous embarquer dans des notions hors cadre de synchronisation de threads et compagnie&#8230; Encore une fois, il s&#8217;agit juste de comprendre le principe de mise en place.<\/p>\n<p>On d\u00e9finit une quantit\u00e9 de donn\u00e9es \u00e0 \u00e9crire dans un fichier (FileSizeInBytes, par d\u00e9faut 10Mb) ainsi qu&#8217;un certain nombre d&#8217;outstanding IOs (MAXOUTSTANDINGIO), c&#8217;est \u00e0 dire d&#8217;IO simultan\u00e9es. On ne peut envoyer plusieurs IO simultan\u00e9es que si on effectue des IO asynchrones, c&#8217;est pourquoi on dit que des compteurs tels que &#8216;Current Disk Queue Length&#8217; par exemple peuvent \u00eatre normalement \u00e9lev\u00e9s avec SQL Server, car on envoie plusieurs IOs en m\u00eame temps.<\/p>\n<pre name=\"code\" class=\"cpp\">\/\/ writethrougasync.cpp : Defines the entry point for the console application.\r\n\/\/\r\n#include \"stdafx.h\"\r\n#include\r\n#include\r\n#include\r\n#include\r\n\r\n#define MAXOUTSTANDINGIO 4            \/\/ Definit le nb d'IO max en cours\r\n#define FileSizeInBytes 10485760    \/\/ Definit la taille max du fichier\r\n\r\nint _tmain(int argc, _TCHAR* argv[])\r\n{\r\n    if (argc!=2)\r\n    {\r\n        printf(\"Usage is: writethrougasync\\n\");\r\n        printf(\"Example:  writethrougasync destfile.dat \\n\");\r\n        return -1;\r\n    }\r\n\r\n    \/\/ Initialisation des diff\u00e9rents objets n\u00e9cessaires: OLs, events, HANDLES, etc... ------------------------------\r\n    OVERLAPPED olwrite[MAXOUTSTANDINGIO];    \/\/ Ol IO pour l'\u00e9criture\r\n    HANDLE hEvents[MAXOUTSTANDINGIO];        \/\/ tableau pour stocker les events \u00e9critures\r\n\r\n    HANDLE hOutput;                             \/\/ HANDLE fichier dest\r\n    LARGE_INTEGER OffsethOutput;                \/\/ (Offsets,OffsetHigh) olwrite \r\n\r\n    DWORD dwBytesWritten[MAXOUTSTANDINGIO];     \/\/ Nb octets lus par olwrite\r\n    LONGLONG llTotalBytesWritten=0;             \/\/ Quantit\u00e9 totale d'octets \u00e9crits\r\n    BOOL isASYNCWRITE=FALSE;                    \/\/ Flag de contr\u00f4le IO async \/ sync pour les \u00e9critures\r\n    int NBIOCOMPLETED=0;                        \/\/ Compteur d'IOs termin\u00e9es\r\n    DWORD dwEventSignPos=0;                     \/\/ Index de l'event signal\u00e9 retourn\u00e9 par WaitForMultipleObjects()\r\n\r\n    \/\/ R\u00e9cup\u00e9ration de la taille du secteur disque avec un IOCTL ---------------------------------------------------\r\n    DISK_GEOMETRY dg;\r\n    HANDLE hDrive;\r\n    DWORD ioctlJnk; \r\n\r\n    hDrive=CreateFile(TEXT(\"\\\\\\\\.\\\\PhysicalDrive0\")\r\n                           ,0\r\n                           ,FILE_SHARE_READ\r\n                          | FILE_SHARE_WRITE\r\n                          ,NULL\r\n                          ,OPEN_EXISTING\r\n                          ,0\r\n                          ,NULL);\r\n\r\n    if (INVALID_HANDLE_VALUE==hDrive)\r\n    {\r\n        printf(\"Unable to open drive PhysicalDrive0. Last error=%d\\n\",GetLastError());\r\n        return 1;\r\n    }\r\n\r\n    if (! DeviceIoControl(hDrive\r\n                          ,IOCTL_DISK_GET_DRIVE_GEOMETRY\r\n                          ,NULL\r\n                          ,0\r\n                          ,&amp;dg\r\n                          ,sizeof(dg)\r\n                          ,&amp;ioctlJnk\r\n                          ,NULL) )\r\n    {\r\n        printf(\"Error on IOCTL (IOCTL_DISK_GET_DRIVE_GEOMETRY) to %s.  Last error=%d\\n\",argv[1],GetLastError());\r\n        return 1;\r\n    }\r\n\r\n    CloseHandle(hDrive);\r\n\r\n    \/\/ Ouverture du fichier cible en write-though \/ no buffering \/ Overlapped -------------------------------------\r\n    hOutput = CreateFile(argv[1]\r\n                         ,GENERIC_READ\r\n                         | GENERIC_WRITE\r\n                          ,0\r\n                          ,NULL\r\n                          ,CREATE_ALWAYS\r\n                          ,FILE_ATTRIBUTE_NORMAL\r\n                          | FILE_FLAG_NO_BUFFERING\r\n                          | FILE_FLAG_WRITE_THROUGH\r\n                          | FILE_FLAG_OVERLAPPED\r\n                          ,NULL);\r\n\r\n    if (INVALID_HANDLE_VALUE==hOutput)\r\n    {\r\n        printf(\"Unable to open output file %S.  Last error=%d\\n\",argv[1],GetLastError());\r\n        return -1;\r\n    }\r\n\r\n    \/\/ Initialisation du tampon --------------------------------------------------------------------------------\r\n    wchar_t *csBuffer = (wchar_t *)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,dg.BytesPerSector * sizeof(wchar_t));\r\n    int len = swprintf_s(csBuffer,dg.BytesPerSector,L\"abcdefghijklmnopqrstuvwxyz\");    \r\n\r\n    \/\/ On initialise les events\r\n    for(int i=0;i<\/pre>\n<ol>\n<li>Comme on veut envoyer 4 IO en m\u00eame temps, on est oblig\u00e9s d&#8217;avoir 4 structures Overlapped et 4 events. Donc on utilise un tableau de dimension [MAXOUTSTANDINGIO] de chaque pour stocker nos objets. Les autres variables locales sont plut\u00f4t \u00e9videntes: un HANDLE pour le fichier, un LARGE_INTEGER pour g\u00e9rer nos offsets, deux variables pour comptabiliser les octets \u00e9crits, etc&#8230;<\/li>\n<li>Comme dans l&#8217;article pr\u00e9c\u00e9dent, l&#8217;utilisation conjointe de FILE_FLAG_WRITE_THROUGH et FILE_FLAG_NO_BUFFERING impose de faire des \u00e9critures align\u00e9es sur la taille du secteur disque, donc on va le r\u00e9cup\u00e9rer avec un IOCTL. Sur mon laptop, il est de 512 octets.<\/li>\n<li>On ouvre le fichier en lui passant les deux flags pr\u00e9-cit\u00e9s plus FILE_FLAG_OVERLAPPED qui indique que l&#8217;on souhaite effectuer des IO asynchrones.<\/li>\n<li>On initialise le buffer \u00e0 \u00e9crire avec swprintf_s()<\/li>\n<li>On initialise les 4 Events avec CreateEvent() en passant bien bManualReset \u00e0 TRUE (on resette manuellement l&#8217;event, prerequis pour se synchroniser dessus) et bInitialState \u00e0 FALSE (non signal\u00e9, il sera signal\u00e9 lors de la fin de l&#8217;IO correspondant \u00e0 la structure overlapped).<\/li>\n<li>On initialise \u00e0 0 les Offsets en utilisant le QuadPart, on commence \u00e0 \u00e9crire en tout d\u00e9but de fichier.<\/li>\n<li>On boucle tant qu&#8217;on n&#8217;a pas \u00e9crit la quantit\u00e9 de donn\u00e9es attendue (10mb):<\/li>\n<li>On met \u00e0 jour Offset et OffsetHigh puis on envoie les 4 premi\u00e8res IO simultan\u00e9es: si GetLastError() renvoie 997 (ERROR_IO_PENDING), ASYNCWRITE passe \u00e0 true et on a bien une IO asynchrone. Sinon si le code retour de WriteFile() est ERROR_IO_SUCCESS, alors on a une IO synchrone, et sinon on a une erreur.<\/li>\n<li>On incr\u00e9mente les valeurs d&#8217;offsets en ajoutant la quantit\u00e9 d&#8217;octets envoy\u00e9s avec le QuadPart.<\/li>\n<li>Lorsqu&#8217;on a envoy\u00e9 notre premier paquet d&#8217;IOs, on bloque avec WaitForMultipleObjects sur le tableau d&#8217;events. Lorsqu&#8217;un premier event est signal\u00e9, on ex\u00e9cute un GetOverlappedResult () sur la structure Overlapped correspondante.<\/li>\n<li>On r\u00e9initialise l&#8217;event pour cette IO (ResetEvent() repasse bInitialState \u00e0 FALSE).<\/li>\n<li>Et on incr\u00e9mente la quantit\u00e9 totale d&#8217;octets \u00e9crits jusqu&#8217;\u00e0 \u00e9crire les 10Mb, etc&#8230;<\/li>\n<\/ol>\n<h2>R\u00e9sum\u00e9:<\/h2>\n<ul>\n<li>Par d\u00e9faut, une op\u00e9ration d&#8217;entr\u00e9e\/sortie est synchrone, et le thread qui l&#8217;a initi\u00e9e doit bloquer et attendre que chaque op\u00e9ration soit termin\u00e9e avant de poursuivre. Pour ne pas bloquer, on utilise des IO asynchrones.<\/li>\n<li>On indique le flag FILE_FLAG_OVERLAPPED \u00e0 la primitive d&#8217;ouverture pour indiquer que l&#8217;on souhaite faire des IO asynchrones.<\/li>\n<li>La structure Overlapped contient des informations sur chaque IO asynchrone. Elle doit \u00eatre initialis\u00e9e et maintenue dans le code utilisateur.<\/li>\n<li>Il existe plusieurs fa\u00e7ons de se synchroniser sur le retour des IOs, la plus simple \u00e9tant d&#8217;utiliser un event associ\u00e9 \u00e0 chaque structure overlapped. Cet event doit \u00eatre \u00e0 reset manuel et initialis\u00e9 dans l&#8217;\u00e9tat non-signal\u00e9.<\/li>\n<li>Il faut maintenir les pointeurs d&#8217;offsets manuellement dans le cas d&#8217;une IO asynchrone. On utilise les propri\u00e9t\u00e9s Offset et OffsetHigh pour cela.<\/li>\n<li>Rien ne garantit que l&#8217;IO sera bien asynchrone, il existe des cas o\u00f9 il ne sera pas possible de le faire, donc il faut pouvoir tester le code retour de ReadFile() ou WriteFile() et agir en cons\u00e9quence.<\/li>\n<li>On peut r\u00e9cup\u00e9rer la fin d&#8217;une IO asynchrone en utilisant GetOverlappedResult() ou HasOverlappedIOCompleted().<\/li>\n<\/ul>\n<p>La prochaine fois, on verra comment faire pour utiliser les IO completion ports pour g\u00e9rer les IO asynchrones.<\/p>\n<p>A+. David B.<\/p>\n<h2>Biblio:<\/h2>\n<p><a href=\"http:\/\/www.amazon.com\/Windows-via-Pro-Jeffrey-Richter\/dp\/0735624240\">Windows via C++<\/a> (Jeffrey Richter \/ Christope Navarre)<br \/>\n<a href=\"http:\/\/www.flounder.com\/\">Floundercraft<\/a>: a Joseph M. Newcomer resource<br \/>\n<a href=\"http:\/\/download.microsoft.com\/download\/6\/E\/8\/6E882A06-B71B-4642-9EB4-D1EA0D6223C8\/SQL%20Server%20IO%20Reliability%20Program%20Requirements%20Document.docx\">http:\/\/download.microsoft.com\/download\/6\/E\/8\/6E882A06-B71B-4642-9EB4-D1EA0D6223C8\/SQL%20Server%20IO%20Reliability%20Program%20Requirements%20Document.docx<\/a> : SQL Server I\/O Reliability Program Review.<\/p>\n<h2>Notes:<\/h2>\n<p>[1] Dave Cutler, le papa de VMS et du premier kernel windows NT avait \u00e9t\u00e9 tellement d\u00e9prim\u00e9 par l&#8217;impl\u00e9mentation des APC dans VMS qu&#8217;il aurait d\u00e9clar\u00e9 plus tard lorsqu&#8217;il travaillait sur le kernel NT : &#8220;<span style=\"font-family: arial,sans-serif;\"><span style=\"font-family: arial,sans-serif;\"><span style=\"font-family: arial,sans-serif;\"><span style=\"font-family: arial,sans-serif;\">Asynchronous callback I\/O will go into Windows over my dead body!<\/span><\/span><\/span><\/span>&#8220;<br \/>\n[2] en dehors de ReadFileEx et WriteFileEx qui sont r\u00e9serv\u00e9es aux I\/Os asynchrones.<\/p>\n<a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox\" data-provider=\"twitter\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Twitter\" href=\"https:\/\/twitter.com\/intent\/tweet?url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"twitter\" title=\"Share on Twitter\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/twitter.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox\" data-provider=\"linkedin\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Linkedin\" href=\"https:\/\/www.linkedin.com\/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868&#038;title=I%2FO%20asynchrones%20%28%C3%A9pisode%201%29\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"linkedin\" title=\"Share on Linkedin\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/linkedin.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox\" data-provider=\"mail\" rel=\"nofollow\" title=\"Share by email\" href=\"mailto:?subject=I%2FO%20asynchrones%20%28%C3%A9pisode%201%29&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1868\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"mail\" title=\"Share by email\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/mail.png\" \/><\/a>","protected":false},"excerpt":{"rendered":"<p>Ce post est le premier d&#8217;une s\u00e9rie sur les API syst\u00e8mes impliqu\u00e9es au niveau de SQL Server: Gestion des entr\u00e9es\/sorties: alignement, I\/Os asynchrones, alternate streams, scatter gather, I\/O completion ports, etc&#8230; Le multithreading:\u00a0 synchronisation, events, mutexes, spinlocks, sections critiques&#8230; La&hellip; <a href=\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\" class=\"more-link\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":2678,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[173,5],"tags":[181],"class_list":["post-1868","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-operating-system","category-sqlserver","tag-io"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.8 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG\" \/>\n<meta property=\"og:description\" content=\"Ce post est le premier d&#8217;une s\u00e9rie sur les API syst\u00e8mes impliqu\u00e9es au niveau de SQL Server: Gestion des entr\u00e9es\/sorties: alignement, I\/Os asynchrones, alternate streams, scatter gather, I\/O completion ports, etc&#8230; Le multithreading:\u00a0 synchronisation, events, mutexes, spinlocks, sections critiques&#8230; La&hellip; Continuer la lecture &rarr;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\" \/>\n<meta property=\"og:site_name\" content=\"Capdata TECH BLOG\" \/>\n<meta property=\"article:published_time\" content=\"2011-07-05T16:02:20+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-09-13T13:38:13+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"952\" \/>\n\t<meta property=\"og:image:height\" content=\"157\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"David Baffaleuf\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"David Baffaleuf\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"19 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\"},\"author\":{\"name\":\"David Baffaleuf\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/person\/136297da9f61d6e4878abe0f48bc5fbf\"},\"headline\":\"I\/O asynchrones (\u00e9pisode 1)\",\"datePublished\":\"2011-07-05T16:02:20+00:00\",\"dateModified\":\"2019-09-13T13:38:13+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\"},\"wordCount\":3167,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/blog.capdata.fr\/#organization\"},\"keywords\":[\"I\/O\"],\"articleSection\":[\"Operating System\",\"SQL Server\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\",\"url\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\",\"name\":\"I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG\",\"isPartOf\":{\"@id\":\"https:\/\/blog.capdata.fr\/#website\"},\"datePublished\":\"2011-07-05T16:02:20+00:00\",\"dateModified\":\"2019-09-13T13:38:13+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/blog.capdata.fr\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"I\/O asynchrones (\u00e9pisode 1)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.capdata.fr\/#website\",\"url\":\"https:\/\/blog.capdata.fr\/\",\"name\":\"Capdata TECH BLOG\",\"description\":\"Le blog technique sur les bases de donn\u00e9es de CAP DATA Consulting\",\"publisher\":{\"@id\":\"https:\/\/blog.capdata.fr\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.capdata.fr\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/blog.capdata.fr\/#organization\",\"name\":\"Capdata TECH BLOG\",\"url\":\"https:\/\/blog.capdata.fr\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp\",\"contentUrl\":\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp\",\"width\":800,\"height\":254,\"caption\":\"Capdata TECH BLOG\"},\"image\":{\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.linkedin.com\/company\/cap-data-consulting\/mycompany\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/person\/136297da9f61d6e4878abe0f48bc5fbf\",\"name\":\"David Baffaleuf\",\"sameAs\":[\"http:\/\/www.capdata.fr\"],\"url\":\"https:\/\/blog.capdata.fr\/index.php\/author\/dbaffaleuf\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/","og_locale":"fr_FR","og_type":"article","og_title":"I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG","og_description":"Ce post est le premier d&#8217;une s\u00e9rie sur les API syst\u00e8mes impliqu\u00e9es au niveau de SQL Server: Gestion des entr\u00e9es\/sorties: alignement, I\/Os asynchrones, alternate streams, scatter gather, I\/O completion ports, etc&#8230; Le multithreading:\u00a0 synchronisation, events, mutexes, spinlocks, sections critiques&#8230; La&hellip; Continuer la lecture &rarr;","og_url":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/","og_site_name":"Capdata TECH BLOG","article_published_time":"2011-07-05T16:02:20+00:00","article_modified_time":"2019-09-13T13:38:13+00:00","og_image":[{"width":952,"height":157,"url":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2011\/07\/large_integer.jpg","type":"image\/jpeg"}],"author":"David Baffaleuf","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"David Baffaleuf","Dur\u00e9e de lecture estim\u00e9e":"19 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#article","isPartOf":{"@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/"},"author":{"name":"David Baffaleuf","@id":"https:\/\/blog.capdata.fr\/#\/schema\/person\/136297da9f61d6e4878abe0f48bc5fbf"},"headline":"I\/O asynchrones (\u00e9pisode 1)","datePublished":"2011-07-05T16:02:20+00:00","dateModified":"2019-09-13T13:38:13+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/"},"wordCount":3167,"commentCount":0,"publisher":{"@id":"https:\/\/blog.capdata.fr\/#organization"},"keywords":["I\/O"],"articleSection":["Operating System","SQL Server"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/","url":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/","name":"I\/O asynchrones (\u00e9pisode 1) - Capdata TECH BLOG","isPartOf":{"@id":"https:\/\/blog.capdata.fr\/#website"},"datePublished":"2011-07-05T16:02:20+00:00","dateModified":"2019-09-13T13:38:13+00:00","breadcrumb":{"@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.capdata.fr\/index.php\/io-asynchrones-episode-1\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/blog.capdata.fr\/"},{"@type":"ListItem","position":2,"name":"I\/O asynchrones (\u00e9pisode 1)"}]},{"@type":"WebSite","@id":"https:\/\/blog.capdata.fr\/#website","url":"https:\/\/blog.capdata.fr\/","name":"Capdata TECH BLOG","description":"Le blog technique sur les bases de donn\u00e9es de CAP DATA Consulting","publisher":{"@id":"https:\/\/blog.capdata.fr\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.capdata.fr\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/blog.capdata.fr\/#organization","name":"Capdata TECH BLOG","url":"https:\/\/blog.capdata.fr\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/","url":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp","contentUrl":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp","width":800,"height":254,"caption":"Capdata TECH BLOG"},"image":{"@id":"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.linkedin.com\/company\/cap-data-consulting\/mycompany\/"]},{"@type":"Person","@id":"https:\/\/blog.capdata.fr\/#\/schema\/person\/136297da9f61d6e4878abe0f48bc5fbf","name":"David Baffaleuf","sameAs":["http:\/\/www.capdata.fr"],"url":"https:\/\/blog.capdata.fr\/index.php\/author\/dbaffaleuf\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/1868","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/comments?post=1868"}],"version-history":[{"count":280,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/1868\/revisions"}],"predecessor-version":[{"id":2732,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/1868\/revisions\/2732"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/media\/2678"}],"wp:attachment":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/media?parent=1868"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/categories?post=1868"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/tags?post=1868"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}