CreateThreadと_beginthreadexの使い分けと実装上の留意点
Windows環境でマルチスレッドプログラミングを行う際、スレッド生成APIとしてCreateThreadと_beginthreadexの二つが選択肢に上がります。どちらも最終的にはWindowsカーネルが管理するスレッドオブジェクトを作成しますが、その設計思想や挙動には明確な差異があります。Visual C++で開発を始めたばかりの頃は、両者が実質的に同じものだと誤解されがちです。
歴史的に見ると、CreateThreadはWin32 APIが生まれた初期から存在するカーネル寄りの関数です。一方、_beginthreadexはCランタイム(CRT)がスレッドごとに必要な内部状態を正しく管理するために後から導入されました。Microsoftの公式ドキュメントでは、CRT関数を利用するスレッドでは_beginthreadexの使用が強く推奨されています。
両者の選択を誤ると、メモリリークや未定義動作といった深刻なバグの原因となります。とくにerrnoやstrtok、mbstowcsといった関数を別スレッドで呼び出した際に問題が顕在化することが多いです。表面上は問題なく動作するケースもあるため、リリース後に発覚する厄介な不具合にもつながります。
本記事では、CreateThreadと_beginthreadexの仕組みを整理し、それぞれの使い分けと実装時の注意点を体系的に解説します。関連技術のリファレンスはWindows API技術文書サイトなども参考になります。
APIの基本的位置付け
CreateThreadはkernel32.dllが提供するWin32 APIであり、スレッドの作成をWindowsカーネルに直接要求します。引数としてセキュリティ属性、スタックサイズ、起動ルーチンなどを取り、生成されたスレッドのハンドルを返します。C言語の規約とは独立して設計されているため、CRTの内部状態とは結びつきません。
_beginthreadexはmsvcrt.dll(あるいは静的にリンクされたCRT)に属する関数で、内部でCreateThreadを呼び出すラッパーとして実装されています。CreateThreadを直接呼ぶ前に、CRTが必要とするスレッドごとのデータ構造を割り当て、初期化を行います。戻り値の型はuintptr_tですが、CreateThreadのHANDLEと実質的に同じ値を返します。
これらのAPI選択は、スレッド関数の本体で何を行うかによって決まります。純粋なWin32 API呼び出しだけで完結する場合はCreateThreadでも問題ありませんが、printfやmallocなどのCRT関数を使う場面では_beginthreadexが安全です。
Cランタイムとの連携
_beginthreadexがCRTを重視する理由は、スレッドローカルストレージにあります。errnoやstrtokの内部バッファ、mbstowcsの状態などは、スレッドごとに独立して管理される必要があります。CreateThreadで生成したスレッドからこれらの関数を使うと、内部状態用のメモリが解放されないままプロセス終了まで残ります。
これは_endthreadexやスレッド関数のreturn時にCRTが_cleanupルーチンを実行する仕組みになっているためです。CreateThreadだけでスレッドを終えると、この後処理が完全にスキップされます。結果として、わずかなリークが積み重なって長期的にはメモリ消費が膨らみます。
実例として、ファイルI/Oを伴うスレッドではfopenとfcloseが内部でスレッドローカルな状態を使うため、CreateThreadとの組み合わせで予期しないリークが報告されてきました。安全側に倒して_beginthreadexを使うのが、Microsoft推奨の方針です。
スレッド終了時のリソース管理
_beginthreadexで生成したスレッドは、_endthreadexを呼ぶか、起動ルーチンからreturnすることで終了できます。returnした場合は内部的に_endthreadexが呼ばれ、CRTが確保したリソースが確実に解放されます。CreateThreadで生成したスレッドでは、ExitThreadを呼ぶと即座にスレッドが終了し、CRTの後処理が完全にスキップされます。
ExitThreadの呼び出しは、kernel32.dllレベルで強制終了させるため、DLLのデタッチ通知が正常に呼ばれない可能性があります。動的にロードされたDLLがスレッド終了時にクリーンアップを行えないと、メモリリークやデッドロックにつながることがあります。純粋なWin32コードであれば問題になりませんが、CRTやその他のライブラリを使う場面では避けるべきです。
スレッド関数の最後にreturnを記述しておけば、_endthreadex経由で安全に終了処理が進みます。コードの可読性と安全性の両面から、returnによる正常終了が最も推奨されます。
シグナル、例外、浮動小数点処理の差異
CRTのsignal関数やraise関数を使ったシグナル処理は、スレッドごとに登録情報が管理されます。CreateThreadで生成したスレッドでは、signalハンドラが想定通りに動作しないことがあります。_beginthreadexはシグナル処理のための内部テーブルを正しく初期化するため、移植性の高いシグナルコードを書く場合に有利です。
構造化例外処理(SEH)に関しては、両APIともWindowsの仕組みの上で動作するため、基本的に大きな差はありません。ただし、CRTが独自に設定する例外ハンドラがある場合は、_beginthreadexでないと正常にインストールされないケースがあります。
浮動小数点演算においても、_beginthreadexはFPU制御ワードやMMX/SSE状態をスレッドごとに適切に扱うよう配慮されています。CreateThreadではスレッド生成時のFPU状態が継承される仕組みのため、浮動小数点演算の設定に依存するコードでは予期しない結果が生じることがあります。レジストリアクセス時の権限管理については、RegOpenKeyExでの読み取り手法などの資料も合わせて確認しておくと、システムプログラミング全体の理解が深まります。
パフォーマンスへの影響
_beginthreadexは内部でCreateThreadを呼ぶため、オーバーヘッドは微小です。スレッド生成コスト全体から見れば無視できるレベルで、実測しても差が出ない場合がほとんどです。パフォーマンスを理由にCreateThreadを選ぶ合理性はありません。
逆に、CreateThreadで生成したスレッドでCRTを後から使うと、リソースリーク検出やデバッグ情報の収集で性能が劣化することがあります。本番環境では表面化しなくても、デバッグビルドでは顕著になる場合があります。
スレッドプールを活用する設計が増えている現在、CreateThreadや_beginthreadexを直接呼ぶこと自体が少なくなっています。ただし、レガシーコードの保守や組み込み環境では依然として重要であり、正しい知識を持っておく価値があります。
実務における選択基準
実際の開発現場では、新規コードでは_beginthreadexを既定とし、CreateThreadは保守対象のlegacyコードに限るのが妥当です。既存プロジェクトの移行時には、スレッド関数の実装を確認しながら段階的に置き換えていくアプローチが現実的でしょう。
両者の技術的差異を以下の表にまとめます。選定時の判断材料としてご活用ください。
| 項目 | CreateThread | _beginthreadex |
|---|---|---|
| 提供モジュール | kernel32.dll | msvcrt.dll(CRT) |
| CRT用スレッドデータ | 確保しない | 確保して初期化 |
| 終了時の後処理 | 呼び出し側責任 | CRTが_cleanupを実行 |
| 戻り値の型 | HANDLE | uintptr_t |
| errnoやstrtokなどの利用 | 非推奨 | 安全 |
| ExitThreadの直接呼び出し | 問題化しやすい | 避けるべきだが影響は少ない |
| 浮動小数点状態の独立性 | 継承される | 独立して扱われる |
開発現場での推奨事項
これらの知見を踏まえ、実際の開発プロジェクトで心がけるべきポイントを整理します。ガイドラインとして明文化しておくと、新規メンバーへの教育コストを抑えられます。コードレビューの段階で早期に発見できる体制を整えておくことが重要です。
- CRT関数を一つでも使う可能性があるスレッドでは、迷わず_beginthreadexを採用する
- 純粋なWin32 APIのみであっても、保守性と可搬性のため_beginthreadexを選ぶ
- ExitThreadの呼び出しは避け、スレッド関数末尾のreturnで正常終了させる
- WaitForSingleObjectで終了を待ち、CloseHandleでハンドルを明示的に解放する
- 静的解析ツールやレビューチェックリストに「CreateThreadの使用箇所」を項目化する
CreateThreadと_beginthreadexのどちらを採用するかは、プロジェクトの最初の段階で決定すべき設計判断です。後から方針を変更すると、スレッド生成箇所をすべて書き換える必要があり、テストコストも大きくなります。新規スレッドを追加するレビュー時には、必ずどちらのAPIが使われているかを確認し、_beginthreadexが選ばれていることをチェック項目に加えておくと、コードベース全体の品質を均一に保てます。