Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
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
歴史に学ぶGitOpsの姿 / Learning the state of GitOps from history
ffjlabo
1
280
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
170
GoにおけるCall Graphを用いた 大規模コードベースの影響調査
ffjlabo
1
6.2k
Other Decks in Programming
See All in Programming
안드로이드 9년차 개발자, 프론트엔드 주니어로 커리어 리셋하기
maryang
1
120
これだけで丸わかり!LangChain v1.0 アップデートまとめ
os1ma
6
1.9k
ローカルLLMを⽤いてコード補完を⾏う VSCode拡張機能を作ってみた
nearme_tech
PRO
0
110
AIエンジニアリングのご紹介 / Introduction to AI Engineering
rkaga
8
3.1k
ゲームの物理 剛体編
fadis
0
350
生成AIを利用するだけでなく、投資できる組織へ
pospome
2
370
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
190
リリース時」テストから「デイリー実行」へ!開発マネージャが取り組んだ、レガシー自動テストのモダン化戦略
goataka
0
130
Canon EOS R50 V と R5 Mark II 購入でみえてきた最近のデジイチ VR180 事情、そして VR180 静止画に活路を見出すまで
karad
0
130
AIの誤りが許されない業務システムにおいて“信頼されるAI” を目指す / building-trusted-ai-systems
yuya4
6
3.8k
re:Invent 2025 のイケてるサービスを紹介する
maroon1st
0
130
Microservices rules: What good looks like
cer
PRO
0
1.5k
Featured
See All Featured
Bash Introduction
62gerente
615
210k
Discover your Explorer Soul
emna__ayadi
2
1k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
33
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
47
33k
How GitHub (no longer) Works
holman
316
140k
Optimizing for Happiness
mojombo
379
70k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.8k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
0
120
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
310
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
85
Marketing to machines
jonoalderson
1
4.3k
What does AI have to do with Human Rights?
axbom
PRO
0
1.9k
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つの状態変数を用いて管理 • コンテキストスイッチを極力減らす工夫
ありがとうございましたʕ◔ϖ◔ʔ