ブログ記事
io.Writerからシステムコールへ:『Goならわかるシステムプログラミング』
システムプログラミングは敷居が高く感じられがちだが、見慣れたコードを起点にすれば近づきやすくなる。渋川よしき『Goならわかるシステムプログラミング 第2版』は、Goの身近なインターフェースからOSの低レイヤへと地続きに降りていく構成で、システムの動作原理が身につく一冊だ。
1. 見慣れたコードから低レイヤへ降りる
本書はC言語でカーネルをボトムアップに学ぶのではなく、普段使う io.Writer や io.Reader を出発点にする。
これらのシンプルなインターフェースが、実はOSのファイル操作や低レベルなシステムコールの鏡写しになっていることを解き明かす。自分が書いたアプリケーションコードが起点になるため、低レイヤの挙動が「自分に関係のある身近な領域」として見えてくる。小さなインターフェースを組み合わせて高度な機能を作るGoらしい設計も同時に学べる。
2. なぜGoは速いのか:goroutineの軽さとミニOS構造
「Goは並行処理が得意」と言われる理由を、本書はOSのプロセスやスレッドとの比較で明らかにする。
OSスレッドは起動時に大きなスタックメモリを消費し、切り替えのたびにカーネルを介した重い処理が発生する。一方、Goのgoroutineは初期スタックがごく小さく、Goランタイム自身がスレッド上でgoroutineを管理する仕組みを採る。この構造により、切り替えコストを抑えながら大量の並行処理を捌ける。本書はGoランタイムを一種の「ミニOS」として説明し、並行処理パターンまで体感的に学ぶ道筋を示す。
flowchart TD
subgraph OS["OSスレッド(重い)"]
T1["スレッド A\n大きなスタック"] & T2["スレッド B\n大きなスタック"]
end
subgraph GO["Go ランタイム(軽い)"]
GR["Goランタイム\n(ミニOS)"]
GR --> G1["goroutine 1\n小さなスタック"]
GR --> G2["goroutine 2"]
GR --> G3["goroutine N"]
end
OS -->|"カーネルが管理(コスト大)"| OS
GO -->|"ランタイムが管理(コスト小)"| GO
style OS fill:#fce8e8,stroke:#e53935
style GO fill:#e8f8e8,stroke:#4caf50
3. 第2版の加筆:シェルと起動プロセスで全体を繋ぐ
第2版では、コマンドシェルとプロセス起動に関する新しい章が加わった。
ターミナルに打ったコマンドをシェルがどう解釈し、環境変数を読み込んで子プロセスを生成するのか。ビルドされたバイナリがメモリ上にどう配置され、ランタイムの初期化を経て main 関数が動き出すのか。プログラムがOS上で生まれ、動き、終わるまでの一連の流れをコードで追える。この全体像は、コンテナ環境でのデバッグや最適化の土台になる。
こんな人に向いている
「Go でコードは書けるが、並行処理の仕組みが腑に落ちていない」「コンテナやシステムコールの話が出るたびに会話についていけない」というエンジニアに特に向いている。Goを業務で使っている中級者が、自分のコードがOSレベルでどう動いているかを把握するための一冊として最適だ。
事前に必要な知識は「Goが読める」程度でよい。システムプログラミングの前提知識は要求せず、むしろ「システムプログラミングとは何か」を具体的なGoコードで実感させることが本書の目的だからだ。LinuxやmacOSなどUNIX系環境で動作させることを前提としている点は念頭に置いておくといい。
逆に、いま向いていないのはGoの文法をまだ習得していない段階の人だ。タイトルの「わかる」に反して難易度は中級者向けで、動くコードを書ける前提で話が進むため、文法の学習中に手を出すと本題のシステムの説明で足が止まりやすい。OSやネットワークの基礎がまったくない非CS出身者も、前提の説明が薄い箇所で置いていかれることがある。その場合はGoの入門を一通り終えてから戻ってくるほうが、同じ内容でも吸収の度合いが変わってくる。
読んだ後のステップ
本書を読んだあと、「では実際にOSがどう動いているか、カーネル視点でもっと深く理解したい」という欲求が生まれることが多い。そこから先は、カーネルやCコンパイラの内部構造を扱う低レイヤ専門の書籍へと進む読者が多い。また、「並行処理のパターンをもっとしっかり使いこなしたい」という方向では、Go の並行処理に特化したリソースや設計パターンの本が次のステップになる。
本書が扱う「アプリとOSの境界」は、コンテナ・クラウドが当たり前の時代に改めて問われる領域だ。パフォーマンスチューニングや障害調査で、低レイヤの視点が一つの仮説を立てる起点になる。
次に読む本
関心の向き先で、次の一冊は分かれる。goroutine と channel をさらに使いこなしたいなら『Go言語による並行処理』へ進むのが本書からの自然な続きだ。本書で掴んだ「goroutine がOSスレッドとどう対応するか」という土台の上に、メモリ共有ではなく通信で協調させる並行設計と、競合やデッドロックを避ける組み立て方が積み上がる。一方、こうした低レイヤの手当てをフレームワークがどこまで肩代わりしているかを知りたいなら『Spring徹底入門』が対になる。素のコードでサーバーを書く苦労を一度味わったあとだと、フレームワークの便利さと制約を差し引きして読めるようになる。
goroutine と channel の落とし穴も学べる
実務でGoを書いているエンジニアが本書を読むと、「なぜgoroutineを使い過ぎるとメモリが増えるのか」「channel のバッファをどう設計するべきか」という問いに根拠を持って答えられる。並行処理のバグ(競合状態・デッドロック)が起きたとき、「goroutineがOSスレッドとどう対応しているか」の視点から根本原因を追える。アプリケーションエンジニアとしてGoを書いてきた人が、一段下のシステム的な視点を手に入れる機会として本書は機能する。
goroutineリークとデッドロックを「なぜ起きるか」で語れるか
goroutineリークやチャネルのデッドロックといった実務上のバグを、「Goランタイムがスレッドをこう使っているから」と説明できるかどうかは、障害調査の速度に直結する。パフォーマンスチューニングでも、OSとの境界を知らずに数値を眺めていても仮説を立てにくい。本書が育てるのは、その「一段下から見る」視点だ。
筆者の体験から
Goのバックエンドサービスを担当することになった時期、コードは書けても挙動の理由を説明できずに焦りを感じていた。straceの出力を眺めたりネットの記事をつまみ食いしたりしても知識が点在するばかりで、本書を読んで初めてio.Writer/io.Readerとシステムコールが地続きだという点にようやく説明がついた。以降、標準ライブラリのインターフェースを見るときに裏でシステムコールが何回呼ばれているかを意識するようになり、goroutineとOSスレッドの比較は「軽いから雑に立ち上げていい」という思い込みを崩す根拠にもなった。ただしシステムコールの章だけは手を動かす実感が薄く、記憶への定着がやや弱かったのも正直なところだ。あるバッチ処理でメモリがじわじわ増える不具合を追ったとき、pprofでgoroutine数を確認すると、受信側のいないchannelへの送信で止まったままのgoroutineが積み重なっていた。以来、goroutineを起動する箇所では終了経路も必ずセットで確認するようにしている。
DevBookPath のマップで確認する
この本の前後の読書順は、DevBookPath のグラフで確認できます。
本記事のリンクには Amazon アソシエイト等の広告が含まれる場合があります。リンク経由の購入で運営者に紹介料が支払われることがあります。
この記事を共有
この地図を共有