C と Go のデータ競合と ThreadSanitizer の限界

2026/09/13 13:09

C と Go のデータ競合と ThreadSanitizer の限界

RSS: https://news.ycombinator.com/rss

要約

日本語翻訳:

タイトル「C および Go の Data Races と ThreadSanitizer の限界」を著者とした Phil Eaton が指摘する通り、Clang、GCC、Go、Swift、OCaml でサポートされているツールである ThreadSanitizer (TSan) には、2012 年以来 3 つの主要な改修が施されたにもかかわらず、単純なバグではなくアーキテクチャ的な限界が存在します。特に、~255 スレッド境界を越える場合にカウンターの巻き戻りにより信頼できないように Race の検出ができず、Go ウェブサービスなどの高コンカレンシーシステムに頻繁に影響を与えます。具体的な盲点としては、Farzam Dorostkar による 2025 年の博士論文などで特定された最近の研究で指摘された問題、グラニュールあたり限られた「4 セルの予算」を使い果たすようなメモリアクセスパターン、Go の

sync.Pool
のシャドウスロットハッシングなど特定の実装詳細によって Race が隠蔽されること、そして頻繁なロックによりスレッドごとに割り当てられる 14 ビットのカウンターの使用上限に達することなどが挙げられます。したがって、標準的な TSan の使用方法のみを頼りにしてプロダクション環境での安全性を図ることは不十分であり、開発者はツールの設計に伴うこれらの制約と潜在的な偽陰性について認識しておく必要があります。

本文

C と Go を対象としたソフトウェアインフラ上の意思決定:データレイスと ThreadSanitizer の限界について

データレイスとは何か?レース検出器がどのように機能するのか?もしあなたのレース検出器にバグがある場合どうなるのか?それらについて詳しく見ていきます。

執筆者:Phil Eaton | 2026 年 9 月 6 日

注記: あなたは購読者としてこの記事を優先的にご閲覧いただけます。皆様のご支援により、このような記事の執筆が可能になっています。ありがとうございます。


概要と背景

コードに自信を持たせるためのテストを実行すると、並列処理(コンカレンシー)を追加するとバグが**非決定性(nondeterministic)**になります。

  • レース検出器を有効にすることで、実行中のコード部分でデータレイスが発生したことを報告できます。
  • テストが成功または失敗に関わらず、潜在的なバグが存在することを意味します。

主な言語(Clang、GCC、Go、Swift、OCaml)のレース検出器は、ほぼすべて LLVM の ThreadSanitizer (TSan) を使用しています。

  • TSan は文書化が不十分で、バージョン 3(2021 年リリース)ではソースコードを読むことを推奨されています。
  • TSan の前身には Eraser や FastTrack などがあり、それぞれ独自の限界を持っています。

この記事では、TSan が以下のようなアーキテクチャ上の選択により**過負荷状態(overloaded)**となり、明確なデータレイスを検出漏れを起こす仕組みを解説します。

  • 255 を超えるスレッド境界をまたぐデータレイスの報告不可。
  • Go の「リクエストごとに 1 つの goroutine」という設計では珍しくない状況。

背景:データレイスとは何か?

  • C11 標準: 異なるスレッド内の不一致な動作が同時に発生し、互いに先行・遅延もない場合(非原子操作)、データレイスが未定義の動作をもたらします。
  • Go 言語: 全てのアクセスが
    sync/atomic
    でない場合、書き込みと読み取り(または他の書き込み)が並行して発生した時点でデータレイスです。

基本例:カウンター更新(C)

以下のコードは、2 つのスレッドが共有変数

counter
にアクセスします。通常の実行ではバグに気づかないことがありますが、負荷が高くなると失敗します。

#include <pthread.h>
#include <stdlib.h>

long counter;

void* bump(void* n) {
  for (long i = 0; i < *(long*)n; i++) {
    long v = counter; // Read
    // some logic
    counter = v + 1;  // Write
  }
  return 0;
}

int main(void) {
  pthread_t p, q;
  long n = atol(getenv("N") ?: "100");
  pthread_create(&p, 0, bump, &n);
  pthread_create(&q, 0, bump, &n);
  pthread_join(p, 0);
  pthread_join(q, 0);
  return (counter == n * 2) ? 0 : 1;
}

実行結果の非決定性:

  • ストレス低い状態(N=100): 通常成功 (
    0
    )
  • ストレス高い状態(N=10,000): 失敗 (
    1
    ) が発生する可能性があります。

