非同期IO

非同期I/O  とは、入出力の処理を、その要請元のプロセス・スレッドとは独立に行う、入出力のAPIの類型である。

1. 概要
-----

1.1. ブロッキング・非ブロッキングとの違い
-----------------------

非同期I/Oはほぼ必ず非ブロッキングI/O  であるため、非常にしばしば混同されるが、同期 or 非同期と、ブロッキング or 非ブロッキングという分類は、必ずしも一致しない。POSIX環境において、O_NONBLOCKが設定されたファイル記述子に対して通常のreadやwriteを行うと非ブロッキングになるが、それは「ブロックされるようであればエラーにする」という動作になるのであって、非同期になるのではない。ディスクに実際に書き込まれるまでを待つかどうか、という観点での同期・非同期もあるが、それはここで扱っているものとは別の話である。

非同期I/Oとは、

バッファの内容が、カーネル等によってコピーされるか、あるいはプログラマの責任で処理が完了するまで要求元のプロセスがそれを保持しなければならない
入出力の成否も、入出力を要求するシステムコールの結果としては得られず、コールバックか、別のシステムコール等で改めて得る必要がある
以上のような制限の下に、入出力要求のシステムコールはブロックせず、最小限の処理ですぐに終了するといったようなスタイルの入出力APIによるI/Oである。よって非同期I/Oが利用されるのは、「時間制約の厳しいRTOSだから」といったような理由ではない。排他制御の都合などでブロックさせられないとか、あるいは、性能上の理由ではエンタープライズ用途で欲されることもあれば、イベントドリブン型のフレームワークであるために必要であるといった場合もある。別スレッドを使うことで、プロセス内で非同期I/Oのように見せかけるライブラリといったものもあり得る。

2. 実装
-----

Linuxでは、POSIX-XSI、POSIX 1003.1bあるいはio_uringの実装が行われている。

Windowsでは、Windows NT系列  の全てのバージョンで実装が行われている。

3. 使用方法
-------

基本的な呼び出し方法は、以下のステップである。

実行するシステムコールの内容をリンクリストで記述する。
リンクリストをパラメータとして、非同期システムコールを発行する。
エラーあるいは、終了しているかどうかを、問い合わせるシステムコールを発行する。
終了待ちシステムコールを発行するか、既に発行したシステムコールをキャンセルするシステムコールを発行する。組込型RTOSやミニコンピュータでの実装は、非同期システムコール発行時に、システムサービスコールをOSの処理として行うが、UNIXやLinuxの処理では、ユーザースレッドとして処理を行っている実装が多い。

下記の例では、lio_listio;に続いて、aio_suspend;を実行しているため、実質的にwriteと同じになる。

4. Linuxでのサンプルプログラム
-------------------

/* 非同期I/Oによるファイル出力の例 */

include <sys/types.h>
include <sys/stat.h>
include
include
include
include
include <sys/mman.h>
define DATA_BUF_SIZE 4096
define DATA_BUF_NUM 128int main { int fd; int n, status; unsigned char *Aio_buff[DATA_BUF_NUM]; struct aiocb Aiocb[DATA_BUF_NUM]; struct aiocb *List[DATA_BUF_NUM];

if , 0666)) < 0) { exit; } /* リンクリストの作成 */ for  { Aio_buff[n] = memalign, DATA_BUF_SIZE); memset, n, DATA_BUF_SIZE); /* データはページ単位にnで埋めている */ Aiocb[n].aio_buf = Aio_buff[n]; Aiocb[n].aio_offset = ; Aiocb[n].aio_nbytes = DATA_BUF_SIZE; Aiocb[n].aio_fildes = fd; Aiocb[n].aio_reqprio = 0; Aiocb[n].aio_lio_opcode = LIO_WRITE; Aiocb[n].aio_sigevent.sigev_notify = SIGEV_NONE; List[n] = &Aiocb[n]; } /* 非同期I/Oの発行 */ lio_listio&List[0], DATA_BUF_NUM, &sig);  /* この間ファイル出力と並行して、別の処理を記述できる */  /* 非同期I/Oの終了待ち */ aio_suspend&List[0], DATA_BUF_NUM, &timeout); /* エラーステータスの確認 */ for  { status = aio_error); if  printf); } /* 終了ステータスの確認 */ for (n = 0; n <
DATA_BUF_NUM; n++) { status = aio_return); if  printf; } close; return 0; }<WtXmlEndTag />

5. Windows
----------

Microsoft Windows環境では、Windows NT 3.1以降の全てのディスクI/O、Winsock  などのWindows APIに非同期バージョンの関数がいくつか用意されている。例えばReadFileやWriteFile APIは、OVERLAPPED構造体に非同期I/Oのための現在のコンテキストを保持し、結果を待機するためのカーネルシグナルオブジェクトを指定することができる。

より高度な実装を行う場合は、これらのシグナルオブジェクトとスレッドを動的に管理する「I/O完了ポート」を利用し、最適なワーカースレッド数の制御とI/OオフロードをAPIレベルで実現できる。

6. 非同期プログラミング環境
---------------

コールバックなどを利用した非同期I/Oの結果の取得は、設計やプログラミングが煩雑になる。特に出力よりも入力で問題が大きいdate=2021-02}}。非同期I/Oに限った話ではないが、一般的な非同期処理を記述する「非同期プログラミング」を容易にするために、ライブラリや言語構文によるサポートが用意されているプログラミング環境もある。

ライブラリによるサポートは、例えばJava 1.5にて標準化されたname=Futureや、C++11にて標準化されたstd::async/std::futureなどがあり、これらはスレッドベースのFuture パターン  を実現する。.NET Framework/.NET CoreではC#などの.NET言語から利用可能なタスク並列ライブラリ  が、またMicrosoft Visual C++の同時実行ランタイムではC++から利用可能な並列パターンライブラリ  が用意されている。さらにこれらを発展させたものとして、C# 5.0/VB.NET 11以降や、Python 3.5以降にはasync/await構文が用意されている。なお、F#には非同期ワークフロー  と呼ばれる、TPLとは異なる独自のインフラを利用した非同期プログラミングのための機能が備わっている。C++ではC++20にてco_await構文が標準化された。

JavaScriptはシングルスレッドで動作するため、非同期プログラミングはイベント駆動ベースの疑似的な手法に頼らざるを得なかったが、Web Workerによってマルチスレッドプログラミングがサポートされるようになった。そのほか、ECMAScript 2015  ではPromiseが、ES2017ではasync/await構文が標準化された。

7. 脚注
-----

8. 関連項目
-------

スレッド 
コールバック