Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
sync.Mutexの仕組みを理解する
Search
Yoshiki Fujikane
June 02, 2023
Programming
2
2.1k
sync.Mutexの仕組みを理解する
Go Conference 2023
https://gocon.jp/2023/sessions/A14-S/
Yoshiki Fujikane
June 02, 2023
Tweet
Share
More Decks by Yoshiki Fujikane
See All by Yoshiki Fujikane
コンパイルのたびに出力が変わる!?protocプラグインをめぐるトラブルシュートとHyrum'Lawの教訓
ffjlabo
0
74
歴史に学ぶGitOpsの姿 / Learning the state of GitOps from history
ffjlabo
1
290
Our Journey from in-House CD System to Open Source
ffjlabo
0
200
KubeCon NA 2023 Recap for Balancing AI’s Productivity Boost with Ethical Considerations in Cloud Native
ffjlabo
0
190
GoにおけるCall Graphを用いた 大規模コードベースの影響調査
ffjlabo
1
6.4k
Other Decks in Programming
See All in Programming
JPUG勉強会 OSSデータベースの内部構造を理解しよう
oga5
2
230
今、アーキテクトとして 品質保証にどう関わるか
nealle
0
200
TROCCOで実現するkintone+BigQueryによるオペレーション改善
ssxota
0
130
Claude Codeセッション現状確認 2026福岡 / fukuoka-aicoding-00-beacon
monochromegane
4
390
エラーログのマスキングの仕組みづくりに役立ったASTの話
kumoichi
0
110
コーディングルールの鮮度を保ちたい / keep-fresh-go-internal-conventions
handlename
0
150
受け入れテスト駆動開発(ATDD)×AI駆動開発 AI時代のATDDの取り組み方を考える
kztakasaki
2
520
AWS Infrastructure as Code の新機能 2025 総まとめ 〜SA 4人による怒涛のデモ祭り〜
konokenj
10
3.2k
ベクトル検索のフィルタを用いた機械学習モデルとの統合 / python-meetup-fukuoka-06-vector-attr
monochromegane
2
320
AI駆動開発の本音 〜Claude Code並列開発で見えたエンジニアの新しい役割〜
hisuzuya
4
480
CSC307 Lecture 12
javiergs
PRO
0
460
maplibre-gl-layers - 地図に移動体たくさん表示したい
kekyo
PRO
0
180
Featured
See All Featured
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
430
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
80
[SF Ruby Conf 2025] Rails X
palkan
2
810
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.1k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
1.9k
The Curious Case for Waylosing
cassininazir
0
260
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
67
37k
GraphQLとの向き合い方2022年版
quramy
50
14k
We Have a Design System, Now What?
morganepeng
55
8k
Typedesign – Prime Four
hannesfritz
42
3k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
140
Code Reviewing Like a Champion
maltzj
528
40k
Transcript
sync.Mutexの仕組み を理解する Go Conference 2023/06/02
自己紹介 Name • Yoshiki Fujikane (ふじを) Company • CyberAgent, Inc.
22新卒入社 • ABEMA バックエンドエンジニア @ffjlabo @ffjlabo
• 排他制御を実現するプリミティブ • 共有リソースに対する競合を起こさず 並行処理を実装できる sync.Mutexとは
• 排他制御を実現するプリミティブ • 共有リソースに対する競合を起こさず 並行処理を実装できる sync.Mutexとは 🤔どうやって実現してるのだろう
sync.Mutex の仕組みが知りたい 背景
本発表の目的 • sync.Mutexの内部実装について把握する • sync.Mutexの仕組みを原理から理解する
ʕ◔ϖ◔ʔ> Let’s≡Go $ git checkout 7f1467ff4ddd882acb318c0ffe24fd3702ce75cc
• sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 • まとめ
アジェンダ
• sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 • まとめ
アジェンダ
「リソースへアクセスできるのは 1 goroutineのみ」 sync.Mutexが実現する排他制御
リソースへのアクセスが複数来ても、1goroutineのみアクセスを許可 sync.Mutexが実現する排他制御 resource G G G Mutex ✅ ❌ ❌
1goroutineのみにアクセスを制限するには? sync.Mutexが実現する排他制御
A. 「リソースへのアクセス状況」を記録しておく sync.Mutexが実現する排他制御
リソースへアクセスが来た段階でその状態を変数として記録しておく 「リソースへのアクセス状況」を記録 resource G Mutex Locked = false
リソースへアクセスが来た段階でその状態を変数として記録しておく 「リソースへのアクセス状況」を記録 resource G Mutex Locked = true ✅
別goroutineがアクセスしてきたとき、その変数を元に制御可能! 「リソースへのアクセス状況」を記録 resource G G G Mutex ✅ ❌ ❌
Locked = true
sync.Mutexが実現する排他制御: まとめ • sync.Mutexの排他制御とは 「リソースへのアクセスを1 gorutineのみに制限する」 こと • リソースへのアクセス状況を保持しておくことで実現
• sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 • まとめ
アジェンダ
2つの状態変数を保持 • state ◦ Mutexの現在の状態を表す • sema ◦ goroutineの待機、解放を管理するためのセマフォ sync.Mutexの内部構造
src/sync/mutex.go
state Mutexのロック取得を待機している goroutine(waiter)の数 Mutexの状態 32bit 29bit 3bit src/sync/mutex.go
state: Mutexの状態 • Mutexの状態は3種類 • 各bitがそれぞれの状態に対応 S W L src/sync/mutex.go
state: Mutexの状態 • mutexLocked ◦ Mutexがロックされた状態 S W L src/sync/mutex.go
mutexLocked: イメージ 「ロックを獲得したgoroutineが存在する」状態 G G Mutex ✅ ❌
state: Mutexの状態 • mutexWoken ◦ 該当Mutexをロックしようとgoroutineが起動した状態 S W L src/sync/mutex.go
mutexWoken: イメージ 「ロックしようとするgoroutineが存在する」状態 G G Mutex
state: Mutexの状態 • mutexStarving ◦ 該当Mutexを長期間ロックできずにいるgoroutineが存在する状態 ◦ 飢餓状態(Starving mode)と呼ばれる S
W L src/sync/mutex.go
mutexStarving: イメージ 「一定時間以上ロックを待たされているgoroutineが存在する」状態 G G G Mutex ✅ ❌ G
もうずっと待ってるんだけどな …
• バイナリセマフォ ◦ sema == 0 => リソースが占有された状態 ◦ sema
== 1 => リソースが解放された状態 sema runtimeレベルのロック状況を管理するために利用される(詳細は後ほど) src/sync/mutex.go
sync.Mutexの構造: まとめ • sync.Mutexには2つの状態変数が存在 ◦ state: Mutexの様々な状況を表現 ◦ sema: バイナリセマフォでruntimeレベルのロック状況を表現
sync.Mutexの構造: まとめ • sync.Mutexには2つの状態変数が存在 ◦ state: Mutexの様々な状況を表現 ◦ sema: バイナリセマフォでruntimeレベルのロック状況を表現
🤔 なぜ2つも状態変数が存在する?
アジェンダ • sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 •
まとめ
Lock()の内部実装 • CompareAndSwapを利用して、stateのmutexLockedフラグを更新 • 更新できればその時点でロックされる src/sync/mutex.go
📝 Compare And Swap • Mutexをアクセス制御する リソースにつき1つ作成 • それを複数のgoroutineが 参照する
resource G Mutex ✅ ❌ ❌ G G 変数の値の更新が競合しないようにatomicな操作で更新 https://pkg.go.dev/sync/atomic#pkg-overview
Lock()の内部実装 • stateの値を更新できなかった場合はlockSlowへ src/sync/mutex.go
lockSlow(): 全体像 大まかに以下の3ステップに分かれている • スピンループ • stateの再更新 • semaを用いたロック取得
lockSlow(): スピンループ • 直前のstateがロック状態だった場合にスピンループ src/sync/mutex.go
• 「何もしない」という空振り処理を実行する 📝 スピン resource G G Mutex ✅ G
可能な限りロック状態が解除されるのを待つ
lockSlow(): stateの再更新 • 再度stateをロック状態に更新しようと試みる src/sync/mutex.go
lockSlow(): stateの再更新 • ロック状態でも飢餓状態でもない場合はbreak • 以降はロック状態に更新できても、直前の状態がロック状態の可能性 => 別goroutineがロック中だが更新できた可能性 src/sync/mutex.go
lockSlow(): stateの再更新 • stateのみでは1goroutineのみがロック中か保証できない src/sync/mutex.go
lockSlow(): semaを用いたロック取得 • runtime_SemacquireMutexへ • semaを用いてruntimeレベルで1goroutineのみがロックした状態を 確保しにいく src/sync/mutex.go
ここまでのまとめ • Lock時はまずはじめにstateを用いて、ロック状態に更新できるか 確認する • stateをロック状態に更新できたとしても、1goroutineのみが アクセスしていることを保証できない • stateを用いてもロック状態を保証できない場合は semaを用いてロック状態を確保しようと試みる
runtime_SemacquireMutex() • //go:linkname によってruntime側のprivateメソッドをリンクさせる • 実体はsync_runtime_SemacquireMutex => semacquire1 src/sync/runtime.go src/runtime/sema.go
semacquire1() • easy caseとharder caseの2パターンの処理が存在 src/runtime/sema.go
semacquire1(): easy case • sema(addr) をロック状態に更新しようと試みる src/runtime/sema.go src/runtime/sema.go
semacquire1(): harder case • waiter countをインクリメント • cansemacquireを複数回実行 • waiterとしてenqueue???
• sleep??? runtimeレベルでgoroutineがどのように動作するか着目する必要あり src/runtime/sema.go
goroutine実行過程の概要 X := 3 pow(x) P G G M G
G (goroutine): goroutine本体 P (Prosessor): 論理プロセッサ M (Machine): OSスレッド Inspired by https://speakerdeck.com/sakiengineer/sukeziyurakaraxue-bugorantaimu-code-reading-of-runtime-pkg
goroutine実行過程の概要 X := 3 pow(x) P G G M G
実行待ちのgoroutineを貯めるqueue 実行中のgoroutine
goroutine実行過程の概要 fmt.Println(“Hello”) P G G M G 実行待ちのgoroutineがqueueから取り出され、逐次実行される
G semacquire1(): harder case sudog を用意 P G G M
G G src/runtime/sema.go
• 待ち行列を表現するための構造体 • チャネルの内部実装にも使われる sudog G prev addr next addr
src/runtime/runtime2.go
• semtableからsemaのアドレスを元に、rootノードを取得 semacquire1(): harder case src/runtime/sema.go
semtable Mutex Mutex G G G G G 各Mutexをロックしようと待機している goroutineの待ち行列
「各Mutexのsemaをロック状態に更新しようと待機する goroutineの待ち行列」の集合 G Mutex Mutex G G G sematable (semaRootの配列) src/runtime/sema.go
semacquire1(): harder case P G G M G G Mutex
Mutex G G G Mutexのsemaをロック状態に更新しよ うと待機するgoroutineの待ち行列の先 頭ノードを取得!! G src/runtime/sema.go
semacquire1(): harder case • semaをロック状態に更新しようと試みる src/runtime/sema.go
semacquire1(): harder case • この段階でロック状態に更新できない場合は、 現状ロックを取得できない状態と判断 => ロック更新を一時停止し、goroutineをsleepさせるフェーズへ src/runtime/sema.go
semacquire1(): goroutineの一時停止 P G G M G G G G
Mutex 現在実行中のgoroutineを待ち行列に追加 src/runtime/sema.go
• goparkunlockを呼び出して、 現在のgoroutineを一時停止 & 別goroutineに実行を譲る semacquire1(): goroutineの一時停止 src/runtime/sema.go
goparkunlockの挙動 P G G M goroutineを一時停止状態にする G Sleeping… src/runtime/sema.go
goparkunlockの挙動 P G G M G Sleeping… 実行待ちのgoroutineを取り出して、起動させる G src/runtime/sema.go
Lockの内部実装 まとめ • まずはじめにstateを用いて、ロック状態に更新できるか確認する • stateを用いてもロック状態を保証できない場合は semaを用いてロック状態を確保しようと試みる • semaによるロック取得も無理だった場合、次にスケジューリング されるまでgoroutineは一時停止する
Lockの内部実装 まとめ • まずはじめにstateを用いて、ロック状態に更新できるか確認する • stateを用いてもロック状態を保証できない場合は semaを用いてロック状態を確保しようと試みる • semaによるロック取得も無理だった場合、次にスケジューリング されるまでgoroutineは一時停止する
🤔 semaのみ使えばいいのでは?
• sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 • まとめ
アジェンダ
なぜわざわざstateとsemaの2つの状態変数を使って 2段階の処理を行っているの? 実装背景
A. コンテキストスイッチを最小限に抑えて効率化するため 実装背景
src/runtime/sema.go • Semaphores in Plan 9. • https://swtch.com/semaphore.pdf src/runtime/sema.go
Semaphores in Plan 9 • Plan 9におけるセマフォ機構の実装方針などが書かれている ◦ Plan 9:
かつてRuss Coxらが開発していた教育用OS
Semaphores in Plan 9 • • u: ユーザ空間上のセマフォ • k:
カーネル空間上のセマフォ 効率を高めるために、競合がない場合は完全にユーザー空間で実行でき、競合を処理するためにカー ネルにのみフォールバックするセマフォ実装があると便利です
Semaphores in Plan 9 • 基本的にユーザ空間上で処理を行う • 競合が起きた時にだけカーネル空間上で処理を行う ユーザ空間 <->
カーネル空間のコンテキストスイッチを軽減する
Semaphores in Plan 9 Goで考えると… • 基本的にgoroutine空間上で処理を行う • 競合が起きた時にだけruntime空間上で処理を行う goroutine空間
<-> runtime空間のコンテキストスイッチを軽減する
アジェンダ • sync.Mutexが実現する排他制御 • sync.Mutexの構造 • Lock()の内部実装 • 実装背景 •
まとめ
まとめ sync.Mutexは • 「リソースへのアクセス状況」を管理する役割を持つ • stateとsemaの2つの状態変数を用いて管理 • コンテキストスイッチを極力減らす工夫
ありがとうございましたʕ◔ϖ◔ʔ