レース検出器による検出

-fsanitize=thread
フラグを付けてコンパイルすると、TSan が正常にレイスを検出します。

$ gcc -fsanitize=thread -g counter_data_race.c -o counter_data_race
$ ./counter_data_race; echo $?
==================
WARNING: ThreadSanitizer: data race (pid=704862)
  Read of size 8 at 0x56110c2ce018 by thread T2...
  Previous write of size 8 at 0x56110c2ce018 by thread T1...
==================
ThreadSanitizer: reported 1 warnings
66

Go 言語における同様の挙動

Go でも

-race
フラグを有効にすると同様に検出されます。

$ go build -race -o counter_data_race_go counter_data_race.go
$ ./counter_data_race_go; echo $?
==================
WARNING: DATA RACE
Read at 0x0000005bda00 by goroutine 8...
Previous write at 0x0000005bda00 by goroutine 7...
==================
Found 1 data race(s)
66

誤検知と一般化されたレイス(General Race)

  • ライセンスを適切に使えば、TSan は「一般化されたレイス」すら検出できません。これは TSan の限界の一つです。

C の理想化されたサブセットの解釈実行

C のコードをモデル化する Python インタープリターを作成し、FastTrack 風のベクトル時計を用いて TSan が概ねどのように動作するかを示します。

インタープリターの構築

  • 機能: 関数内のステートメントをパースし、スレッドとロックの管理を行います。
  • 実装方針: C の完全なコンパイラではなく、理想化されたサブセットの実行に焦点を当てます。

主要な処理ロジック:

  • write
    : メモリへの値書き込み(変数名でマッピング)。
  • compare
    : 2 つの値の比較。
  • thread_create
    /
    thread_join
    : スレッドの作成と結合管理。
  • mutex_lock
    /
    mutex_unlock
    : ロックの取得と解放、ブロック処理のシミュレーション。

FastTrack とベクトル時計の実装

TSan のアルゴリズムの核心である「ベクトル時計(Vector Clocks)」を模擬します。

  • 各スレッド: 単調な時間カウンターと、他のすべてのスレッドに対する認識した時間を含むベクトル時計を持っています。
  • グローバルマッピング: 最後に読み取った/書き込んだスレッドと時刻(タイク)を記録します。

検出ロジック (

access
):

  1. 現在のアドレスの「最後の書き込み」を参照。
  2. もし現在のスレッドがその書き込みの「時間認識値」よりも遅れている場合、データレイスと判定します。

検出器との連携

インタープリターに検出器の機能をフックし、以下のステップで動作を検証します。

def link(interp, detector):
    # 元の関数を保存
    original_step = interp.step
    
    def step(thread, statement):
        # 実際のステートメントを実行
        original_step(thread, statement)
        
        # ロック/アンロック時の同期情報を更新
        if operation == "mutex_lock":
            detector.acquire(...)
        elif operation == "mutex_unlock":
            detector.release(...)

検証結果:

no_sync.c
(ロックなしの読み書き)をこのインタープリターで実行すると、TSan と同等にレイスが検出されます。


TSan が失敗する箇所

TSan は有用なツールですが、以下のような制限により特定のケースで検出漏れを起こします。

1. スレッド数への制限(255)

  • 制限: TSan は合計 255 つのスレッド を追跡し、その枠を超えると古いスロットを破棄して新しいスロットを再利用します。
  • 問題: 永続的な書き込みスレッドが長く存在し、255 を超えて新たな読み取りスレッドが作成された場合、TSan はレイスを検出できません。

C 言語例 (

toomanythreads.c
):

  • メインスレッドと永続スレッドを別枠で管理し、新しい作業スレッドを作成します。
  • スレッド数が 254 を超えると(合計 255 の制限)、レイスを検出しないようになります。

Go 言語例:

  • Go ランタイム自体もスロットを使用するため、限界はより早期に達します。

2. バジェット(Budgets)の限界

TSan はリソース使用量を制限しており、以下のようなケースで過負荷状態になります。

A. スレッド境界での追跡

  • C11/GCC: 合計 255 スロット。
  • Go: ランタイムのオーバーヘッドにより、スロットはより早く消費されます。

