Go 言語仕様、効果的な Go、および Go メモリ モデルを (簡単に) 確認した後でも、Go チャネルが内部でどのように機能するかについてはまだ少し不明です。
それらはどのような構造ですか?それらは、スレッドセーフなキュー/配列のように機能します。
それらの実装はアーキテクチャに依存しますか?
チャネルのソース ファイルは (go ソース コード ルートから) /src/pkg/runtime/chan.goにあります。
hchan
チャネルの中心的なデータ構造であり、送信および受信のリンクされたリスト (ゴルーチンとデータ要素へのポインターを保持) とclosed
フラグを持ちます。Lock
runtime2.go で定義され、OS に応じてミューテックス (futex) またはセマフォとして機能する埋め込み構造があります。ロックの実装は、ビルド タグに基づいて、lock_futex.go (Linux/Dragonfly/一部の BSD) または lock_sema.go (Windows/OSX/Plan9/一部の BSD) にあります。
チャネル操作はすべてこの chan.go ファイルに実装されているため、makechan、send および receive 操作のほか、select コンストラクト、close、len、および cap ビルトインを確認できます。
チャネルの内部動作に関する非常に詳細な説明については、 Dmitry Vyukov 自身による強力なGo チャネルを読む必要があります(とりわけ、Go コア開発者、ゴルーチン、スケジューラ、およびチャネル)。
次の 2 つの質問をしました。
Go のチャネルは実際には「スレッドセーフなキューのようなもの」です。より正確に言うと、Go のチャネルには次のプロパティがあります。
チャネルを作成するたびに、hchan構造体がヒープに割り当てられ、チャネルとして表される hchan メモリ位置へのポインターが返されます。これが、go-routine がそれを共有できる方法です。
上記の最初の 2 つのプロパティは、ロック付きのキューと同様に実装されます。チャネルがさまざまな go-routine に渡すことができる要素は、hchan 構造体のインデックスを持つ循環キュー (リング バッファー) として実装されます。インデックスは、バッファー内の要素の位置を説明します。
循環キュー:
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
そしてインデックス:
sendx uint // send index
recvx uint // receive index
go-routine がチャネル構造にアクセスしてその状態を変更する必要があるたびに、ロックを保持します。たとえば、要素をバッファにコピーしたり、バッファからコピーしたり、リストやインデックスを更新したりします。一部の操作はロックフリーになるように最適化されていますが、これはこの回答の範囲外です。
go チャネルのブロックとブロック解除のプロパティは、ブロックされた go ルーチンを保持する 2 つのキュー (リンクされたリスト) を使用して実現されます。
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
go-routine が完全なチャネル (バッファがいっぱい) にタスクを追加したり、空のチャネル (バッファが空) からタスクを取得したりするたびに、疑似 go-routine sudog構造体が割り当てられ、go-routineそれに応じて、sudog をノードとして送信または受信待機リストに追加します。gopark
次に、go-routine は特別な呼び出しを使用して go ランタイム スケジューラを更新します。これは、いつ実行を中止するか ( )、いつ実行できるようにするか( )を示唆しgoready
ます。これは非常に単純化された説明であり、いくつかの複雑さが隠されていることに注意してください。
@mnaが既に説明した OS 固有のロックの実装に加えて、アーキテクチャ固有の制約の最適化や違いについては知りません。