Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14358] ファイルの概要

このコミットは、Go言語のランタイムにおけるスライス(slice)のappend操作に関するものです。特に、競合状態(race condition)検出のためのインストゥルメンテーション(計測)の修正に焦点を当てています。

コミット

commit 3f7f030c5965bbf62ae2cb54f10ea01d2f49e212
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Nov 8 20:37:05 2012 +0400

    runtime: fix instrumentation of slice append for race detection
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/6819107

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/3f7f030c5965bbf62ae2cb54f10ea01d2f49e212

元コミット内容

runtime: fix instrumentation of slice append for race detection

このコミットメッセージは、Goランタイムにおけるスライスへのappend操作のインストゥルメンテーションが、競合状態検出の目的で修正されたことを示しています。

変更の背景

Go言語には、並行処理におけるデータ競合(data race)を検出するための「Race Detector」という強力なツールが組み込まれています。データ競合は、複数のゴルーチン(goroutine)が同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は、プログラムの予測不能な動作やバグの主要な原因となります。

スライスはGo言語の基本的なデータ構造であり、動的な配列として機能します。append関数は、スライスに要素を追加するために使用されます。この操作は、内部的に新しいメモリの割り当てや既存の要素のコピーを伴うことがあります。Race Detectorが正しく機能するためには、プログラム内のすべてのメモリアクセス(読み込みと書き込み)が適切にインストゥルメント(計測)される必要があります。

このコミットが行われた背景には、append操作が内部的に実行するメモリ読み込みの一部が、Race Detectorによって適切に追跡されていなかったという問題があったと考えられます。具体的には、appendの際に既存のスライスxに別のスライスyの要素を追加する場合、yの要素が読み込まれる必要がありますが、この読み込みがRace Detectorの対象となっていなかった可能性があります。これにより、yの要素が別のゴルーチンによって同時に変更された場合でも、Race Detectorがデータ競合を報告できないという潜在的な問題が生じていました。

前提知識の解説

Go言語のスライス (Slice)

Goのスライスは、配列のセグメントを参照するデータ構造です。スライスは、基盤となる配列へのポインタ、長さ(len)、容量(cap)の3つのコンポーネントで構成されます。

  • ポインタ: スライスが参照する基盤となる配列の先頭要素へのポインタ。
  • 長さ (len): スライスに含まれる要素の数。
  • 容量 (cap): スライスの先頭要素から基盤となる配列の末尾までの要素の数。

append関数は、スライスに要素を追加するために使用されます。append(s, elems...)のように呼び出され、selemsを追加した新しいスライスを返します。容量が不足している場合、appendはより大きな新しい基盤配列を割り当て、既存の要素をコピーしてから新しい要素を追加します。

Go言語のRace Detector

GoのRace Detectorは、並行プログラムにおけるデータ競合を検出するためのツールです。これは、プログラムの実行中にメモリアクセスを監視し、競合するアクセスパターンを特定します。Race Detectorは、コンパイル時に特別なインストゥルメンテーションをコードに追加することで機能します。これにより、メモリの読み書きが発生するたびに、その操作が記録され、他のゴルーチンからのアクセスと照合されます。

Race Detectorは、以下の条件がすべて満たされた場合にデータ競合を報告します。

  1. 少なくとも2つのゴルーチンが同じメモリ位置にアクセスする。
  2. 少なくとも1つのアクセスが書き込みである。
  3. アクセスが同期メカニズム(ミューテックス、チャネルなど)によって保護されていない。

Race Detectorは、開発者が並行処理のバグを特定し、修正するのに非常に役立ちます。

インストゥルメンテーション (Instrumentation)

インストゥルメンテーションとは、プログラムの実行時の動作を監視・分析するために、コードに計測用の命令やフックを追加するプロセスを指します。Race Detectorの場合、メモリの読み書き操作の前後で、その操作に関する情報を記録するためのコードが自動的に挿入されます。これにより、Race Detectorはどのゴルーチンがどのメモリ位置にアクセスしたかを追跡し、競合を検出できます。

技術的詳細

このコミットは、Goランタイムのsrc/pkg/runtime/slice.cファイル内のruntime·appendslice関数に対する変更です。この関数は、Go言語のappend組み込み関数が内部的に呼び出すC言語(またはGoのランタイムがCで書かれていた当時のCのような言語)の実装です。

変更前は、runtime·appendslice関数内で、既存のスライスxの要素に対するracereadpc(Race Detectorによる読み込みのインストゥルメンテーション)とracewritepc(Race Detectorによる書き込みのインストゥルメンテーション)は行われていましたが、追加されるスライスyの要素に対する読み込みのインストゥルメンテーションが欠落していました。

具体的には、append(x, y...)のような操作では、yの要素がxの基盤配列にコピーされるか、新しい基盤配列にコピーされる前に読み込まれる必要があります。この読み込み操作がRace Detectorによって追跡されていないと、もし別のゴルーチンが同時にyの要素を書き換えていた場合、Race Detectorはその競合を検出できません。

このコミットは、yの要素が読み込まれる際にruntime·racereadpcが呼び出されるように修正することで、このギャップを埋めています。これにより、append操作中に発生するすべてのメモリ読み込みがRace Detectorの監視対象となり、より正確な競合検出が可能になります。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/slice.cファイル内のruntime·appendslice関数にあります。

--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -86,6 +86,8 @@ runtime·appendslice(SliceType *t, Slice x, Slice y, Slice ret)\n \t\t\truntime·racereadpc(x.array + i*t->elem->size, pc);\n \t\tfor(i=x.len; i<x.cap; i++)\n \t\t\truntime·racewritepc(x.array + i*t->elem->size, pc);\n+\t\tfor(i=0; i<y.len; i++)\n+\t\t\truntime·racereadpc(y.array + i*t->elem->size, pc);\n \t}\n \n \tif(m > x.cap)\n```

## コアとなるコードの解説

変更されたコードは以下の部分です。

```c
		for(i=0; i<y.len; i++)
			runtime·racereadpc(y.array + i*t->elem->size, pc);

このコードは、runtime·appendslice関数内の既存のRace Detectorインストゥルメンテーションブロックに追加されました。

  • for(i=0; i<y.len; i++): これは、追加されるスライスyのすべての要素をループ処理するためのものです。y.lenはスライスyの現在の長さを表します。
  • runtime·racereadpc(y.array + i*t->elem->size, pc);: この行が追加された主要な修正です。
    • y.array: スライスyが参照する基盤となる配列の先頭アドレスです。
    • i*t->elem->size: i番目の要素のオフセットを計算します。t->elem->sizeは、スライスが保持する要素の型(t->elem)のサイズです。これにより、yi番目の要素のメモリアドレスが正確に計算されます。
    • runtime·racereadpc(...): これはRace Detectorの内部関数で、指定されたメモリアドレスからの読み込み操作を記録します。pcはおそらくプログラムカウンタ(呼び出し元の命令のアドレス)であり、競合検出のコンテキスト情報として使用されます。

この追加により、append操作がyの要素を読み込む際に、その読み込みがRace Detectorによって適切に監視されるようになります。これにより、yの要素に対する並行アクセスによるデータ競合が正確に検出されるようになり、Race Detectorの信頼性と有効性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/runtime/slice.goや関連するC/Assemblyファイル)
  • Go言語のRace Detectorの設計に関するドキュメントや論文(もし公開されていれば)
  • Go言語のappend関数の内部動作に関する技術記事や解説
  • データ競合と並行プログラミングに関する一般的な知識
  • このコミットのChange List (CL): https://golang.org/cl/6819107 (これはコミットメッセージに記載されているリンクです)