「非同期IO」の版間の差分
m →使用方法 |
|||
46行目: | 46行目: | ||
== Linuxでのサンプルプログラム == |
== Linuxでのサンプルプログラム == |
||
< |
<syntaxhighlight lang="c"> |
||
/* 非同期I/Oによるファイル出力の例 */ |
/* 非同期I/Oによるファイル出力の例 */ |
||
#include <sys/types.h> |
#include <sys/types.h> |
||
106行目: | 106行目: | ||
return 0; |
return 0; |
||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
== Windows == |
== Windows == |
2020年7月5日 (日) 23:02時点における版
非同期I/O (Asynchronous I/O) とは、入出力の処理を、その要請元のプロセス・スレッドとは独立に(非同期に)行う、入出力のAPIの類型である。
概要
ブロッキング・非ブロッキングとの違い
非同期I/Oはほぼ必ず非ブロッキングI/O (non-blocking I/O) であるため、非常にしばしば混同されるが、同期 or 非同期と、ブロッキング or 非ブロッキングという分類は、必ずしも一致しない。O_NONBLOCK
が設定されたファイル記述子に対して通常のreadやwriteを行うと非ブロッキングになるが、それは「ブロックされるようであればエラーにする」という動作になるのであって、非同期になるのではない(たいていのI/O操作はOS内のバッファなどによって、同期型のAPIでもブロックすることなく完了できることも多い)。ディスクに実際に書き込まれるまでを待つかどうか、という観点での同期・非同期もあるが、それはここで扱っているものとは別の話である(詳細は文献等の、フラグ O_DSYNC
, O_DIRECT
についての記述や英語版記事 en:Raw device などを参照のこと)。
非同期I/Oとは、
- バッファの内容が、カーネル等によってコピーされるか、あるいはプログラマの責任で処理が完了するまで要求元のプロセスがそれを保持しなければならない
- (権限違反など、即座にカーネルがエラー等にできる場合を除き)入出力の成否も、入出力を要求するシステムコールの結果としては得られず、コールバックか、別のシステムコール等で改めて得る必要がある
- 以上のような制限の下に、入出力要求のシステムコールはブロックせず、最小限の処理ですぐに終了する
といったようなスタイルの入出力APIによるI/Oである。よって非同期I/Oが利用されるのは、「時間制約の厳しいRTOSだから」といったような理由ではない。排他制御の都合などでブロックさせられないとか、あるいは、性能上の理由ではエンタープライズ用途で欲されることもあれば、イベントドリブン型のフレームワークであるために必要であるといった場合もある。別スレッドを使うことで、プロセス内で非同期I/Oのように見せかけるライブラリ(フレームワーク)といったものもあり得る。
実装
Linuxでは、POSIX-XSIあるいはPOSIX 1003.1bの実装が行われている。
Windowsでは、Windows NT系列 (Windows NT 3.1以降) の全てのバージョンで実装が行われている。
使用方法
基本的な呼び出し方法は、以下のステップである(前提とするAPIによって異なる。ここで示すのは一例)。
- 実行するシステムコールの内容をリンクリストで記述する。
- リンクリストをパラメータとして、非同期システムコールを発行する(呼び出す)。
- エラーあるいは、終了しているかどうかを、問い合わせるシステムコールを発行する。
- 終了待ちシステムコールを発行するか、既に発行したシステムコールをキャンセルするシステムコールを発行する。
組込型RTOSやミニコンピュータでの実装は、非同期システムコール発行時に、システムサービスコールをOSの処理として行うが、UNIXやLinuxの処理では、ユーザースレッドとして処理を行っている実装が多い。
下記の例では、lio_listio(...);
に続いて、aio_suspend(...);
を実行しているため、実質的にwrite()と同じになる。
Linuxでのサンプルプログラム
/* 非同期I/Oによるファイル出力の例 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <aio.h>
#include <sys/mman.h>
#define DATA_BUF_SIZE 4096
#define DATA_BUF_NUM 128
int main(void)
{
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 ((fd = open("datafile", (O_CREAT | O_WRONLY), 0666)) < 0)
{
exit(1);
}
/* リンクリストの作成 */
for (n = 0; n < DATA_BUF_NUM; n++)
{
Aio_buff[n] = (unsigned char*)memalign(sysconf(_SC_PAGESIZE), DATA_BUF_SIZE);
memset((void *)(Aio_buff[n]), n, DATA_BUF_SIZE); /* データはページ単位にnで埋めている */
Aiocb[n].aio_buf = Aio_buff[n];
Aiocb[n].aio_offset = (long long)(DATA_BUF_SIZE * n);
Aiocb[n].aio_nbytes = (long long)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(LIO_WAIT, (struct aiocb **)&List[0], DATA_BUF_NUM, &sig);
/******/
/* この間ファイル出力と並行して、別の処理を記述できる */
/******/
/* 非同期I/Oの終了待ち */
aio_suspend((const struct aiocb **)&List[0], DATA_BUF_NUM, &timeout);
/* エラーステータスの確認 */
for (n = 0; n < DATA_BUF_NUM; n++)
{
status = aio_error(&(Aiocb[n]));
if (status) printf("%d is error %d:%s\n", n, status, strerror(status));
}
/* 終了ステータスの確認 */
for (n = 0; n < DATA_BUF_NUM; n++)
{
status = aio_return(&(Aiocb[n]));
if (status != DATA_BUF_SIZE) printf("%d is write error\n", n);
}
close(fd);
return 0;
}
Windows
Microsoft Windows環境では、Windows NT 3.1以降の全てのディスクI/O、Winsock (バージョン2.0以降) などのWindows APIに非同期バージョンの関数がいくつか用意されている。例えばReadFileやWriteFile APIは、OVERLAPPED構造体に非同期I/Oのための現在のコンテキストを保持し、結果を待機するためのカーネルシグナルオブジェクトを指定することができる。
より高度な実装を行う場合は、これらのシグナルオブジェクトとスレッドを動的に管理する「I/O完了ポート」を利用し、最適なワーカースレッド数の制御とI/OオフロードをAPIレベルで実現できる。
このほか、.NET FrameworkやWindowsランタイムの一部に非同期I/Oをサポートするメソッドが実装されている。C#などの.NET言語やC++においてこれらの非同期I/Oを利用するインフラとして、TPLやPPLが用意されている。
その他
コールバックなどを利用した非同期IOの結果の取得は、設計やプログラミングが煩雑になる。特に出力よりも入力で問題が大きい。非同期プログラミングなどと呼ばれる、言語仕様あるいは高機能なライブラリでサポートされる構造化されたコールバックの利用は、この負担を軽減する。特に、スレッドも無くブロックしないイベントドリブンのプログラミングが必要なJavaScriptの場合が典型と言える。
言語仕様でこれをサポートするものとしては、async/await構文による機能がある。C# 5.0/VB.NET 11以降や、Python 3.5以降ではそれが使える。F#には非同期ワークフロー (asynchronous workflow) と呼ばれる、TPLとは異なる独自のインフラを利用した非同期プログラミングのための機能が備わっている。C++ではC++11にてライブラリでstd::asyncが標準化された。