B. リリース(ロック解放)への制限

  • 仕様: 各スレッド内のカウンターは最大 14 ビット で管理され、ある閾値を超えると予算が枯渇します。
  • 現象: 同じスレッドで同一のロックを頻繁に解放・再取得すると、TSan の内部バッファーが飽和し、レイスを検出できなくなります。
    • C 例:数百万回のロック解放を行うと報告されなくなります。
    • Go 例:
      sync.Mutex
      pthread_mutex_t
      よりもコストが高く、3 倍早く予算を使い果たします。

C. アクセス履歴の限界(8 バイト粒度)

  • 仕様: TSan はメモリを 8 バイト粒度 にグループ化し、各グループに対して 4 セル の履歴しか保持しません。
  • 現象: 同じ 8 バイト領域に対して多くの異なるアドレスへのアクセスが行われると、古い記録が破棄(evict)されます。これにより、レイスの検出ができなくなります。

3. sync.Pool のハッシュ衝突による誤検知回避

Go 言語の

sync.Pool
は、オブジェクトを再利用するためにアドレスのハッシュ値を使用します。

  • 仕組み: TSan は実際のポインタではなく、ハッシュされたスロット(128 スロット)に基づいてレイスを判定します。
  • 問題点: ハッシュ衝突により、実際には無関係な 2 つのオブジェクトが同じスロットに割り当てられる可能性があります。
    • その場合、TSan は「同期済み」と誤認し、データレイスを報告しません(False Negative)。

Go 言語例 (

poolrace.go
):

  • ハッシュ衝突するオブジェクトを意図的に作成すると、本来あるべきレイスが検出されなくなります。
$ go run -race poolrace.go collide; echo $?
0
# (本来は Data Race であるはずのプログラムが正常終了)

終焉的思考

レース検出器は非常に有用ですが、以下の点に注意が必要です:

  • エッジケース: TSan は多くのバジェット制限を持っていますが、これらは公式には十分に文書化されていません。
  • 追加対策:
    chan struct{}
    を使用する際の欠落したレイス検出や、予算の限界を探索することが推奨されます。

重要: 本記事は「TSan が悪いツールである」と言うのではなく、「ツールには限界があり、それを理解しておくことが重要である」と述べています。

Happy hunting(愉快な探検)。間違いや疑問があれば、編集者に送信してください。

同じ日のほかのニュース

一覧に戻る →

2026/09/16 4:25

「System One モデルと Jev」の紹介

## Japanese Translation: TypeSafe AI は、即座で誤りのない自動意思決定のために設計された画期的な「System One」モデルである **Jev** を発表しました。従来の言語モデルが単なるテキスト文字列を生成するのに対し、Jev は型安全構造化値を出力し、データの一貫性を確保しながらハルシネーションを排除します。このアーキテクチャ変更は並列サンプラにより支えられており、すべての応答を同時に処理して結果を 70ms から 500ms の範囲で提供可能にしています。これにより既存のツールと比較して最大 200 倍高速化されながら、著しく低いコスト(入力トークンあたり約 0.042 ドルで出力コストはほぼゼロ)を実現しています。システムは、標準的なアライメント手法に依存せず、検証可能な報酬を最優先する「Calibrated Decisions」用の強化学習を用いた専門的なトレーニングを受けました。 カハニーマンの快思考といった認知科学の概念に触発された Jev は、現在のフロンティアモデルと対比して顕著な効率性でベンチマークされています。Jev は、高速ゲームインタラクションや迅速なビッグデータ処理といったリアルタイムアプリケーションを可能にしており、既存のツールに対して最大 200 倍高速化されながらコストは大幅に削減されています。その結果、リアルタイムインテリジェンスに依存する業界では、一貫した信頼スコアと近乎ゼロのレイテンシを提供するシステムへの転換が期待でき、これにより現在の大規模言語モデル展開におけるボトルネックを効果的に解決します。

2026/09/15 21:31

Show HN: 鳥の声に反応して、19 世紀の挿絵風に描く電子ペーパーフレーム

