0Pricing
C Academy · レッスン

ブロッキングI/OとノンブロッキングI/O

イベントループが重要な理由を学びます。

「ブロッキングI/OとノンブロッキングI/O」はCoddyKit上の無料C Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C Academyコースには全4レッスンが含まれています。

ブロッキングの意味

ブロッキングシステムコールは、処理を続行できるようになるまで、呼び出し元のスレッドを停止させます。データのないソケットで recv() を呼び出すと、バイト列が到着するまでカーネルがスレッドを待機させます。

これは、1接続につき1スレッドで直線的にコードを書くため、理解しやすい方式です。ただし、数千のクライアントを同時に処理する必要があると、コストが問題になります。

ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);

スケーリングの問題

ブロッキング I/O では、1つの応答しないクライアントがスレッド全体をブロックします。典型的な解決策は、接続ごとに1つのスレッド(またはプロセス)を割り当てることです。

これはある程度までは機能しますが、10,000スレッドでは10,000個のスタック、大量のコンテキストスイッチ、スケジューラーのオーバーヘッドが発生します。これが有名な C10k 問題であり、サーバーをイベント駆動設計へ向かわせました。

ノンブロッキングモード

ノンブロッキングソケットは待機しません。呼び出しをすぐに完了できない場合は、直ちに -1 を返し、errno に EAGAIN または EWOULDBLOCK を設定します。

後で再試行する責任はコード側にあります。これにより、1つのソケットで停止することなく、1つのスレッドで多数のソケットを扱えます。

ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    /* no data right now, try again later */
}

fcntlでO_NONBLOCKを設定する

fcntl() で O_NONBLOCK フラグを追加すると、ディスクリプターをノンブロッキングに切り替えられます。必ず最初に現在のフラグを読み取り、その後ビットを OR してください。そうしないと、他の設定を上書きしてしまいます。

この同じヘルパーは、リスニングソケット、受け入れたクライアントソケット、パイプのいずれにも使用できます。

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

部分読み取りの処理

ノンブロッキング I/O では、操作が部分的にしか完了しないのが普通です。recv() は要求したバイト数より少ないバイト数を返すことがあり、send() もバッファーの一部しか受け付けないことがあります。

送受信済みの量を追跡し、そこから処理を再開する必要があります。1回の呼び出しですべてのバイトが処理されると決めつけないでください。

size_t sent = 0;
while (sent < len) {
    ssize_t w = send(fd, buf + sent, len - sent, 0);
    if (w < 0) { if (errno == EAGAIN) break; else return -1; }
    sent += w;
}

ビジーウェイトは誤り

ノンブロッキングソケットを使う素朴な方法は、すべてのソケットをループして絶えず再試行することです。このビジーウェイトは、何も起きていないときでも CPU を100%消費します。

本当に必要なのは、カーネルに「準備ができたディスクリプターを教えて、そこまでスリープさせてください」と依頼することです。それを実現するのが、まさに準備完了通知です。

準備完了通知

I/O 多重化を使うと、1つのスレッドで多数のディスクリプターを同時に待機し、少なくとも1つが準備できたときだけ起床できます。監視はカーネルが行います。

代表的なインターフェースは select() と poll() です。どちらも機能しますが、呼び出しのたびにすべてのディスクリプターを再走査するため、規模が大きくなるとコストが高くなります。

fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);

selectとpollがスケールしない理由

select() と poll() はどちらも O(n) です。呼び出しごとにディスクリプターの集合全体をカーネルへ渡し、カーネルがすべてを走査します。その後、準備ができたものを見つけるために、こちらもすべてを再び走査します。

select() には FD_SETSIZE による上限もあり、通常は約1024です。数千の接続では、このオーバーヘッドが支配的になります。

epollの登場

epoll は Linux におけるスケーラブルな解決策です。ディスクリプターへの関心を一度登録すると、カーネルが準備状態を追跡する内部データ構造を保持します。

待機するたびに、実際に準備ができたディスクリプターだけが返されます。そのため、コストは接続総数ではなくアクティブな接続数に応じて増加します。準備完了イベントごとのコストは、おおむね O(1) です。

int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */

ノンブロッキングとepollの組み合わせ

epoll とノンブロッキングソケットは相性のよい組み合わせです。epoll がディスクリプターの準備完了を通知し、ノンブロッキング呼び出しによって待機せずにデータを読み切れます。

epoll に渡すソケットには、必ず O_NONBLOCK を設定してください。そうしないと、誤った起床や部分読み取りによって、単一のイベントループスレッドがブロックされる可能性があります。

set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);

メンタルモデル

サーバーを次のようなループとして考えてみましょう。epoll_wait() でブロックし、準備ができた少数のディスクリプターを受け取り、それぞれにノンブロッキング処理を行い、繰り返します。

スレッドはアイドル時にはスリープし、実際の処理があるときだけ起床します。これで、1つのスレッドで数万の接続を効率的に処理できます。

理解度チェック

ノンブロッキングソケットについての理解度を確認しましょう。

まとめ

ブロッキング I/O は単純ですが、接続ごとにスレッドを占有するため、大規模環境では対応できません。ノンブロッキング I/O はスリープする代わりに、EAGAIN を返してすぐに終了します。

ソケットをタイトなループでポーリングすると CPU を無駄に消費するため、準備完了通知を使用します。select/poll は O(n) ですが、epoll は数千を超える接続にも対応できます。次は epoll を設定します。

よくある質問

「ブロッキングI/OとノンブロッキングI/O」レッスンは無料ですか?

はい。「ブロッキングI/OとノンブロッキングI/O」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C Academyコースには全4レッスンが含まれています。

「ブロッキングI/OとノンブロッキングI/O」で何を学びますか?

イベントループが重要な理由を学びます。 ブラウザで直接実行するハンズオンコードでC Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「ブロッキングI/OとノンブロッキングI/O」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC Academyレッスンでコードを書いて実行できますか?

はい。すべてのC Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ブロッキングI/OとノンブロッキングI/O
  2. epollをセットアップする
  3. イベントループ
  4. シンプルなエコーサーバー
← C Academyに戻る