## Japanese Translation: 「Fugleramme」プロジェクトは、ノルウェー・ベルゲンの厨房の窓を、ローカル AI と歴史的自然史のアートを組み合わせることでリアルタイムデジタルバードウォッチングキオスクへと変えます。BirdNET-Go を使用してデバイス上で鳴き声を検出し、公有ドメインソースからの手切りされた 1800 年代の図版として一致結果を Inky Impression e-ink パネルに表示します(アート作品は AI で生成されておらず、一部のものは補正されています)。800 枚以上の切り抜きがあり、400 種以上をカバーし、主にスキャンディナヴィア、英国、中欧の種を対象とし、より広いカバレッジが計画されています。検出された種は背景除去処理され、体格サイズに合わせたテクスチャ付きページに配置され、空のスロットには裸の枝が表示されます。システムは Raspberry Pi 5(推奨)、Inky Impression 13.3 インチディスプレイ、マイク、A4 フレームでローカルで動作しますが、Web キオスクまたは Docker(`ghcr.io/arnegiacomo/fugleramme`)または `install.sh` を通じても動作します。また、ローカルまたはリモートの BirdNET-Go インスタンスをターゲットとすることも可能です。現在は初期開発段階であり、コミュニティからの貢献(修正、ドキュメント、アート作品)を歓迎しており、バグ報告には Discussions を使用し、コード・アート・ドキュメントの変更には PR を使用します。WWF のポスター(Axel Thorenfeldt 氏)や AvianVisitors に着想を得た Fugleramme は、アクセシブルなハードウェアが厳選された公有ドメインのアートを通じて複雑なオーディオデータを可視化する方法を示しています。コードは MIT ライセンス、検出および画像は適切な CC ライセンス(適用可能な場合、非商用制限を含む)の下にあります。 ## Text to translate: The "Fugleramme" project turns a kitchen window in Bergen, Norway, into a real-time digital bird-watching kiosk by combining local AI with historical natural history art. Using BirdNET-Go, it detects bird calls on-device and displays matches as hand-cut 1800s illustrations from public-domain sources on an Inky Impression e-ink panel; no artwork is AI-generated (some is retouched). Over 800 cut-outs cover more than 400 species, primarily Scandinavian, British, and central European, with broader coverage planned. Detected species are background-removed and packed onto a textured page sized by body mass; empty slots show a bare perch. The system runs locally on a Raspberry Pi 5 (recommended), an Inky Impression 13.3" display, a microphone, and an A4 frame, but can also run as a web-only kiosk or via Docker (`ghcr.io/arnegiacomo/fugleramme`) or `install.sh`. It supports pointing at local or remote BirdNET-Go instances. Currently in early development, the project invites community contributions (fixes, docs, artwork) and uses Discussions for bug reports while PRs are for code/art/docs changes. Inspired by a WWF poster by Axel Thorenfeldt and AvianVisitors, Fugleramme demonstrates how accessible hardware can visualize complex audio data through curated public-domain art, with code under MIT and detection/images under appropriate CC licenses (including non-commercial constraints where applicable).

2026/09/16 6:07

ドイツのライネメタルが戦術システム接続用武器プロトコルのオープンソース化を発表

## Japanese Translation: The onboardapi ライブラリは、Object Management Group (OMG) から Data Distribution Service (DDS) によるデータ交換の標準化を通じて、センサーシステムとソフトウェア間の通信を簡素化します。ddkit ツールキットを基盤とし、この C++ ベースのソリューションは OMG の XTypes および XCDR2 エンコーディングを活用してシームレスな相互運用性を確保し、データモデルが進化するに連れて完全な後方互換性を保証します。Java、Python、C#、および .NET 向けのラッパーを通じて多言語統合をサポートし、クライアント/サービスアーキテクチャに関するドキュメント、セットアップガイド、コード例、変更ログ、 browsable データモデルインターフェースを含む豊富なリソースを提供します。このライブラリは、堅牢なクロスプラットフォーム接続を維持することでスケール可能な産業用アプリケーションを可能にします。そのインターフェースは EPL v2.0 ライセンスに基づき、ランタイムライブラリは EULA-RME-SDK-1.0 ライセンスに従います。 ## Text to translate: The onboardapi library streamlines communication between sensor systems and software by standardizing data exchange through the Data Distribution Service (DDS) from the Object Management Group (OMG). Built on the ddkit toolkit, this C++-based solution ensures seamless interoperability via OMG's XTypes and XCDR2 encoding, guaranteeing full backward compatibility as the data model evolves. It supports multi-language integration through wrappers for Java, Python, C#, and .NET, with extensive resources including documentation on Client/Service architecture, setup guides, code examples, a changelog, and browsable Data Model interfaces. The library facilitates scalable industrial applications by maintaining robust cross-platform connectivity; its interfaces are licensed under EPL v2.0, while runtime libraries adhere to the EULA-RME-SDK-1.0 license.