CS โ race condition
๐ 05/13 โ Race Condition์ ๋ํด์ ์ด์ผ๊ธฐ ํด์ฃผ์ธ์
๋ชจ์๋ฉด์ ์ฃผ์ : โRace Condition์ ๋ํด์ ์ด์ผ๊ธฐ ํด์ฃผ์ธ์โ ์ ์(๊ณต์ ์์ + ๋์ ์ ๊ทผ + ๋น๊ฒฐ์ ์ฑ) โ Critical Section โ ๋๊ธฐํ ๊ฐ์ฒด ์นดํ๋ก๊ทธ(MutexยทSemaphoreยทCritical SectionยทSRWLockยทEventยทCondition Variable) โ Lock-free / atomic / CAS โ Memory OrderingยทMemory Barrier(acquire/release) โ ABA ๋ฌธ์ โ DeadlockยทLivelockยทStarvation โ Priority Inversion โ Windows/POSIX API ๋น๊ต โ ๋น์ฉ ์คํํธ๋ผ โ ์ธ๋ฆฌ์ผ(GameThread/RenderThread ๋ถ๋ฆฌยทTaskGraph)๊น์ง
ํ์ต ์์ญ โ ํ๋ก์ธ์ค vs ์ค๋ ๋(19)ยท์ปจํ ์คํธ ์ค์์นญ(21)ยทIPC(22)์์ ํ์๋ ๋์์ฑ ํ๊ท
ํ๋ก์ธ์ค vs ์ค๋ ๋(19)์์ ๊ฐ์ ํ๋ก์ธ์ค ์์ ์ค๋ ๋๋ ์ฝ๋ยท๋ฐ์ดํฐยทํ์ ๊ณต์ ํ๋ค๋ ์ ์ ๋ดค๊ณ , ์ปจํ ์คํธ ์ค์์นญ(21)์์ ์ค๋ ๋๊ฐ ์์์ ์์ ์ ๊ฐ์ ๋ก ๊ต์ฒด๋ ์ ์๋ค๋ ์ ์ ๋ดค์ต๋๋ค. IPC(22)์์ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ฐ์ฅ ๋น ๋ฅด์ง๋ง ๋๊ธฐํ๋ฅผ ์ง์ ํด์ผ ํ๋ ํธ๋ ์ด๋์คํ๋ฅผ ์ง์์ต๋๋ค. 23๋ฒ์ ๊ทธ ์ธ ์ฃผ์ ๊ฐ ๋ชจ๋ ๊ฐ๋ฆฌํค๋ ๊ฒฐ๋ก , ๊ณต์ ์์ + ๋์ ์ ๊ทผ + ๋น๊ฒฐ์ ์ ์ค์ผ์ค๋ง์ด ๋ง๋๋ ์ง์ ์์ ์ผ์ด๋๋ Race Condition์ด ๋ณธ ์ฃผ์ ์ ๋๋ค.
1
2
3
4
5
6
7
8
9
01๋ฒ ๋ฉ๋ชจ๋ฆฌ 4์์ญ (Code/Data/Heap/Stack) โ ๊ณต์ ์์ญ์ ์์น
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
19๋ฒ ํ๋ก์ธ์ค vs ์ค๋ ๋ โ ์ค๋ ๋๋ ํยท์ ์ญ ๊ณต์
20๋ฒ Stack Overflow (์ค๋ ๋๋ณ ๋
๋ฆฝ ์คํ) โ ๊ณต์ vs ๋ถ๋ฆฌ ์์ญ ๊ตฌ๋ถ
21๋ฒ Context Switching (โ
) โ ์์ ์์ ์ค๋ ๋ ๊ต์ฒด
22๋ฒ IPC (โ
) โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ ๋๊ธฐํ ์ง์ ์ฒ๋ฆฌ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
23๋ฒ Race Condition (โ
) โ ๋ณธ ์ฃผ์ โ ๋์ ์ ๊ทผ์ ๊ฒฐ๊ณผ ๋น๊ฒฐ์ ์ฑ
์ดํ ๋ฝ-ํ๋ฆฌ ์๋ฃ๊ตฌ์กฐ / ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ ์ฌํ / ํธ๋์ญ์
๋ฉ๋ชจ๋ฆฌ
Race Condition์ OS ์ฑ ์ ๋์์ฑ ์ฑํฐ์์ ๊ฐ์ฅ ์์ฃผ ๋ฑ์ฅํ๋ ๋จ์ด์ง๋ง, ์ค์ ๋ก๋ CPU ๋ง์ดํฌ๋ก์ํคํ ์ฒ(๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธยท์บ์ ์ผ๊ด์ฑ)ยท์ธ์ด ํ์ค(C++ memory_order)ยทOS API(MutexยทSRWLock)ยทํ๋์จ์ด ๋ช ๋ น(LOCK CMPXCHG) ์ด ๋ชจ๋ ๋ง๋๋ ์ง์ ์ ๋๋ค. ๊ทธ๋์ ๋ฉด์ ์์ โRace Condition์ด ๋ญก๋๊นโ๋ฅผ ๋ฌผ์ผ๋ฉด, ์ ์ยท์์ยทํด๊ฒฐ์ฑ ยทํด๊ฒฐ์ฑ ์ ๋น์ฉยทํด๊ฒฐ์ฑ ์ฌ์ด์ ํธ๋ ์ด๋์คํ๊น์ง 4~5๋จ๊ณ๋ก ํ ์ ์์ด์ผ ๊น์ด๊ฐ ๋๋ฌ๋ฉ๋๋ค.
Windows๋ ํนํ CRITICAL_SECTIONยทSRWLockยทMutexยทSemaphoreยทEventยทInterlockedExchangeยทstd::atomic๊น์ง ๋จ๊ณ๋ณ๋ก ๋น์ฉ์ด ๋ค๋ฅธ ๋๊ธฐํ ๋๊ตฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ์ โrace๋ฅผ ๋ง๋๋คโ๋ ์์
์ด ์ ns(atomic CAS) ๋ถํฐ ์ ฮผs(์ปค๋ Mutex) ๊น์ง 1000๋ฐฐ ๋น์ฉ ์ฐจ์ด๋ฅผ ๋ง๋ญ๋๋ค. ๊ทธ๋์ Race Condition์ ๋ง๋ ๊ฒ ์๋๋ผ โ์ด๋ป๊ฒ ์ ์ ๋น์ฉ์ผ๋ก ๋ง๋๋โ ๊ฐ ์ง์ง ์์ง๋์ด๋ง ๋ฌธ์ ์
๋๋ค.
๋ชจ์๋ฉด์ ๋ต๋ณ
Race Condition์ ๋ ์ด์์ ์คํ ๋จ์(์ค๋ ๋ยทํ๋ก์ธ์ค)๊ฐ ๊ณต์ ์์์ ๋์์ ์ ๊ทผํ๋ฉด์, ์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ํ์์ ๋๋ค. ํต์ฌ์ ์ธ ๊ฐ์ง ์กฐ๊ฑด์ด ๋ชจ๋ ๋ง์กฑ๋์ด์ผ ํ๋ค๋ ์ ์ ๋๋ค โ โ ๊ณต์ ์์์ด ์กด์ฌํ๊ณ , โก ์ ์ด๋ ๋ ๊ฐ ์ด์์ ์คํ ๋จ์๊ฐ ๊ทธ ์์์ ์ ๊ทผํ๊ณ , โข ๊ทธ์ค ํ๋ ์ด์์ด ์ฐ๊ธฐ(write)๋ฅผ ์ํํ๋ฉฐ, โฃ ์ ๊ทผ ์์๋ฅผ OS๊ฐ ๋ณด์ฅํ์ง ์์ต๋๋ค. ์ด ๋ค ์กฐ๊ฑด์ด ๋ชจ๋ ๋ง์กฑ๋์ด์ผ race๊ฐ ๋ฐ์ํฉ๋๋ค. ์ฝ๊ธฐ๋ง ํ๋ ๋์ ์ ๊ทผ์ race๊ฐ ์๋๊ณ , ๋จ์ผ ์ค๋ ๋์์์ ์ ๊ทผ๋ race๊ฐ ์๋๋๋ค.
๊ฐ์ฅ ํํ ์๊ฐ counter ์ฆ๊ฐ์
๋๋ค. count++๋ผ๋ ํ ์ค์ด CPU ๋ช
๋ น์ผ๋ก๋ load โ increment โ store ์ธ ๋จ๊ณ๋ก ๋ถํด๋๊ณ , ๊ทธ ์ฌ์ด ์ด๋์๋ ์ปจํ
์คํธ ์ค์์นญ(21)์ด ์ผ์ด๋ ์ ์์ต๋๋ค. ์ค๋ ๋ A๊ฐ loadํด์ 100์ ์ฝ๊ณ increment๊น์ง ํ๋๋ฐ(101), ๊ทธ ์ฌ์ด ์ค๋ ๋ B๊ฐ ๋ค์ด์์ ๊ฐ์ 100์ loadํด์ incrementยทstore๊น์ง ๋๋ด๊ณ (101), A๊ฐ ๋ค์ ๊นจ์ด๋์ ์๊ธฐ ๊ฒฐ๊ณผ 101์ storeํ๋ฉด, ๋ ๋ฒ ์ฆ๊ฐํ๋๋ฐ๋ ์ต์ข
๊ฐ์ด 101์
๋๋ค. ๊ฒฐ๊ณผ๊ฐ 102๊ฐ ๋์ด์ผ ํ์ง๋ง 101์ด ๋๊ณ , ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋์ฌ์ง ๋ฏธ๋ฆฌ ์ ์ ์์ต๋๋ค โ ๋น๊ฒฐ์ ์ฑ(non-determinism) ์ด race์ ๋ณธ์ง์
๋๋ค.
Race๋ฅผ ๋ง๋ ํต์ฌ ๊ฐ๋ ์ด Critical Section(์๊ณ ์์ญ)์ ๋๋ค. โํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐโ์ ๊ฐ๋ฆฌํค๋ ์ถ์์ ๊ฐ๋ ์ด๊ณ , ๊ทธ ๊ตฌ๊ฐ์ ๋ณดํธํ๋ ๋๊ตฌ๊ฐ ๋๊ธฐํ ๊ฐ์ฒด(synchronization primitive) ์ ๋๋ค. ๋๊ธฐํ ๊ฐ์ฒด๋ ๋น์ฉ์ ๋ฐ๋ผ ์ธ ์ธต์ผ๋ก ๋๋ฉ๋๋ค.
- ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ (user-mode first) โ
Critical Section(Windows)ยทSRWLockยทstd::mutex(MSVC). ๊ฒฝํฉ์ด ์์ผ๋ฉด ์ฌ์ฉ์ ๋ชจ๋์์ atomic ๋ช ๋ น์ผ๋ก ๋๋๊ณ , ๊ฒฝํฉ ์์๋ง ์ปค๋๋ก ์ง์ ํฉ๋๋ค. ๊ฒฝํฉ ์์ ๋ ์์ญ ns, ๊ฒฝํฉ ์ 1~3 ฮผs. - ํญ์ ์ปค๋ ๊ฐ์ฒด โ
Mutex(Windows์ ์ปค๋ mutex)ยทSemaphoreยทEvent. ๋งค๋ฒ ์์คํ ์ฝ โ ์ปจํ ์คํธ ์ค์์นญ(21)์ ๋ชจ๋ ์ค์์น. 1~3 ฮผs. ๋์ ํ๋ก์ธ์ค ๊ฐ ๊ณต์ (์ด๋ฆ ๋ถ์ฌ)๊ฐ ๊ฐ๋ฅํฉ๋๋ค. - Lock-free / atomic โ
std::atomicยทInterlockedExchangeยทCAS(Compare-And-Swap). CPU์ LOCK ์ ๋์ฌ ๋ช ๋ น(LOCK CMPXCHG)์ผ๋ก ์ปจํ ์คํธ ์ค์์นญ ์์ด ๋๊ธฐํ. ์ ns. ๋จ ์๋ฃ๊ตฌ์กฐ ์ค๊ณ๊ฐ ๋งค์ฐ ์ด๋ ต์ต๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ(memory model)์ด ๋ ํ ์ธต์ ๋ฌธ์ ๋ฅผ ๋ง๋ญ๋๋ค. ํ๋ CPU๋ ๋ช
๋ น ์ฌ๋ฐฐ์น(reordering) ์ ์บ์ ์ผ๊ด์ฑ(cache coherence) ๋๋ฌธ์, ํ ์ค๋ ๋๊ฐ ๋ณธ ์ฐ๊ธฐ ์์๊ฐ ๋ค๋ฅธ ์ค๋ ๋์์๋ ๋ค๋ฅด๊ฒ ๋ณด์ผ ์ ์์ต๋๋ค. ๊ทธ๋์ ๋จ์ํ ๋ฝ๋ง ๊ฑธ์ด์ ๋ถ์กฑํ๊ณ , std::atomic์ memory_order_acquire/memory_order_release ๊ฐ์ memory barrier๋ก ๋ช
๋ น ์ฌ๋ฐฐ์น๋ฅผ ๋ง์์ผ ํฉ๋๋ค. acquire๋ โ์ด ์์ ์ดํ์ ์ฝ๊ธฐยท์ฐ๊ธฐ๊ฐ ์ด ์์ ๋ณด๋ค ์์ผ๋ก ์ฌ๋ฐฐ์น๋์ง ์๊ฒโ, release๋ โ์ด ์์ ์ด์ ์ ์ฝ๊ธฐยท์ฐ๊ธฐ๊ฐ ์ด ์์ ๋ณด๋ค ๋ค๋ก ์ฌ๋ฐฐ์น๋์ง ์๊ฒโ ๋ง๋ ํ์ค(fence)์
๋๋ค. ๋ฝ ์์ด ๋ ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ฃผ๊ณ ๋ฐ์ผ๋ ค๋ฉด ์ด ๋์ด ํ ์ง์ด์ด์ผ ํฉ๋๋ค.
Race๋ฅผ ๋ง๋๋ค๊ณ ํด์ ๋ชจ๋ ๋ฌธ์ ๊ฐ ํ๋ฆฌ๋ ๊ฑด ์๋๋๋ค. ๋ฝ์ ์๋ชป ์ฐ๋ฉด ์๋ก์ด ๋ณ๋ฆฌ๊ฐ ์๊น๋๋ค โ Deadlock(๋ ์ค๋ ๋๊ฐ ์๋ก์ ๋ฝ์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ์์ํ ๋ฉ์ถค), Livelock(์๋ก ์๋ณดํ๋ค๊ฐ ์งํ์ด ์ ๋จ), Starvation(ํน์ ์ค๋ ๋๊ฐ ์์ํ ๋ฝ์ ๋ชป ์ก์), Priority Inversion(๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์ก์ ๋ฝ์ ๋์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๊ธฐ๋ค๋ฆฌ๋๋ฐ, ๊ทธ ์ฌ์ด์ ์ค๊ฐ ์ฐ์ ์์ ์ค๋ ๋๊ฐ CPU๋ฅผ ์ฐจ์งํด ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์งํ ๋ชป ํจ โ ํ์ฑ ํ์ฌ์ Pathfinder์ ์ ๋ช ํ ๋ฒ๊ทธ). Lock-free ์๋ฃ๊ตฌ์กฐ๋ ์์ฒด ํจ์ ์ด ์์ต๋๋ค โ ABA ๋ฌธ์ (๊ฐ์ด AโBโA๋ก ๋ฐ๋์์ง๋ง CAS๋ A๋ฅผ ๋ณด๊ณ ๋ณ๊ฒฝ์ด ์๋ค๊ณ ์ฐฉ๊ฐ).
์ธ๋ฆฌ์ผ ์์ง์ ์ด ๋ฌธ์ ๋ฅผ โ๋ฝ์ ์ค์ด๋โ ๋ฐฉํฅ์ด ์๋๋ผ โ๊ณต์ ๋ฅผ ์ค์ด๋โ ๋ฐฉํฅ์ผ๋ก ํ๋๋ค. ์ปจํ
์คํธ ์ค์์นญ(21)์์ ๋ณธ ๊ฒ์ฒ๋ผ GameThreadยทRenderThreadยทRHIThread๋ฅผ ๋ถ๋ฆฌํ๊ณ , ๊ทธ ์ฌ์ด๋ฅผ ๋ช
๋ น ํ(command queue) ์ TaskGraph๋ก ์ฐ๊ฒฐํด์ ํ ์์์ ๋ ์ค๋ ๋๊ฐ ๋์์ ๋ง์ง๋ ์ํฉ ์์ฒด๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค. UObject๋ GameThread๋ง ๋ง์ง๊ณ , RenderThread๋ GameThread๊ฐ ํ ํ๋ ์ ๋ถ๋์ ์ ๋ฆฌํด์ ๋๊ธด ํ๋ก์ ๋ฐ์ดํฐ๋ง ๋ค๋ฃน๋๋ค. ๊ทธ๋์ ์ธ๋ฆฌ์ผ ๊ฒ์ํ๋ ์ด ์ฝ๋๋ ๋ฝ์ด ๊ฑฐ์ ์์ต๋๋ค โ race๊ฐ ๋ฐ์ํ ์ฌ์ง๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฐจ๋จํ ๊ฒ์
๋๋ค. ๊ฒฐ๊ตญ ๋์์ฑ ์์ง๋์ด๋ง์ ๊ฐ์ฅ ์ข์ ๋ต์ โ๋ฝ์ ์ ์ฐ๋ ๊ฒโ์ด ์๋๋ผ โ๊ณต์ ๋ฅผ ์ ๋ง๋๋ ๊ฒโ์
๋๋ค.
ํต์ฌ ๊ฐ๋
| ๋ถ๋ฅ | ํค์๋ | ํ ์ค ์ ์ |
|---|---|---|
| ์ ์ | Race Condition | ๋ ์ด์์ ์คํ ๋จ์๊ฐ ๊ณต์ ์์์ ๋์ ์ ๊ทผํ ๋, ์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋น๊ฒฐ์ ์ ํ์ |
| ย | ๋ฐ์ 4์กฐ๊ฑด | โ ๊ณต์ ์์ โก ๋ ์ด์ ๋์ ์ ๊ทผ โข ์ ์ด๋ ํ๋๋ ์ฐ๊ธฐ โฃ ์์ ๋ณด์ฅ ์์ |
| ย | Critical Section (์๊ณ ์์ญ) | ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐ์ ์ถ์ ๊ฐ๋ |
| ย | Atomicity (์์์ฑ) | ํ ์ฐ์ฐ์ด ์ธ๋ถ์์ ๋ณด๊ธฐ์ โํ ๋ฒ์ ๋๋๊ฑฐ๋ ์์ ์ ์ผ์ด๋โ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ ์ฑ์ง |
| ย | ๋น๊ฒฐ์ ์ฑ (Non-determinism) | ๊ฐ์ ์ ๋ ฅ์ ๋ํด ์คํ๋ง๋ค ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์ ์๋ ์ฑ์ง. race์ ๋ณธ์ง |
| ย | Data Race | C++ ํ์ค ์ฉ์ด โ ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ์ ๋๊ธฐํ ์์ด ์ ๊ทผ, ์ ์ด๋ ํ๋๊ฐ ์ฐ๊ธฐ ์ UB |
| ย | Race Condition vs Data Race | Data race๋ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์ฐจ์ / Race condition์ ์๋ฏธยท๊ฒฐ๊ณผ ์ฐจ์. ๋ชจ๋ data race๋ race condition์ด์ง๋ง ์ญ์ ์๋ |
| ๋๊ธฐํ ๊ฐ์ฒด | Mutex (์ํธ ๋ฐฐ์ , MUTual EXclusion) | ํ ์ค๋ ๋๋ง ๋ฝ์ ์ก์ ์ ์๋ ๋ฐฐํ์ ๋ฝ. Windows ์ปค๋ ๊ฐ์ฒด๋ ํ๋ก์ธ์ค ๊ฐ ๊ณต์ ๊ฐ๋ฅ |
| ย | Semaphore | ์นด์ดํฐ ๊ธฐ๋ฐ ๋ฝ โ N๊ฐ ๋์ ์ ๊ทผ ํ์ฉ. counting / binary semaphore |
| ย | Critical Section (Windows API) | ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ mutex. ๊ฒฝํฉ ์์๋ง ์ปค๋ ์ง์ . ๊ฐ์ ํ๋ก์ธ์ค ๋ด ํ์ |
| ย | SRWLock (Slim Reader/Writer Lock) | ์ฌ์ฉ์ ๋ชจ๋ R/W lock. Vista+. ์ฝ๊ธฐ๋ ๋ค์คยท์ฐ๊ธฐ๋ ๋ฐฐํ. std::shared_mutex์ ๋ด๋ถ |
| ย | Event (Windows) | ์๊ทธ๋/๋ ผ์๊ทธ๋ ์ํ๋ฅผ ๊ฐ์ง๋ ๊ฐ์ฒด. ๋ค๋ฅธ ์ค๋ ๋์ โ์ด๋ค ์ผ์ด ์ผ์ด๋ฌ๋คโ ์๋ฆผ |
| ย | Condition Variable | mutex์ ์ง์ง์ด ์กฐ๊ฑด ๋ง์กฑ ๋๊ธฐ โ wait/notify_one/notify_all |
| ย | Spin Lock | ๋ฝ ์กํ ๋๊น์ง busy-wait. ์งง์ critical section + ๋ฉํฐ์ฝ์ด ํ๊ฒฝ์ ์ ํฉ. ๋จ์ผ ์ฝ์ด์์ ์ํ |
| ย | Recursive Mutex | ๊ฐ์ ์ค๋ ๋๊ฐ ์ฌ๋ฌ ๋ฒ ์ฌ์ ๊ธ ๊ฐ๋ฅ. std::recursive_mutex. ์ค๊ณ ๊ฒฐํจ ์ ํธ |
| ย | lock_guard / scoped_lock / unique_lock | C++ RAII ๋ํผ. scoped_lock์ ์ฌ๋ฌ mutex deadlock-free ์ ๊ธ |
| Lock-free | Lock-free | ์ด๋ค ์ค๋ ๋๋ผ๋ finite step ์์ ์งํ ๋ณด์ฅ. CAS ๊ฐ์ atomic primitive ๊ธฐ๋ฐ |
| ย | Wait-free | ๋ชจ๋ ์ค๋ ๋๊ฐ finite step ์์ ์งํ ๋ณด์ฅ. Lock-free๋ณด๋ค ๊ฐํ ๋ณด์ฅ |
| ย | CAS (Compare-And-Swap) | โ๊ฐ์ด expected๋ฉด desired๋ก ๋ฐ๊พธ๊ณ ์ฑ๊ณต, ์๋๋ฉด ์คํจโ ๋จ์ผ ๋ช
๋ น. x86 LOCK CMPXCHG |
| ย | atomic | C++11 std::atomic<T>. CPU์ LOCK ์ ๋์ฌ ๋ช
๋ น์ผ๋ก read-modify-write๋ฅผ ๋จ์ผ ๋ช
๋ น์ผ๋ก |
| ย | InterlockedExchange / InterlockedIncrement | Windows์ atomic ํจ์๊ตฐ. std::atomic์ Windows ๊ตฌํ ๊ธฐ๋ฐ |
| ย | fetch_add / exchange / compare_exchange_weak/strong | std::atomic์ ๋ฉค๋ฒ ํจ์. CAS๋ compare_exchange |
| ย | weak vs strong CAS | weak๋ spurious failure ํ์ฉ(๋ฃจํ ๊ฐ์ ), strong์ ์ง์ง ๋น๊ต ๊ฒฐ๊ณผ๋ง ๋ฐํ |
| ย | ABA ๋ฌธ์ | ๊ฐ์ด AโBโA๋ก ๋ฐ๋์์ง๋ง CAS๋ A๋ฅผ ๋ณด๊ณ ๋ณ๊ฒฝ ์๋ค๊ณ ์ฐฉ๊ฐ. ํฌ์ธํฐ lock-free ์๋ฃ๊ตฌ์กฐ์ ํจ์ |
| ย | Tagged Pointer / Hazard Pointer / Epoch-based reclamation | ABA ํํผ ๊ธฐ๋ฒ โ ๋ฒ์ ์นด์ดํฐ / ์ํ ํฌ์ธํฐ ๋ฑ๋ก / ์ํญ ๊ธฐ๋ฐ ํ์ |
| Memory Model | Memory Model (๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ) | CPUยท์ธ์ด๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์์ยท๊ฐ์์ฑ์ ๋ณด์ฅํ๋ ๊ท์น |
| ย | Sequential Consistency (SC, ์์ฐจ ์ผ๊ด์ฑ) | ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์์๋ก ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ์ฐ์ฐ์ ๋ณธ๋ค๋ ๊ฐ์ฅ ๊ฐํ ๋ชจ๋ธ |
| ย | Memory Reordering (๋ฉ๋ชจ๋ฆฌ ์ฌ๋ฐฐ์น) | ์ปดํ์ผ๋ฌยทCPU๊ฐ ์ฑ๋ฅ์ ์ํด ๋ช ๋ น ์์๋ฅผ ๋ฐ๊พธ๋ ๊ฒ. ๋จ์ผ ์ค๋ ๋ ์๋ฏธ๋ ๋ณด์กดํ์ง๋ง ๋ฉํฐ ์ค๋ ๋์์ race ์์ธ |
| ย | Memory Barrier / Fence | ์ฌ๋ฐฐ์น๋ฅผ ๋ง๋ ํ์ค ๋ช ๋ น. acquire / release / full barrier |
| ย | memory_order_relaxed | ์์ ๋ณด์ฅ ์์. counter์ฒ๋ผ ์์ ๋ฌด๊ดํ ๋๋ง |
| ย | memory_order_acquire | ์ด ์์ ์ดํ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ด ์์ ๋ณด๋ค ์์ผ๋ก ์ฌ๋ฐฐ์น๋์ง ์์. load์ ๋ถ์ |
| ย | memory_order_release | ์ด ์์ ์ด์ ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ด ์ด ์์ ๋ณด๋ค ๋ค๋ก ์ฌ๋ฐฐ์น๋์ง ์์. store์ ๋ถ์ |
| ย | memory_order_seq_cst | ๊ฐ์ฅ ๊ฐํ ๋ชจ๋ธ. atomic์ ๊ธฐ๋ณธ๊ฐ. ๋น์ฉ ํผ |
| ย | acquire-release ํ์ด | release store โ acquire load๋ก ๋ ์ค๋ ๋ ๊ฐ happens-before ๊ด๊ณ ์ฑ๋ฆฝ |
| ย | happens-before | A์ ๊ฒฐ๊ณผ๊ฐ B์์ ๋ณด์ด๋๋ก ๋ณด์ฅ๋๋ ์์ ๊ด๊ณ |
| ย | x86 ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ (TSO, Total Store Order) | ๋น๊ต์ ๊ฐํ ๋ชจ๋ธ. storeโload๋ง ์ฌ๋ฐฐ์น ํ์ฉ. ๊ทธ๋์ x86์์ memory barrier ๋น์ฉ์ด ๋น๊ต์ ์์ |
| ย | ARM/RISC-V (Weak Memory Model) | ๊ฑฐ์ ๋ชจ๋ ์ฌ๋ฐฐ์น ํ์ฉ. ๋ช ์์ barrier ํ์ |
| ๋ณ๋ฆฌ | Deadlock (๊ต์ฐฉ ์ํ) | ๋ ์ค๋ ๋๊ฐ ์๋ก์ ๋ฝ์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ์์ํ ๋ฉ์ถค. 4์กฐ๊ฑด: ์ํธ๋ฐฐ์ ยท์ ์ ์ ๋๊ธฐยท๋น์ ์ ยท์ํ ๋๊ธฐ |
| ย | Deadlock ํํผ | ๋ฝ ์์ ๊ณ ์ (lock ordering), std::scoped_lock(์ฌ๋ฌ ๋ฝ ๋์ ํ๋), ํ์์์ |
| ย | Livelock | ๋ฝ์ ์ ์ก๊ณ ์์ง๋ง ์๋ก ์๋ณดํ๋ค๊ฐ ์งํ ์ ๋จ. retry loop์ ํํ ๋ฒ๊ทธ |
| ย | Starvation (๊ธฐ์) | ํน์ ์ค๋ ๋๊ฐ ์์ํ ๋ฝ์ ๋ชป ์ก์. ์ฐ์ ์์ ์ ์ฑ ยทSRWLock์ writer starvation |
| ย | Priority Inversion (์ฐ์ ์์ ์ญ์ ) | ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์ก์ ๋ฝ์ ๋์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๊ธฐ๋ค๋ฆฌ๋๋ฐ, ์ค๊ฐ ์ฐ์ ์์๊ฐ CPU ์ฐจ์ง |
| ย | Priority Inheritance | ์ฐ์ ์์ ์ญ์ ํด๋ฒ โ ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๋ฝ ์ก์ ๋์ ์ผ์์ ์ผ๋ก ์ฐ์ ์์ ์์น |
| Windows API | CRITICAL_SECTION | InitializeCriticalSection/EnterCriticalSection/LeaveCriticalSection. ๊ฐ์ ํ๋ก์ธ์ค ํ์ , ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ |
| ย | SRWLOCK | AcquireSRWLockShared/AcquireSRWLockExclusive. Reader/Writer ๋ถ๋ฆฌ. ๊ฐ๋ฒผ์ |
| ย | Mutex (์ปค๋) | CreateMutex/WaitForSingleObject/ReleaseMutex. ์ด๋ฆ ๋ถ์ฌ ์ ํ๋ก์ธ์ค ๊ฐ |
| ย | Semaphore | CreateSemaphore/WaitForSingleObject/ReleaseSemaphore. ์นด์ดํ
๋ฝ |
| ย | Event | CreateEvent/SetEvent/ResetEvent/PulseEvent |
| ย | CONDITION_VARIABLE | SleepConditionVariableSRW/WakeConditionVariable |
| ย | InterlockedExchange ๊ณ์ด | x86 LOCK ์ ๋์ฌ ๋ช ๋ น ๋ํผ. atomic์ Windows ๊ตฌํ |
| ย | MemoryBarrier() / _mm_mfence | ๋ช ์์ ๋ฉ๋ชจ๋ฆฌ ํ์ค |
| POSIX API | pthread_mutex_t | POSIX mutex. ๊ธฐ๋ณธยทrecursiveยทerrorcheckยทrobust ์ข ๋ฅ |
| ย | pthread_rwlock_t | Reader/Writer lock |
| ย | pthread_cond_t | Condition variable. pthread_cond_wait/signal/broadcast |
| ย | sem_t (POSIX semaphore) | Named (ํ๋ก์ธ์ค ๊ฐ) / unnamed (์ค๋ ๋ ๊ฐ) |
| ย | __atomic_* / GCC builtins | C11 _AtomicยทC++11 std::atomic ์ด์ ์ GCC ๋นํธ์ธ |
| ย | PTHREAD_PROCESS_SHARED ์์ฑ | mutex/cond๋ฅผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ๋๊ณ ํ๋ก์ธ์ค ๊ฐ ๊ณต์ |
| ๋น์ฉ | atomic CAS | ์ ns. ๋ฝ ์์ด ๋๊ธฐํ |
| ย | ์ฌ์ฉ์ ๋ชจ๋ ๋ฝ (๊ฒฝํฉ ์์) | ์์ญ ns. Critical SectionยทSRWLock ๋ฝ ํ๋๋ง |
| ย | ์ฌ์ฉ์ ๋ชจ๋ ๋ฝ (๊ฒฝํฉ) | 1~3 ฮผs. ์ปค๋ ์ง์ + ์ปจํ ์คํธ ์ค์์นญ |
| ย | ์ปค๋ ๊ฐ์ฒด ๋ฝ | 1~3 ฮผs. ํญ์ ์์คํ ์ฝ |
| ์ธ๋ฆฌ์ผ | GameThread | UObjectยทAActorยทTick ์ฒ๋ฆฌ. ๋ฉ์ธ ์ค๋ ๋ โ ๋ฝ ๊ฑฐ์ ์์ (๋จ์ผ ์ค๋ ๋ ๊ฐ์ ) |
| ย | RenderThread | ๋ ๋๋ง ๋ช ๋ น ์ฒ๋ฆฌ. GameThread์ ๋ถ๋ฆฌ๋์ด race ์ฐจ๋จ |
| ย | TaskGraph | ์์กด์ฑ ๊ธฐ๋ฐ ์์ ๋ถํ . ๋ฝ ๋์ ์์ ๊ทธ๋ํ๋ก ๋์์ฑ ํํ |
| ย | FCriticalSection | ์ธ๋ฆฌ์ผ์ Critical Section ๋ํผ |
| ย | FScopeLock | RAII ๋ฝ ๊ฐ๋ (std::lock_guard์ ๋๋ฑ) |
| ย | FThreadSafeCounter | atomic counter (std::atomic<int32> ๋๋ฑ) |
| ย | TQueue<T, EQueueMode::Spsc/Mpsc> | lock-free ํ โ single/multi producer single consumer |
| ย | ENQUEUE_RENDER_COMMAND | GameThread โ RenderThread ๋๋ค ์ ๋ฌ ๋งคํฌ๋ก. ๋ฝ ์์ด ๋ช ๋ น ํ๋ก |
๋ชฉ์ฐจ
- ํต์ฌ ์์ฝ ์นด๋
- ํ ์ค ์ ์ โ Race Condition์ด๋ ๋ฌด์์ธ๊ฐ
- ๋ฐ์ ์กฐ๊ฑด 4๊ฐ์ง์ ๊ฐ์ฅ ๋จ์ํ ์์
- Critical Section โ ์๊ณ ์์ญ์ ๊ฐ๋
- ๋๊ธฐํ ๊ฐ์ฒด ์นดํ๋ก๊ทธ โ MutexยทSemaphoreยทCritical SectionยทSRWLockยทEventยทCondition Variable
- Lock-freeยทatomicยทCAS โ ๋ฝ ์๋ ๋๊ธฐํ
- Memory OrderingยทMemory Barrier โ acquire/release ํ์ด
- ABA ๋ฌธ์ โ Lock-free์ ํจ์
- Deadlock โ ๋ฐ์ 4์กฐ๊ฑด๊ณผ ํํผ ์ ๋ต
- LivelockยทStarvation โ Deadlock์ด ์๋ ๋ค๋ฅธ ์ ์ฒด
- Priority Inversion โ Pathfinder ํ์ฑ ํ์ฌ์ ์ฌ๋ก
- Windows API vs POSIX API ๋น๊ต
- ๋น์ฉ ์คํํธ๋ผ ์ ๋ฆฌ โ ์ด๋ ๋๊ธฐํ๊ฐ ์ผ๋ง๋ ๋น์ผ๊ฐ
- ์ธ๋ฆฌ์ผ์์์ Race Condition ํํผ โ ์ค๋ ๋ ๋ถ๋ฆฌยทTaskGraph
- ๊ผฌ๋ฆฌ์ง๋ฌธ ์์ ๊ฒฝ๋ก
- ํต์ฌ ์์ฝ ์นด๋ (์ฌ๊ฒ์ฌ)
- ํ๊ท ๋ค๋ฆฌ โ ๋ค๋ฅธ CS ํ์ผ ์ฐ๊ฒฐ
1. ํต์ฌ ์์ฝ ์นด๋
30์ด ๋ต๋ณ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Race Condition = ๋ ์ด์์ ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ๋์ ์ ๊ทผ,
์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋น๊ฒฐ์ ์ ํ์.
๋ฐ์ 4์กฐ๊ฑด:
โ ๊ณต์ ์์ ์กด์ฌ
โก ๋ ์ด์์ด ๋์ ์ ๊ทผ
โข ์ ์ด๋ ํ๋๋ ์ฐ๊ธฐ (write)
โฃ ์์๋ฅผ OS๊ฐ ๋ณด์ฅํ์ง ์์
๋ํ ์: count++ ๊ฐ load โ inc โ store 3๋จ๊ณ โ ๊ทธ ์ฌ์ด ์ปจํ
์คํธ ์ค์์นญ
โ ๊ฒฐ๊ณผ ๋น๊ฒฐ์ (102 ๊ธฐ๋, 101 ๋์ฌ ์ ์์)
ํด๊ฒฐ โ Critical Section์ ๋๊ธฐํ ๊ฐ์ฒด๋ก ๋ณดํธ:
โ ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ (์์ญ ns ~ 1 ฮผs)
- Critical Section (Win) / std::mutex / SRWLock
โก ์ปค๋ ๊ฐ์ฒด (1~3 ฮผs, ํ๋ก์ธ์ค ๊ฐ ๊ณต์ ๊ฐ๋ฅ)
- Mutex / Semaphore / Event
โข Lock-free / atomic / CAS (์ ns)
- std::atomic<T>, InterlockedExchange
- ์๋ฃ๊ตฌ์กฐ ์ค๊ณ ๋งค์ฐ ์ด๋ ต๋ค
Memory Model:
CPUยท์ปดํ์ผ๋ฌ๊ฐ ๋ช
๋ น ์ฌ๋ฐฐ์น โ ๋ฉํฐ ์ค๋ ๋ ๊ฐ์์ฑ ๊นจ์ง
โ memory_order_acquire / release ํ์ด๋ก fence
โ x86์ TSO (๊ฐํจ) / ARM์ weak (barrier ํ์)
๋ณ๋ฆฌ:
Deadlock = ์๋ก์ ๋ฝ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๋ฉ์ถค (4์กฐ๊ฑด: ์ํธ๋ฐฐ์ ยท์ ์ ๋๊ธฐยท๋น์ ์ ยท์ํ)
Livelock = ๋ฝ ์ ์ก๊ณ ์๋ณด๋ง ํ๋ค ์งํ ์ ๋จ
Starvation = ํน์ ์ค๋ ๋ ์์ํ ๋ฝ ๋ชป ์ก์
Priority Inv. = ๋ฎ์ ์ฐ์ ์์ ๋ฝ์ ๋์ ์ฐ์ ์์๊ฐ ๊ธฐ๋ค๋ฆผ (Pathfinder ์ฌ๋ก)
ABA = lock-free์์ AโBโA ๋ชป ์์์ฑ
์ธ๋ฆฌ์ผ ์ฒ ํ โ "๋ฝ์ ์ ์ฐ์ง ๋ง๊ณ , ๊ณต์ ๋ฅผ ์ ๋ง๋ ๋ค":
GameThread (UObject) / RenderThread (proxy) / RHIThread (GPU ๋ช
๋ น) ๋ถ๋ฆฌ
TaskGraph๋ก ์์กด์ฑ ๊ทธ๋ํ ํํ โ ๋ฝ ๋์ ์์
์์
ENQUEUE_RENDER_COMMAND โ ๋ช
๋ น ํ๋ก GameThread โ RenderThread
๊ผฌ๋ฆฌ์ง๋ฌธ ์ฐ๊ฒฐ ๋งต
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Race Condition
โโโ ๋ฐ์ ์กฐ๊ฑด (์ ์ผ์ด๋๋?)
โ โโโ ๊ณต์ ์์ (ํยท์ ์ญยท์ ์ )
โ โโโ ๋์ ์ ๊ทผ (๋ ์ด์ ์ค๋ ๋)
โ โโโ ์ฐ๊ธฐ ํฌํจ
โ โโโ ๋น๊ฒฐ์ ์ ์ค์ผ์ค๋ง โ ์ปจํ
์คํธ ์ค์์นญ(21) ํ๊ท
โโโ Critical Section (ํด๊ฒฐ ์ถ์)
โ โโโ ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ
โโโ ๋๊ธฐํ ๊ฐ์ฒด (๋น์ฉ ์คํํธ๋ผ)
โ โโโ Critical Section (Win, ๊ฐ์ ํ๋ก์ธ์ค)
โ โโโ SRWLock (R/W ๋ถ๋ฆฌ)
โ โโโ std::mutex (์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ )
โ โโโ Mutex ์ปค๋ ๊ฐ์ฒด (ํ๋ก์ธ์ค ๊ฐ, IPC(22) ํ๊ท)
โ โโโ Semaphore (์นด์ดํ
)
โ โโโ Event (์๋ฆผ)
โ โโโ Condition Variable (์กฐ๊ฑด ๋๊ธฐ)
โโโ Lock-free
โ โโโ atomic (std::atomic, InterlockedExchange)
โ โโโ CAS (Compare-And-Swap)
โ โโโ ABA ๋ฌธ์ + ํํผ (tagged pointer)
โ โโโ lock-free vs wait-free
โโโ Memory Model
โ โโโ memory_order_relaxed / acquire / release / seq_cst
โ โโโ Memory Barrier / Fence
โ โโโ happens-before
โ โโโ x86 TSO vs ARM weak
โ โโโ ์ปดํ์ผ๋ฌ vs CPU ์ฌ๋ฐฐ์น
โโโ ๋ณ๋ฆฌ
โ โโโ Deadlock (4์กฐ๊ฑดยทํํผยทscoped_lock)
โ โโโ Livelock
โ โโโ Starvation
โ โโโ Priority Inversion (Pathfinder, Priority Inheritance)
โโโ Windows API
โ โโโ CRITICAL_SECTION
โ โโโ SRWLOCK
โ โโโ Mutex (์ปค๋)
โ โโโ Semaphore / Event
โ โโโ CONDITION_VARIABLE
โ โโโ InterlockedExchange ๊ณ์ด
โโโ POSIX API
โ โโโ pthread_mutex_t (+ recursive / errorcheck / robust)
โ โโโ pthread_rwlock_t
โ โโโ pthread_cond_t
โ โโโ sem_t (named / unnamed)
โโโ ์ธ๋ฆฌ์ผ
โโโ GameThread / RenderThread / RHIThread ๋ถ๋ฆฌ
โโโ TaskGraph
โโโ FCriticalSection / FScopeLock
โโโ FThreadSafeCounter (atomic)
โโโ TQueue (lock-free)
โโโ ENQUEUE_RENDER_COMMAND
2. ํ ์ค ์ ์ โ Race Condition์ด๋ ๋ฌด์์ธ๊ฐ
ํต์ฌ ํ ๋ฌธ์ฅ
Race Condition์ ๋ ์ด์์ ์คํ ๋จ์๊ฐ ๊ณต์ ์์์ ๋์ ์ ๊ทผํ ๋, ์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋น๊ฒฐ์ ์ ํ์์ ๋๋ค.
๋จ์ด ๋ถํด
Race๋ โ๊ฒฝ์ฃผโ๋ผ๋ ๋ป ๊ทธ๋๋ก์ ๋๋ค. ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์์์ ๋ํด ๊ฒฝ์ฃผํ๋๋ฐ, ๋๊ฐ ๋จผ์ ๋์ฐฉํ๋๋์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ค๋ ์๋ฏธ์ ๋๋ค. Condition์ โ์กฐ๊ฑดโ ๋๋ โ์ํโ โ ๊ทธ ๊ฒฝ์ฃผ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์์คํ ์ด ์๋ชป๋ ์ํ์ ๋น ์ง ์ ์๋ ์กฐ๊ฑด์ด ๋ง๋ค์ด์ง๋๋ค.
Race Condition vs Data Race
C++ ํ์ค์ ๋์ ๊ตฌ๋ถํฉ๋๋ค:
| ์ฉ์ด | ์ ์ | ๋ฒ์ |
|---|---|---|
| Data Race | ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ๋๊ธฐํ ์์ด ์ ๊ทผ, ์ ์ด๋ ํ๋๊ฐ ์ฐ๊ธฐ. C++์์ UB | ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์์ค |
| Race Condition | ์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋ชจ๋ ์ํฉ | ์๋ฏธยท๊ฒฐ๊ณผ ์์ค |
๋ชจ๋ data race๋ race condition์ด์ง๋ง, race condition์ด ๋ชจ๋ data race๋ ์๋๋๋ค. ์๋ฅผ ๋ค์ด ๋ atomic ์ฐ์ฐ์ ์ ์ ํ ์์ ์์ด ํธ์ถํ๋ฉด data race๋ ์์ง๋ง(atomic์ด๋ผ UB ์๋) ๊ฒฐ๊ณผ๋ ๋น๊ฒฐ์ ์ ์ด๋ผ race condition์ ๋๋ค.
1
2
3
4
5
6
7
8
std::atomic<int> count{0};
// ์ค๋ ๋ A
if (count.load() == 0) { // 1) ์ฝ์
count.store(1); // 2) ์ฐ๊ธฐ - ์ด ์ฌ์ด์ B๊ฐ ๋ค์ด์ค๋ฉด race condition
}
// ์ค๋ ๋ B๋ ๋์์ ๊ฐ์ ์ฝ๋ ์คํ
// โ ๋ ๋ค 1์ ์. ๋ ๋ฒ ์ด๊ธฐํ ๊ฐ์ ์๋ฏธ์ race condition.
// โ data race๋ ์์ (atomic์ด๋ผ).
๋ฉด์ ์์๋ ๋ณดํต ๋์ ํตํ์ด โrace conditionโ์ด๋ผ ๋ถ๋ฅด์ง๋ง, ๋ ๋จ์ด๋ฅผ ๊ตฌ๋ถํด ๋ตํ๋ฉด ๊น์ด๊ฐ ์ฐ๋ค.
ํ๋ฆ ํ๋์
1
2
3
4
5
6
7
8
9
10
์ค๋ ๋ A ๊ณต์ ์์ count ์ค๋ ๋ B
โโโโโ โโโโโโโโโโ โโโโโ
load count โ 100 [100]
load count โ 100
increment โ 101
increment โ 101
store 101
[101]
store 101
[101] โ ๋ ๋ฒ +1 ํ๋๋ฐ +1๋ง ๋ฐ์
์ด๊ฒ race condition์ ๊ฐ์ฅ ๋จ์ํ ํจํด์ ๋๋ค. ์๊ฐ ์์๋ฅผ ํ์ดํ๋ก ๊ทธ๋ฆด ์ ์๋ ๋ชจ๋ ๊ฒฝ์ฐ ์ค์ ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋๋ ๊ฒฝ๋ก๊ฐ ์กด์ฌํ๋ฉด ๊ทธ๊ฒ์ด race condition์ ๋๋ค.
3. ๋ฐ์ ์กฐ๊ฑด 4๊ฐ์ง์ ๊ฐ์ฅ ๋จ์ํ ์์
3.1 ๋ฐ์ 4์กฐ๊ฑด
| ์กฐ๊ฑด | ์๋ฏธ | ์๋ฐ ์ |
|---|---|---|
| โ ๊ณต์ ์์ | ๋ ์ด์์ด ์ ๊ทผ ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌยทํ์ผยท์์ผยท๋๋ฐ์ด์ค | ์์์ด ์ง์ง thread-local์ด๋ฉด race ์์ |
| โก ๋ ์ด์ ๋์ ์ ๊ทผ | ๊ฐ์ ์์ ์ ๋ ์ค๋ ๋ ์ด์์ด ์ ๊ทผ | ๋จ์ผ ์ค๋ ๋๋ง ์ ๊ทผํ๋ฉด race ์์ |
| โข ์ ์ด๋ ํ๋๋ ์ฐ๊ธฐ | ๋ชจ๋ ์ฝ๊ธฐ๋ง ํ๋ฉด race ์์ | const ๋ฐ์ดํฐ ๋ค์ค ์ค๋ ๋ ์ฝ๊ธฐ๋ ์์ |
| โฃ ์์ ๋ณด์ฅ ์์ | OSยทCPU๊ฐ ์ ๊ทผ ์์๋ฅผ ๋ณด์ฅํ์ง ์์ | ๋๊ธฐํ ๊ฐ์ฒด๋ก ์์ ๊ฐ์ ํ๋ฉด race ์์ |
์ด ๋ค ์กฐ๊ฑด ์ค ํ๋๋ง ๊นจ๋ race๋ ์ฌ๋ผ์ง๋๋ค. race๋ฅผ ๋ง๋ ๋ชจ๋ ๊ธฐ๋ฒ์ ์ด ์ค ํ๋๋ฅผ ๊นจ๋ ๊ฒ์ ๋๋ค.
1
2
3
4
์กฐ๊ฑด โ ๊นจ๊ธฐ: ์์์ thread-local๋ก โ thread_local ๋ณ์
์กฐ๊ฑด โก ๊นจ๊ธฐ: ๋จ์ผ ์ค๋ ๋ ๋ชจ๋ธ โ ์ธ๋ฆฌ์ผ GameThread ๋จ์ผํ
์กฐ๊ฑด โข ๊นจ๊ธฐ: immutable (๋ณ๊ฒฝ ๋ถ๊ฐ) โ const, std::shared_ptr<const T>
์กฐ๊ฑด โฃ ๊นจ๊ธฐ: ๋๊ธฐํ ๊ฐ์ฒด๋ก ์์ ๊ฐ์ โ mutex, atomic
์ธ๋ฆฌ์ผ์ด GameThread/RenderThread๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฑด โก๋ฅผ ๊นจ๋ ์ ๋ต์ ๋๋ค. const ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ๊ฑด โข์ ๊นจ๋ ์ ๋ต์ ๋๋ค. mutex๋ก ๋ฝ ๊ฑฐ๋ ๊ฑด โฃ๋ฅผ ๊นจ๋ ์ ๋ต์ ๋๋ค. ์ด๋ ์ชฝ์ด ๊ฐ์ฅ ์ข์์ง๋ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด์ง๋ง, ์์๋๋ก โ > โก > โข > โฃ ๊ฐ ๋น์ฉ์ด ๋ฎ์ ์์ ๋๋ค.
3.2 ๊ฐ์ฅ ๋จ์ํ ์์ โ counter ์ฆ๊ฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int g_count = 0;
void worker() {
for (int i = 0; i < 100000; ++i) {
g_count++; // race condition!
}
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join(); t2.join();
std::cout << g_count << std::endl;
// ์์: 200000
// ์ค์ : 100023 / 187432 / ... ๋งค ์คํ๋ง๋ค ๋ค๋ฆ
}
์ ๊ทธ๋ฐ๊ฐ โ g_count++๊ฐ ๋จ์ผ ๋ช
๋ น์ด ์๋๋ค:
1
2
3
4
5
6
7
8
g_count++ ๋ฅผ x86 ์ด์
๋ธ๋ฆฌ๋ก ๋ณด๋ฉด:
mov eax, [g_count] ; โ load: ๋ฉ๋ชจ๋ฆฌ โ ๋ ์ง์คํฐ
inc eax ; โก increment: ๋ ์ง์คํฐ ์์์ +1
mov [g_count], eax ; โข store: ๋ ์ง์คํฐ โ ๋ฉ๋ชจ๋ฆฌ
์ธ ๋ช
๋ น ์ฌ์ด ์ด๋์๋ ์ปจํ
์คํธ ์ค์์นญ(21)์ด ์ผ์ด๋ ์ ์๋ค.
ํ์ด๋จธ ์ธํฐ๋ฝํธ๋ ๋ช
๋ น ๋จ์๋ก๋ง ๋ผ์ด๋ ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
์ค๋ ๋ A์ ์์ g_count ์ค๋ ๋ B์ ์์
โโโโโโโโโ โโโโโโ โโโโโโโโโ
mov eax, [g_count] [100]
eax = 100
inc eax (=101)
[100] mov eax, [g_count]
eax = 100
inc eax (=101)
mov [g_count], eax
[101]
mov [g_count], eax
[101] โ ๋ ๋ฒ ++ ํ๋๋ฐ ํ ๋ฒ๋ง ๋ฐ์
3.3 ATOMICํ์ง ์์ ๋ ํฐ ๋จ์
๊ฐ์ ํจํด์ด ์ด๋ฆํ๋ฅผ ๋ฐ๊ฟ์ ์ด๋๋ ๋ํ๋ฉ๋๋ค.
1
2
3
4
5
6
7
// ์ํ ๊ณ์ข ์ด์ฒด - "์์์ฑ" ๊นจ์ง
void Transfer(Account& from, Account& to, int amount) {
from.balance -= amount; // โ ์ถ๊ธ
// โ ์ฌ๊ธฐ์ ์ปจํ
์คํธ ์ค์์น โ ๋ค๋ฅธ ์ค๋ ๋๊ฐ from ๋ณด๋ฉด ์์ก ๋ถ์กฑ
// โ ๋๋ from์์ ๋น ์ง ๋์ด ์ด๋์๋ ์๋ ์ํ
to.balance += amount; // โก ์
๊ธ
}
1
2
3
4
5
6
7
// linked list ๋
ธ๋ ์ฝ์
- ์ผ๊ด์ฑ ๊นจ์ง
void Insert(Node* newNode) {
newNode->next = head; // โ newNodeโ๋ค์
// โ ์ฌ๊ธฐ์ ๋ค๋ฅธ ์ค๋ ๋๊ฐ head ๋ณด๋ฉด newNode๊ฐ ์ ๋ณด์
// โ ๊ทธ ์ค๋ ๋๊ฐ head๋ฅผ ๋ค๋ฅธ ๋
ธ๋๋ก ๋ฐ๊ฟ๋ฒ๋ฆฌ๋ฉด newNode๊ฐ ์ฌ๋ผ์ง
head = newNode; // โก headโnewNode
}
1
2
3
4
5
6
// ๊ฒ์ ์ธ๋ฒคํ ๋ฆฌ ์ถ๊ฐ
void AddItem(Item* item) {
items[count] = item; // โ ์ฌ๋กฏ์ ์ ์ฅ
// โ ์ฌ๊ธฐ์ ๋ ์ค๋ ๋๊ฐ ๊ฐ์ count๋ฅผ ๋ด์ ๊ฐ์ ์ฌ๋กฏ ๋ฎ์ด์ธ ์ ์์
count++; // โก ์นด์ดํฐ ์ฆ๊ฐ
}
์ธ ์์ ๋ชจ๋ ๋ณธ์ง์ ๊ฐ์ต๋๋ค โ ์ฌ๋ฌ ๋จ๊ณ์ ์ฐ์ฐ์ด ํ๋์ ๋จ์๋ก ๋ณดํธ๋ฐ์ง ๋ชปํด ์ค๊ฐ ์ํ๊ฐ ๋ ธ์ถ๋ฉ๋๋ค.
3.4 ๋จ์ผ ๋ช ๋ น์ด๋ผ๋ race๊ฐ ๊ฐ๋ฅ
64๋นํธ ๊ฐ์ 32๋นํธ ๋จธ์ ์์ ์ฝ๊ณ ์ฐ๋ฉด ๋ ๋ฒ์ ๋๋ ์ผ์ด๋์ torn read/write๊ฐ ๋ฐ์ํฉ๋๋ค:
1
2
3
4
5
6
7
// 32๋นํธ ๋จธ์ ์์
int64_t g_value = 0;
// ์ค๋ ๋ A: g_value = 0x1234567890ABCDEF
// โ mov [g_value+0], 0x90ABCDEF
// โ ์ฌ๊ธฐ์ ์ค์์น
// โ mov [g_value+4], 0x12345678
// ์ค๋ ๋ B: ์ ์ฌ์ด์ ์ฝ์ผ๋ฉด 0x0000000090ABCDEF (์ฐข์ด์ง ๊ฐ)
64๋นํธ ๋จธ์ ์์ 64๋นํธ ์ ๋ ฌ๋ ๊ฐ์ ๋จ์ผ ๋ช
๋ น์ด์ง๋ง, ๊ทธ๊ฒ๋ ํ์ค์ด ๋ณด์ฅํ์ง ์์ต๋๋ค. ๊ทธ๋์ std::atomic<int64_t>๋ฅผ ๋ช
์์ ์ผ๋ก ์จ์ผ ์์ ํฉ๋๋ค.
4. Critical Section โ ์๊ณ ์์ญ์ ๊ฐ๋
4.1 ์ ์
Critical Section(์๊ณ ์์ญ) ์ โํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐโ ์ ๊ฐ๋ฆฌํค๋ ์ถ์ ๊ฐ๋ ์ ๋๋ค. ์์์ด ์๋๋ผ ์ฝ๋ ๊ตฌ๊ฐ์ ๊ฐ๋ ์ด๋ผ๋ ๊ฒ ์ค์ํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
std::mutex mtx;
int g_count = 0;
void worker() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // โโ
g_count++; // โ โ Critical Section
mtx.unlock(); // โโ
}
}
mtx.lock()๊ณผ mtx.unlock() ์ฌ์ด๊ฐ critical section์ด๊ณ , ์ด ๊ตฌ๊ฐ ์์ ๋ชจ๋ ์ฝ๋๋ ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋ฉ๋๋ค.
4.2 Mutual Exclusion (์ํธ ๋ฐฐ์ )
Critical section์ ํต์ฌ ์ฑ์ง์ด mutual exclusion(์ํธ ๋ฐฐ์ ) โ ํ ์ค๋ ๋๊ฐ ๊ทธ ๊ตฌ๊ฐ์ ์คํ ์ค์ผ ๋ ๋ค๋ฅธ ์ค๋ ๋๋ ๋ค์ด์ฌ ์ ์์ต๋๋ค. โMutexโ๋ผ๋ ์ด๋ฆ๋ MUTual EXclusion์์ ์์ต๋๋ค.
1
2
3
4
5
6
7
[์ค๋ ๋ A] [๋ฝ ์ํ] [์ค๋ ๋ B]
UNLOCKED
lock() โ ์ฑ๊ณต LOCKED by A
g_count++ LOCKED by A lock() โ ๋๊ธฐ (๋ธ๋ก)
unlock() UNLOCKED lock() โ ๊นจ์ด๋จ, ์ฑ๊ณต
LOCKED by B g_count++
UNLOCKED unlock()
4.3 ์ข์ critical section์ ์กฐ๊ฑด (DijkstraยทHoare ๊ณ ์ ์ด๋ก )
| ์กฐ๊ฑด | ์๋ฏธ |
|---|---|
| Mutual Exclusion | ํ ์์ ์ ํ ์ค๋ ๋๋ง critical section ์์ ์์ |
| Progress (์งํ) | critical section์ด ๋น์ด ์์ผ๋ฉด ๋ค์ด๊ฐ๋ ค๋ ์ค๋ ๋ ์ค ๋๊ตฐ๊ฐ๋ ๋ค์ด๊ฐ์ผ ํจ |
| Bounded Waiting (์ ํ๋ ๋๊ธฐ) | ๋ค์ด๊ฐ๋ ค๋ ์ค๋ ๋๋ ๋ฌดํ ๋๊ธฐํ์ง ์์์ผ ํจ |
| No Starvation (๊ธฐ์ ์์) | ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฒฐ๊ตญ ๋ค์ด๊ฐ ์ ์์ด์ผ ํจ |
std::mutex๋ Windows CRITICAL_SECTION์ ์ด ๋ชจ๋๋ฅผ OS๊ฐ ์ฑ
์์ง๋๋ค. ํ์ง๋ง ๋ชจ๋ ๋ฝ์ด ๊ทธ๋ ์ง๋ ์์ต๋๋ค โ SRWLOCK์ writer๋ reader๊ฐ ๊ณ์ ๋ค์ด์ค๋ฉด starvation ๊ฐ๋ฅ. ์ฐ์ ์์ ๋ฝ์ ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋ starvation ๊ฐ๋ฅ.
4.4 Critical Section์ ํฌ๊ธฐ โ ์งง์์๋ก ์ข๋ค
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ๋์ ์ โ critical section์ด ๋๋ฌด ๊น
void ProcessItem(Item* item) {
mtx.lock();
auto heavy = ComputeHeavyData(item); // โ 100ms ๊ฑธ๋ฆผ. ๊ทธ ๋์ ๋ค๋ฅธ ์ค๋ ๋ ๋ชจ๋ ๋๊ธฐ
sharedList.push_back(heavy);
mtx.unlock();
}
// ์ข์ ์ โ ๊ณต์ ์์ ์ ๊ทผ๋ง critical section ์์
void ProcessItem(Item* item) {
auto heavy = ComputeHeavyData(item); // โ ๋ฝ ๋ฐ์์ ๊ณ์ฐ
mtx.lock();
sharedList.push_back(heavy);
mtx.unlock();
}
Critical section์ ํฌ๊ธฐ๊ฐ ์ค์ด๋ค๋ฉด lock contention(๋ฝ ๊ฒฝํฉ) ์ด ์ค์ด๋ญ๋๋ค. ๊ฒฝํฉ์ด ๋ง์ผ๋ฉด ์ปจํ ์คํธ ์ค์์นญ(21)์ด ํญ์ฆํด์ single-thread ์ฝ๋๋ณด๋ค ๋ ๋๋ ค์ง ์ ์์ต๋๋ค โ convoy effect.
4.5 RAII ํจํด โ std::lock_guard / std::scoped_lock
1
2
3
4
5
6
void worker() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // ์์ฑ ์ lock, ์๋ฉธ ์ unlock
g_count++;
}
}
lock_guard๋ RTTIยทRAII(9)์์ ๋ณธ ํจํด ๊ทธ๋๋ก. ์ค์ฝํ๋ฅผ ๋ฒ์ด๋ ๋ ์๋์ผ๋ก unlock๋๋ฏ๋ก ์์ธ๊ฐ ๋์ ธ์ ธ๋ unlock ๋๋ฝ์ด ์์ต๋๋ค. C++ ๋ฉํฐ์ค๋ ๋ ์ฝ๋์์ raw lock()/unlock()์ ์ง์ ๋ถ๋ฅด๋ ๊ฑด ๊ฑฐ์ ํญ์ ์ํฐ ํจํด์
๋๋ค.
scoped_lock(C++17)์ ์ฌ๋ฌ mutex๋ฅผ deadlock-free๋ก ๋์ ์ ๊ธ:
1
2
3
4
5
void Transfer(Account& a, Account& b, int amount) {
std::scoped_lock lock(a.mtx, b.mtx); // deadlock-free ๋์ ๋ฝ
a.balance -= amount;
b.balance += amount;
}
std::scoped_lock์ ๋ด๋ถ์ ์ผ๋ก ๋ ๋ฝ์ try-lock + ๋ฐฑ์คํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ก์์ deadlock์ ํํผํฉ๋๋ค (deadlock 4์กฐ๊ฑด์ โ์ํ ๋๊ธฐโ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฐจ๋จ).
5. ๋๊ธฐํ ๊ฐ์ฒด ์นดํ๋ก๊ทธ โ MutexยทSemaphoreยทCritical SectionยทSRWLockยทEventยทCondition Variable
5.1 Mutex (๋ฐฐํ์ ๋ฝ)
๊ฐ์ฅ ๊ธฐ๋ณธ. ํ ์ค๋ ๋๋ง ์ก์ ์ ์๋ ๋ฐฐํ์ ๋ฝ.
| ์ข ๋ฅ | API | ํน์ง |
|---|---|---|
std::mutex | C++11 ํ์ค | RAII ๋ํผ์ ํจ๊ป. ์ฌ์ง์ ๋ถ๊ฐ |
std::recursive_mutex | C++11 | ๊ฐ์ ์ค๋ ๋๊ฐ ์ฌ๋ฌ ๋ฒ ์ฌ์ ๊ธ ๊ฐ๋ฅ. ๋ณดํต ์ค๊ณ ๊ฒฐํจ ์ ํธ |
std::timed_mutex | C++11 | try_lock_for / try_lock_until ์ง์ |
Windows CRITICAL_SECTION | Win32 | ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ . ๊ฐ์ ํ๋ก์ธ์ค ํ์ |
Windows Mutex (์ปค๋) | CreateMutex | ํญ์ ์ปค๋. ์ด๋ฆ ๋ถ์ฌ ์ ํ๋ก์ธ์ค ๊ฐ |
POSIX pthread_mutex_t | POSIX | ๊ธฐ๋ณธยทrecursiveยทerrorcheckยทrobust ์ข ๋ฅ |
1
2
3
4
5
std::mutex mtx;
{
std::lock_guard<std::mutex> lock(mtx);
// critical section
} // ์๋ unlock
5.2 Semaphore (์นด์ดํ ๋ฝ)
N๊ฐ ๋์ ์ ๊ทผ ํ์ฉ. ์นด์ดํฐ๊ฐ ์์๊ฐ ๋๋ฉด wait, ์์๋ฉด ์งํ.
1
2
3
4
5
6
7
// C++20๋ถํฐ ํ์ค
std::counting_semaphore<10> sem(3); // ์ต๋ 10, ์ด๊ธฐ 3
// ๋๋ std::binary_semaphore (0/1๋ง ๊ฐ๋ฅ, mutex์ ๋น์ท)
sem.acquire(); // ์นด์ดํฐ -1, 0 ์ดํ๋ฉด ๋๊ธฐ
// ... critical section (์ต๋ 3๊ฐ ์ค๋ ๋ ๋์ ์ง์
)
sem.release(); // ์นด์ดํฐ +1, ๋๊ธฐ ์ค์ธ ์ค๋ ๋ ๊นจ์
์ฌ์ฉ ์ฌ๋ก:
- ์์ ํ(connection pool): ๋์ ์ ๊ทผ N๊ฐ๋ก ์ ํ
- ์์ฐ์-์๋น์: ๋น ์ฌ๋กฏ ์นด์ดํฐยท์ฐฌ ์ฌ๋กฏ ์นด์ดํฐ
- ์ฐ๋กํ๋ง: ์ด๋น N๊ฐ ์ฒ๋ฆฌ ์ ํ
5.3 Critical Section (Windows)
Windows์ ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ mutex. ๊ฐ์ ํ๋ก์ธ์ค ์ ํ์ ์ด์ง๋ง ๋งค์ฐ ๋น ๋ฆ ๋๋ค.
1
2
3
4
5
6
7
8
9
10
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
// ๋๋ InitializeCriticalSectionEx๋ก spin count ์ง์
InitializeCriticalSectionAndSpinCount(&cs, 4000);
EnterCriticalSection(&cs);
// critical section
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
๋์:
- ๋ฝ์ด ๋น์ด ์์ผ๋ฉด โ atomic ๋ช ๋ น์ผ๋ก ์ฆ์ ์ก์ (์์ญ ns)
- ๋ฝ์ด ์กํ ์์ผ๋ฉด โ spin count๋งํผ busy-wait
- spin count ๋๋๋ ์ ํ๋ฆฌ๋ฉด โ ์ปค๋ ์ง์ , ์ปจํ ์คํธ ์ค์์นญ
Spin count๋ ๋ฉํฐ์ฝ์ด ํ๊ฒฝ์์ ํจ๊ณผ์ ์ ๋๋ค โ ์งง๊ฒ ์กํ๋ค ํ๋ฆด ๋ฝ์ด๋ฉด ์ปจํ ์คํธ ์ค์์นญ(21)๋ณด๋ค spin์ด ๋น ๋ฆ ๋๋ค.
5.4 SRWLock (Slim Reader/Writer Lock, Windows Vista+)
์ฝ๊ธฐ๋ ๋ค์คยท์ฐ๊ธฐ๋ ๋ฐฐํ. std::shared_mutex์ Windows ๊ตฌํ ๊ธฐ๋ฐ.
1
2
3
4
5
6
7
8
9
10
11
12
SRWLOCK srw;
InitializeSRWLock(&srw);
// ์ฐ๊ธฐ ๋ฝ
AcquireSRWLockExclusive(&srw);
// ... ์ฐ๊ธฐ critical section (ํ ์ค๋ ๋๋ง)
ReleaseSRWLockExclusive(&srw);
// ์ฝ๊ธฐ ๋ฝ
AcquireSRWLockShared(&srw);
// ... ์ฝ๊ธฐ critical section (์ฌ๋ฌ ์ค๋ ๋ ๋์)
ReleaseSRWLockShared(&srw);
C++ ํ์ค ๋ฑ๊ฐ๋ฌผ:
1
2
3
4
5
6
7
8
9
10
11
12
13
std::shared_mutex smtx;
// ์ฐ๊ธฐ
{
std::unique_lock<std::shared_mutex> lock(smtx);
// ... ์ฐ๊ธฐ
}
// ์ฝ๊ธฐ
{
std::shared_lock<std::shared_mutex> lock(smtx);
// ... ์ฝ๊ธฐ
}
reader-heavy ์ํฌ๋ก๋(์ฝ๊ธฐ ๋ง๊ณ ์ฐ๊ธฐ ์ ์)์ ์ ํฉ โ ๋ชจ๋ reader๊ฐ ๋์ ์งํ. writer starvation ์ํ ์์(reader๊ฐ ๊ณ์ ๋ค์ด์ค๋ฉด writer๊ฐ ์์ํ ๋ชป ์ก์). ์ ์ฑ ์ OS ๊ตฌํ์ ๋ฐ๋ผ ๋ค๋ฆ.
5.5 Event (Windows) / Condition Variable
์ํ ์๋ฆผ ์ฉ. critical section๊ณผ ๋ฌ๋ฆฌ ๋ฐ์ดํฐ ๋ณดํธ๊ฐ ์๋๋ผ โ์ด๋ค ์ผ์ด ์ผ์ด๋ฌ๋คโ๋ฅผ ์๋ฆผ.
1
2
3
4
5
6
7
8
9
10
// Windows Event
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// ๋ ๋ฒ์งธ ์ธ์ FALSE: auto-reset (ํ ์ค๋ ๋๋ง ๊นจ์)
// ์ธ ๋ฒ์งธ ์ธ์ FALSE: ์ด๊ธฐ ์ํ non-signaled
// ํ ์ค๋ ๋: ๋๊ธฐ
WaitForSingleObject(hEvent, INFINITE);
// ๋ค๋ฅธ ์ค๋ ๋: ์ ํธ
SetEvent(hEvent);
C++ ํ์ค์ condition variable์ mutex์ ์ง์ง์ด ์๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// ๋๊ธฐ ์ธก
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // ready==true ๋ ๋๊น์ง ๋๊ธฐ (atomicํ๊ฒ unlock+sleep)
// ... ready ๋๋ฉด ๊นจ์ด๋จ, ๋ค์ lock ์ก์
}
// ์ ํธ ์ธก
void producer() {
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // ๋๋ notify_all()
}
cv.wait์ ์ ์ด(predicate) ์ธ์๊ฐ ์ค์ํฉ๋๋ค โ spurious wakeup(๊ฐ์ง ๊นจ์ด๋จ) ํํผ์ฉ. CV๋ OS์ ๋ณด์ฅ ๋ถ์กฑ์ผ๋ก ๊นจ์ด๋ฌ์ ๋ ์ ์ด๋ฅผ ์ฌํ์ธํด์ผ ํฉ๋๋ค.
5.6 ๋๊ธฐํ ๊ฐ์ฒด ๋น๊ต ํ
| ๊ฐ์ฒด | ๋์ ์ ๊ทผ | ๋น์ฉ (๊ฒฝํฉ ์์) | ํ๋ก์ธ์ค ๊ฐ | ์ฌ์ฉ์ฒ |
|---|---|---|---|---|
std::mutex / Critical Section | 1 (๋ฐฐํ) | ์์ญ ns | X (Critical Section) | ์ผ๋ฐ ๋ฝ |
std::recursive_mutex | 1 (๊ฐ์ ์ค๋ ๋ ์ฌ์ง์ ) | ์์ญ ns | X | ์ฌ๊ท ํธ์ถ |
std::shared_mutex / SRWLock | N (read) / 1 (write) | ์์ญ ns | X | reader-heavy |
std::counting_semaphore | N (์ง์ ) | ์์ญ ns | (๊ตฌํ์ ๋ฐ๋ผ) | ์์ ํ |
Mutex ์ปค๋ ๊ฐ์ฒด (CreateMutex) | 1 | 1~3 ฮผs | O (์ด๋ฆ ๋ถ์ฌ) | IPC(22) ํ๊ท |
| Semaphore ์ปค๋ ๊ฐ์ฒด | N | 1~3 ฮผs | O | IPC(22) |
| Event | - (์๋ฆผ) | 1~3 ฮผs | O (์ด๋ฆ) | ์๋ฆผยท์ผํ์ฑ ๋๊ธฐํ |
| Condition Variable | - (๋๊ธฐ) | 1~3 ฮผs | X (๋ณดํต) | ์กฐ๊ฑด ๋ง์กฑ ๋๊ธฐ |
6. Lock-freeยทatomicยทCAS โ ๋ฝ ์๋ ๋๊ธฐํ
6.1 ์ lock-free์ธ๊ฐ
๋ฝ์ ๋น์ฉ์ด ํฝ๋๋ค โ ๊ฒฝํฉ ์ ์ปจํ ์คํธ ์ค์์นญ(21) ๋ฐ์, ๋ฐ๋๋ฝ ์ํ, priority inversion. lock-free๋ ๋ฝ ์์ด ๋๊ธฐํํ๋ ๊ธฐ๋ฒ์ผ๋ก, CPU์ atomic instruction์ ์ด์ฉํฉ๋๋ค.
lock-free์ ์ ํํ ์ ์: ์ด๋ค ์์ ์์๋ ์ ์ด๋ ํ ์ค๋ ๋๋ finite step ์์ ์งํ๋๋ค (์์คํ ์ ์ฒด๊ฐ ๋ฉ์ถ์ง ์์). wait-free๋ ๋ ๊ฐํจ: ๋ชจ๋ ์ค๋ ๋๊ฐ finite step ์์ ์งํ๋๋ค.
| ๋จ๊ณ | ๋ณด์ฅ |
|---|---|
| Blocking (๋ฝ ๊ธฐ๋ฐ) | ํ ์ค๋ ๋๊ฐ ๋ฉ์ถ๋ฉด ๋ค๋ฅธ ์ค๋ ๋๋ ๋ฉ์ถ ์ ์์ (deadlock ๊ฐ๋ฅ) |
| Obstruction-free | ๋ค๋ฅธ ์ค๋ ๋์ ๊ฐ์ญ์ด ์์ผ๋ฉด finite step์ ์งํ |
| Lock-free | ์์คํ ์ ์ฒด๋ก ๋ณด๋ฉด ์ ์ด๋ ํ๋๋ ํญ์ ์งํ |
| Wait-free | ๋ชจ๋ ๊ฐ๋ณ ์ค๋ ๋๊ฐ finite step์ ์งํ (๊ฐ์ฅ ๊ฐํจ) |
๋๋ถ๋ถ์ lock-free ์๋ฃ๊ตฌ์กฐ๋ wait-free๊น์ง๋ ๋ชป ๊ฐ๊ณ lock-free์ ๋จธ๋ญ ๋๋ค โ wait-free๊ฐ ํจ์ฌ ์ด๋ ค์.
6.2 atomic โ ๋จ์ผ ๋ช ๋ น ๋๊ธฐํ
1
2
3
4
5
6
std::atomic<int> count{0};
// ์์ โ atomic instruction
count.fetch_add(1); // ๋๋ count++
int v = count.load(); // ๋๋ (int)count
count.store(42); // ๋๋ count = 42
std::atomic<T>์ ์ฐ์ฐ์ CPU์ ๋จ์ผ ๋ช
๋ น์ผ๋ก ๋๋๊ฑฐ๋, LOCK prefix๊ฐ ๋ถ์ RMW(read-modify-write) ๋ช
๋ น์ผ๋ก ๋๋ฉ๋๋ค.
1
2
3
4
5
6
7
count.fetch_add(1)๋ฅผ x86 ์ด์
๋ธ๋ฆฌ๋ก:
lock add [count], 1 ; LOCK prefix โ ๋ค๋ฅธ CPU ์ฝ์ด๊ฐ ๋ผ์ด๋ค์ง ๋ชปํจ
count++ (๋น atomic)์ x86 ์ด์
๋ธ๋ฆฌ๋ก:
mov eax, [count] ; โ ์ฌ๊ธฐ์ ๋ผ์ด๋ค ์ ์์
inc eax
mov [count], eax
LOCK prefix๋ ๋ฒ์ค ๋ฝ(bus lock) ๋๋ ์บ์ ๋ผ์ธ ๋ฝ(cache line lock) ์ ๊ฑธ์ด ๋ค๋ฅธ ์ฝ์ด๊ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ๊ทผํ์ง ๋ชปํ๊ฒ ํฉ๋๋ค. ๋น์ฉ์ ์~์์ญ ns๋ก ๋ฝ์ ๋นํด ๋งค์ฐ ์ ๋ ดํฉ๋๋ค.
6.3 CAS โ Compare-And-Swap
lock-free์ ํต์ฌ ๋ช ๋ น. โ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ์ด expected๋ฉด desired๋ก ๋ฐ๊พธ๊ณ true ๋ฐํ, ์๋๋ฉด ๊ทธ ๋ฉ๋ชจ๋ฆฌ์ ํ์ฌ ๊ฐ์ expected์ ์ฐ๊ณ false ๋ฐํโ.
1
2
3
4
5
6
7
std::atomic<int> value{100};
int expected = 100;
int desired = 200;
bool success = value.compare_exchange_strong(expected, desired);
// value == 100์ด๋ฉด โ 200์ผ๋ก ๋ณ๊ฒฝ, success = true
// value != 100์ด๋ฉด โ expected์ ํ์ฌ value ์ ์ฅ, success = false
x86 ์ด์
๋ธ๋ฆฌ๋ LOCK CMPXCHG. ARM์ LL/SC (Load-Linked/Store-Conditional).
CAS๋ก ๋ง๋ lock-free ์นด์ดํฐ:
1
2
3
4
5
6
7
8
std::atomic<int> count{0};
void Increment() {
int old_val = count.load();
while (!count.compare_exchange_weak(old_val, old_val + 1)) {
// ์คํจ ์ old_val์ ํ์ฌ ๊ฐ์ด ๋ค์ด ์์, ๋ค์ ์๋
}
}
์ด๊ฒ ์ฌ์ค์ count.fetch_add(1)๊ณผ ๊ฐ์ ์ผ์ ํ์ง๋ง, ์์์ ์ฐ์ฐ์ผ๋ก ์ผ๋ฐํ ๊ฐ๋ฅํฉ๋๋ค.
1
2
3
4
5
6
7
// "๊ฐ์ด ์์๋ฉด 0์ผ๋ก ๋ง๋ค๊ธฐ" โ fetch_add๋ก๋ ๋ชป ํจ, CAS๋ก ๊ฐ๋ฅ
void ClampNonNegative() {
int old_val = value.load();
while (old_val < 0) {
if (value.compare_exchange_weak(old_val, 0)) break;
}
}
6.4 weak vs strong CAS
| ๋ณํ | ๋์ | ์ฌ์ฉ์ฒ |
|---|---|---|
compare_exchange_weak | spurious failure ํ์ฉ (์คํจํด๋ ์ ๋ง ๊ฐ์์ง ๋ชจ๋ฆ) | ๋ฃจํ์์ ์ฌ์ฉ โ ์ด์ฐจํผ ์ฌ์๋ |
compare_exchange_strong | ์ง์ง ๋น๊ต ๊ฒฐ๊ณผ๋ง ๋ฐํ | ๋จ์ผ ์๋ |
weak๋ ARM ๊ฐ์ LL/SC ๋จธ์ ์์ ์ธํฐ๋ฝํธ ๋ฑ์ผ๋ก spurious failure๊ฐ ์ผ์ด๋ ์ ์๋ ๋ช ๋ น์ ๋งคํ๋ฉ๋๋ค. ๋ฃจํ์์๋ weak๊ฐ ๋ ํจ์จ์ ์ ๋๋ค.
6.5 Windows Interlocked* ํจ์๊ตฐ
C++ std::atomic์ด ๋์
๋๊ธฐ ์ Windows์ atomic API.
| Windows | C++ ๋ฑ๊ฐ | ์๋ฏธ |
|---|---|---|
InterlockedIncrement | fetch_add(1) | +1 |
InterlockedDecrement | fetch_sub(1) | -1 |
InterlockedExchange | exchange | ๊ตํ |
InterlockedCompareExchange | compare_exchange_strong | CAS |
InterlockedAdd | fetch_add | +N |
InterlockedAnd / Or / Xor | fetch_and/or/xor | ๋นํธ ์ฐ์ฐ |
std::atomic์ Windows ๊ตฌํ์ ๋ณดํต ์ด ํจ์๋ค ๋๋ ์ง์ LOCK prefix ๋ช
๋ น์ผ๋ก ๋งคํ๋ฉ๋๋ค.
6.6 Lock-free ์๋ฃ๊ตฌ์กฐ์ ์ โ ์คํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<typename T>
class LockFreeStack {
struct Node { T value; Node* next; };
std::atomic<Node*> head{nullptr};
public:
void push(T v) {
Node* newNode = new Node{v, nullptr};
Node* oldHead = head.load();
do {
newNode->next = oldHead;
} while (!head.compare_exchange_weak(oldHead, newNode));
// CAS ์คํจ ์ oldHead์ ํ์ฌ head๊ฐ ๋ค์ด์ด โ ๋ค์ ์๋
}
bool pop(T& out) {
Node* oldHead = head.load();
while (oldHead && !head.compare_exchange_weak(oldHead, oldHead->next))
; // CAS ์คํจ ์ ์ฌ์๋
if (!oldHead) return false;
out = oldHead->value;
delete oldHead; // โ ABA ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ โ 8์ ์ฐธ์กฐ
return true;
}
};
์ด ๋จ์ lock-free ์คํ์ ABA ๋ฌธ์ ๋๋ฌธ์ ์ค์ ์์๋ ๊ทธ๋๋ก ๋ชป ์๋๋ค. tagged pointerยทhazard pointerยทepoch-based reclamation ๊ฐ์ ์ถ๊ฐ ๊ธฐ๋ฒ์ด ํ์ํฉ๋๋ค.
6.7 Lock-free๊ฐ ์ ๋ต์ด ์๋ ์ด์
| ๋จ์ | ์ค๋ช |
|---|---|
| ์ค๊ณ ๋งค์ฐ ์ด๋ ค์ | ๋ชจ๋ ๊ฐ๋ฅํ ์ธํฐ๋ฆฌ๋น์ ๊ณ ๋ คํด์ผ ํจ. ๊ฒ์ฆ ๋๊ตฌ ํ์(TLA+ยทSpin) |
| debug ๊ฑฐ์ ๋ถ๊ฐ๋ฅ | ์ฌํ ์ ๋๋ ๋ฒ๊ทธ. memory model ์๋ฐ ์ ๋๋ฒ๊ฑฐ๊ฐ ๋์์ฃผ์ง ๋ชปํจ |
| ๋ฉ๋ชจ๋ฆฌ ํ์ ๋ฌธ์ | ์ delete oldHead์ฒ๋ผ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋ณด๊ณ ์๋์ง ์ ์ ์์ |
| memory ordering ์ง์ ์ง์ | acquire/release๋ฅผ ์๋ชป ์ฐ๋ฉด ์ผ๊ด์ฑ ๊นจ์ง |
| CPU์ ๋ฐ๋ฅธ ๋น์ฉ | LOCK prefix๋ cache coherence traffic์ ๋ง๋ค์ด ๋ฉํฐ์ฝ์ด ํ์ฅ์ฑ ์ ํด |
๊ทธ๋์ ์ค๋ฌด์์ ๊ณ ๋๋ก ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Boost.Lockfree, folly, concurrentqueue)๋ฅผ ์ฐ์ง, ์ง์ lock-free ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ง์ ๋ง๋๋ lock-free๋ ๊ฑฐ์ ํญ์ ๋ฝ ๊ธฐ๋ฐ๋ณด๋ค ๋๋ฆฝ๋๋ค.
7. Memory OrderingยทMemory Barrier โ acquire/release ํ์ด
7.1 ์ memory ordering์ด ๋ฌธ์ ์ธ๊ฐ
ํ๋ CPU๋ ์ฑ๋ฅ์ ์ํด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์์๋ฅผ ์ฌ๋ฐฐ์นํฉ๋๋ค:
- ์ปดํ์ผ๋ฌ ์ฌ๋ฐฐ์น โ ์ต์ ํ ์ ๋ช ๋ น ์์ ๋ณ๊ฒฝ
- out-of-order execution โ CPU๊ฐ dependency ์๋ ๋ช ๋ น์ ๋ณ๋ ฌ ์คํ
- store buffer โ ์ฐ๊ธฐ๊ฐ ์ฆ์ ์บ์์ ์ ๊ฐ๊ณ store buffer์ ๋จธ๋ฌพ
- cache coherence delay โ ํ ์ฝ์ด์ ์ฐ๊ธฐ๊ฐ ๋ค๋ฅธ ์ฝ์ด์ ๋ณด์ด๊ธฐ๊น์ง ์๊ฐ
1
2
3
4
5
6
7
8
// ์ค๋ ๋ A
data = 42; // โ store
ready = true; // โก store
// ์ค๋ ๋ B
if (ready) { // โข load
use(data); // โฃ load โ data ๊ฐ 42 ์ผ๊น?
}
์ง๊ด์ ์ผ๋ก โready๊ฐ true๋ฉด data๋ 42โ์ฌ์ผ ํฉ๋๋ค. ํ์ง๋ง:
- ์ปดํ์ผ๋ฌ๊ฐ โ โก๋ฅผ โก โ โ ์์๋ก ์ฌ๋ฐฐ์นํ ์ ์์
- CPU๊ฐ ๊ฐ์ ์ผ์ ํ ์ ์์
- ์บ์ ์ผ๊ด์ฑ ์ง์ฐ์ผ๋ก B๊ฐ ready=true๋ ๋ดค์ง๋ง data=42๋ ์์ง ๋ชป ๋ด
โ B์์ use(data)๊ฐ 42๊ฐ ์๋ ๋ค๋ฅธ ๊ฐ(์: 0)์ ๋ณผ ์ ์์.
7.2 C++ memory_order 6๋จ๊ณ
1
2
3
4
5
6
7
8
enum memory_order {
memory_order_relaxed, // ์์ ๋ณด์ฅ ์์
memory_order_consume, // (deprecated ๊ถ์ฅ โ relaxed๋ก fallback)
memory_order_acquire, // load์ ๋ถ์ โ ์ดํ ์ ๊ทผ์ด ์์ผ๋ก ์ ์ด
memory_order_release, // store์ ๋ถ์ โ ์ด์ ์ ๊ทผ์ด ๋ค๋ก ์ ๊ฐ
memory_order_acq_rel, // RMW์ ๋ถ์ โ acquire+release
memory_order_seq_cst // ๊ฐ์ฅ ๊ฐํ ๋ชจ๋ธ (๊ธฐ๋ณธ๊ฐ)
};
7.3 ๊ฐ์ฅ ํํ ํจํด โ acquire/release pair
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::atomic<bool> ready{false};
int data = 0;
// Producer ์ค๋ ๋
data = 42;
ready.store(true, std::memory_order_release);
// ^^^^^^^^^^
// "์ด์ ์ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ์ฐ๊ธฐ(data=42)๊ฐ ์ด store ์ด์ ์ ์๋ฃ"
// Consumer ์ค๋ ๋
while (!ready.load(std::memory_order_acquire))
;
// ^^^^^^^^^^
// "์ด load ์ดํ์ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ์ฝ๊ธฐ๊ฐ ์ด load ์ดํ์ ์์"
use(data); // โ ์์ . 42 ๋ณด์ฅ
release store โ acquire load๊ฐ ํ ์ง์ผ ๋ happens-before ๊ด๊ณ๊ฐ ์ฑ๋ฆฝํฉ๋๋ค โ release ์ด์ ์ ๋ชจ๋ ์ฐ๊ธฐ๊ฐ acquire ์ดํ์ ๋ชจ๋ ์ฝ๊ธฐ์์ ๋ณด์ ๋๋ค.
7.4 memory_order_seq_cst (sequential consistency)
๊ธฐ๋ณธ๊ฐ. ๊ฐ์ฅ ๊ฐํ ๋ชจ๋ธ โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ชจ๋ SC atomic ์ฐ์ฐ์ ๊ฐ์ ์์๋ก ๋ด ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
std::atomic<int> x{0}, y{0};
// ์ค๋ ๋ A
x.store(1); // SC
int r1 = y.load();
// ์ค๋ ๋ B
y.store(1); // SC
int r2 = x.load();
// SC ํ์์ r1=0 && r2=0 ๋ถ๊ฐ๋ฅ
// (๋ ๋ค 0์ด๋ฉด ๋ store๊ฐ ๋ ๋ค ์๊ธฐ load๋ณด๋ค ๋ฆ์ ์
, ์ผ๊ด๋ ์์ ์์)
// relaxed ํ์์๋ r1=0 && r2=0 ๊ฐ๋ฅ
SC๋ ๊ฐ์ฅ ์์ ํ์ง๋ง ๊ฐ์ฅ ๋น์๋๋ค โ ๋ชจ๋ ์ฝ์ด์ memory fence๋ฅผ brodcastํด์ผ ํฉ๋๋ค. ARM ๊ฐ์ weak memory model์์ SC๊ฐ ๋งค์ฐ ๋น์ธ๊ณ , x86์ TSO๋ผ ์๋์ ์ผ๋ก ์ธ์ง๋ง ๊ทธ๋๋ ๋น์๋๋ค.
7.5 memory_order_relaxed โ ์์ ๋ณด์ฅ ์์
์์๋ ๋ณด์ฅ ์ ํ๊ณ ์์์ฑ๋ง ๋ณด์ฅ.
1
2
3
4
std::atomic<int> counter{0};
// ๋จ์ ์นด์ดํฐ โ ์์ ๋ฌด๊ด
counter.fetch_add(1, std::memory_order_relaxed);
๋ค๋ฅธ ๋ณ์์์ ์์ ๊ด๊ณ๊ฐ ํ์ ์์ ๋๋ง. ํต๊ณ ์นด์ดํฐยท์ฐธ์กฐ ์นด์ดํฐ(shared_ptr์ ์ฆ๊ฐ ๋ถ๋ถ) ๋ฑ์ ์ ํฉ. ์ฐธ์กฐ ์นด์ดํฐ์ ๊ฐ์๋ release-acquire์ฌ์ผ ํจ (์๋ฉธ ์ง์ ๋ง์ง๋ง ์ฐ๊ธฐ ๊ฐ์์ฑ ํ์).
7.6 ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ ์ฐจ์ด
| ์ํคํ ์ฒ | ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ | ์ฌ๋ฐฐ์น ํ์ฉ |
|---|---|---|
| x86 / x64 | TSO (Total Store Order) | storeโload๋ง ์ฌ๋ฐฐ์น. ๋น๊ต์ ๊ฐํจ |
| ARM / ARM64 | Weak | ๊ฑฐ์ ๋ชจ๋ ์ฌ๋ฐฐ์น ํ์ฉ |
| RISC-V | Weak (RVWMO) | ARM๊ณผ ๋น์ท |
| POWER | Weak | ๋งค์ฐ ์ฝํจ |
x86์์ ์ ๋๋ ์ฝ๋๊ฐ ARM์์ race๋ก ๊นจ์ง๋ ๊ฒฝ์ฐ๊ฐ ํํฉ๋๋ค โ x86์ด ๋ฌด์์์ ์ผ๋ก acquire/release๋ฅผ ์ผ๋ถ ๋ณด์ฅํด์คฌ๊ธฐ ๋๋ฌธ. ๊ทธ๋์ cross-platform ์ฝ๋๋ ๋ช ์์ memory_order๊ฐ ํ์.
7.7 Memory Barrier ์ง์ ํธ์ถ
1
2
3
4
5
6
7
8
9
10
11
12
// Windows
MemoryBarrier(); // full barrier
// C++11
std::atomic_thread_fence(std::memory_order_seq_cst); // full
std::atomic_thread_fence(std::memory_order_acquire);
std::atomic_thread_fence(std::memory_order_release);
// x86 ์ธ๋ผ์ธ
_mm_mfence(); // full memory fence
_mm_sfence(); // store fence
_mm_lfence(); // load fence
์ง์ ํธ์ถ์ ๊ฑฐ์ ์ ์๋๋ค โ atomic ์ฐ์ฐ์ memory_order๋ฅผ ๋ถ์ด๋ ๊ฒ ํ์ค ๋ฐฉ์.
8. ABA ๋ฌธ์ โ Lock-free์ ํจ์
8.1 ์๋๋ฆฌ์ค
1
2
3
4
5
6
7
8
9
10
// LockFreeStack์ pop
bool pop(T& out) {
Node* oldHead = head.load();
while (oldHead && !head.compare_exchange_weak(oldHead, oldHead->next))
;
if (!oldHead) return false;
out = oldHead->value;
delete oldHead;
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
์ด๊ธฐ: head โ A โ B โ C โ null
์ค๋ ๋ 1: oldHead = A๋ฅผ load
(CAS ์ง์ ์ ์ค์์น)
์ค๋ ๋ 2: pop() โ A ์ ๊ฑฐ, head โ B
์ค๋ ๋ 2: pop() โ B ์ ๊ฑฐ, head โ C
์ค๋ ๋ 2: push(D)
์ค๋ ๋ 2: free(A), ๊ทธ ๋ฉ๋ชจ๋ฆฌ์ new๋ก ๋ค์ ์กํ๋๋ฐ ์ฐ์ฐํ ๊ฐ์ ์ฃผ์ โ push(A')
โป A' ๋ผ๊ณ ํ๊ธฐํ์ง๋ง ์ฃผ์๋ ๊ฐ์
head โ A' โ C โ null (A'->next = C)
์ค๋ ๋ 1 ๊นจ์ด๋จ: head.compare_exchange_weak(oldHead=A, oldHead->next=B)
CAS๋ head==A๋ก ๋ณธ๋ค (์ฃผ์๊ฐ ๊ฐ์ผ๋ฏ๋ก) โ ์ฑ๊ณต
head๋ฅผ B๋ก ๋ฐ๊ฟ
ํ์ง๋ง B๋ ์ด๋ฏธ free๋ ๋ฉ๋ชจ๋ฆฌ! โ ๋๊ธ๋ง ํฌ์ธํฐ(10) ํ๊ท
CAS๋ ๊ฐ(์ฃผ์)๋ง ๋น๊ตํ์ง โ๊ทธ ์ฌ์ด์ ๋ค๋ฅธ ์ผ์ด ์ผ์ด๋ฌ๋์งโ๋ ๋ชจ๋ฆ ๋๋ค. AโBโA๋ก ๋์์จ ๊ฑธ ์์์ฑ์ง ๋ชปํฉ๋๋ค โ ์ด๊ฒ ABA ๋ฌธ์ .
8.2 ํํผ 1 โ Tagged Pointer (๋ฒ์ ์นด์ดํฐ)
1
2
3
4
5
6
7
8
9
10
struct TaggedPtr {
Node* ptr;
uintptr_t tag; // ๋งค ์์ ๋ง๋ค ์ฆ๊ฐ
};
std::atomic<TaggedPtr> head;
// pop
TaggedPtr old = head.load();
TaggedPtr next_tagged{old.ptr->next, old.tag + 1};
head.compare_exchange_weak(old, next_tagged);
๋งค ์์ ๋ง๋ค tag๊ฐ 1 ์ฆ๊ฐํ๋ฏ๋ก, A โ B โ A๋ก ๋์์๋ tag๊ฐ ๋ค๋ฆ. CAS๊ฐ ๋ค๋ฅด๋ค๊ณ ์ธ์.
64๋นํธ ๋จธ์ ์์๋ ํฌ์ธํฐ + 16~32๋นํธ tag๋ก double-word CAS(x86์ LOCK CMPXCHG16B)๋ฅผ ์๋๋ค.
8.3 ํํผ 2 โ Hazard Pointer
๊ฐ ์ค๋ ๋๊ฐ โ์ง๊ธ ์ฝ๊ณ ์๋ ํฌ์ธํฐโ๋ฅผ hazard pointer ๋ฐฐ์ด์ ๋ฑ๋ก. ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ํ๋ ค๋ฉด ๋ค๋ฅธ ์ค๋ ๋์ hazard pointer๋ฅผ ๋ชจ๋ ๊ฒ์ฌํด์ ์๋ฌด๋ ์ ๋ณด๊ณ ์๋์ง ํ์ธ.
1
2
3
4
5
์ค๋ ๋ 1: hazard[1] = A (A๋ฅผ ์ฝ์ ๊ฑฐ์)
์ค๋ ๋ 2: A๋ฅผ list์์ ์ ๊ฑฐ
์ค๋ ๋ 2: free(A)? โ hazard[1] = A ์ด๋ฏ๋ก ๋ชป ํจ, retire list์ ๋ฃ์
์ค๋ ๋ 1: ๋๋๋ฉด hazard[1] = nullptr
์ค๋ ๋ 2: ๋ค์ ํ์ ์ retire list ์ฌ๊ฒ์ฌ
์ค๋ฒํค๋๋ ์์ง๋ง ABA๋ฅผ ๊ทผ๋ณธ์ ์ผ๋ก ๋ง์.
8.4 ํํผ 3 โ Epoch-based Reclamation
์ ์ญ epoch ์นด์ดํฐ๋ฅผ ๋๊ณ , ๋ชจ๋ ์ค๋ ๋๊ฐ ์๊ธฐ epoch์ ๊ธฐ๋ก. ๋ชจ๋ ์ค๋ ๋๊ฐ epoch N ์ดํ๋ก ์ง์ ํ์ผ๋ฉด epoch N ์ด์ ์ retire๋ ๋ฉ๋ชจ๋ฆฌ๋ ์์ ํ๊ฒ ํ์.
RCU(Read-Copy-Update, Linux ์ปค๋)๊ฐ ์ด ์๋ฆฌ๋ฅผ ์ฌ์ฉ.
8.5 ํํผ 4 โ Garbage Collection
GC๊ฐ ์๋ ์ธ์ด(JavaยทC#)๋ ABA๊ฐ ์ ์ผ์ด๋จ โ GC๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ํ ๋ ๋๊ฐ ๋ณด๊ณ ์๋์ง ์๋ค. ๊ทธ๋์ Java์ AtomicReference๋ ABA ๊ฑฑ์ ์์ด lock-free ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ์ ์์.
C++์ GC๊ฐ ์์ผ๋ ์ ์ธ ๊ฐ์ง ์ค ํ๋๋ฅผ ์ง์ ๊ตฌํํด์ผ ํจ โ lock-free๊ฐ ์ด๋ ค์ด ์ง์ง ์ด์ .
8.6 ๋ฉด์ ์์ ๊ฐ์กฐ ํฌ์ธํธ
- โCAS๊ฐ ๊ฐ๋ง ๋น๊ตํ์ง ๋ณํ ์ด๋ ฅ์ ๋ชจ๋ฆ ๋๋คโ
- โlock-free ์๋ฃ๊ตฌ์กฐ๋ ABA๋ง ์๋๋ผ memory reclamation ๋ฌธ์ ๊น์ง ๊ฐ์ด ํ์ด์ผ ํฉ๋๋คโ
- โ๊ทธ๋์ ์ค๋ฌด์์ Boost.Lockfree๋ folly ๊ฐ์ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋๋คโ
9. Deadlock โ ๋ฐ์ 4์กฐ๊ฑด๊ณผ ํํผ ์ ๋ต
9.1 Coffman ์กฐ๊ฑด (Deadlock 4์กฐ๊ฑด)
| ์กฐ๊ฑด | ์๋ฏธ |
|---|---|
| โ Mutual Exclusion | ์์์ ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์ฌ์ฉ |
| โก Hold and Wait | ์์์ ์ก์ ์ฑ๋ก ๋ค๋ฅธ ์์ ๋๊ธฐ |
| โข No Preemption | ์์์ ๊ฐ์ ๋ก ๋นผ์์ ์ ์์ |
| โฃ Circular Wait | ๋๊ธฐ ๊ทธ๋ํ์ ์ํ ๋ฐ์ |
๋ค ์กฐ๊ฑด์ด ๋ชจ๋ ๋ง์กฑ๋์ด์ผ deadlock. ํ๋๋ง ๊นจ๋ฉด deadlock ๋ถ๊ฐ.
9.2 ๊ฐ์ฅ ๋จ์ํ ์์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::mutex mtx_a, mtx_b;
void thread1() {
mtx_a.lock();
// โ ์ฌ๊ธฐ์ ์ปจํ
์คํธ ์ค์์น
mtx_b.lock();
// ...
mtx_b.unlock();
mtx_a.unlock();
}
void thread2() {
mtx_b.lock(); // โ ์ปจํ
์คํธ ์ค์์น ํ ์ฌ๊ธฐ ์คํ
// โ ์ฌ๊ธฐ์ ์ปจํ
์คํธ ์ค์์น
mtx_a.lock(); // โ thread1์ด mtx_a ์ก๊ณ ์์, ๋๊ธฐ
// ...
}
// thread1์ mtx_b๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ณ , thread2๋ mtx_a๋ฅผ ๊ธฐ๋ค๋ฆผ
// โ ์์ํ ๋ฉ์ถค (deadlock)
1
2
3
4
5
6
7
8
[thread1] [thread2]
mtx_a.lock() โ
mtx_b.lock() โ
mtx_b.lock() โ (waiting)
mtx_a.lock() โ (waiting)
์ํ ๋๊ธฐ ๊ทธ๋ํ:
thread1 โ mtx_b โ thread2 โ mtx_a โ thread1
9.3 ํํผ ์ ๋ต โ 4์กฐ๊ฑด ๊นจ๊ธฐ
9.3.1 Lock Ordering (์ํ ๋๊ธฐ ๊นจ๊ธฐ)
๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์์๋ก ๋ฝ์ ์ก์ผ๋ฉด ์ํ์ด ์ ์๊น.
1
2
3
4
5
6
7
8
9
10
11
// ์ฝ์: ํญ์ mtx_a โ mtx_b ์์
void thread1() {
mtx_a.lock();
mtx_b.lock();
...
}
void thread2() {
mtx_a.lock(); // โ thread2๋ mtx_a ๋จผ์
mtx_b.lock();
...
}
์ค๋ฌด์์ ๊ฐ์ฅ ํํ ํจํด. ๋ฝ์ ID(์ฃผ์)๋ฅผ ๋ถ์ฌํ๊ณ ID ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๊ธ.
1
2
3
4
5
6
7
8
9
10
void Transfer(Account& a, Account& b, int amount) {
Account* first = (&a < &b) ? &a : &b;
Account* second = (&a < &b) ? &b : &a;
first->mtx.lock();
second->mtx.lock();
a.balance -= amount;
b.balance += amount;
second->mtx.unlock();
first->mtx.unlock();
}
9.3.2 std::scoped_lock / std::lock (Hold and Wait ๊นจ๊ธฐ)
C++17์ std::scoped_lock์ ์ฌ๋ฌ ๋ฝ์ deadlock-free ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋์ ์ ๊ธ.
1
2
3
4
5
void Transfer(Account& a, Account& b, int amount) {
std::scoped_lock lock(a.mtx, b.mtx); // ๋ด๋ถ์ ์ผ๋ก try-lock + back-off
a.balance -= amount;
b.balance += amount;
}
๋ด๋ถ ์๊ณ ๋ฆฌ์ฆ์ ๋ณดํต try-lock all then back off:
1
2
3
4
1. mtx_a.lock()
2. mtx_b.try_lock() ์๋
์ฑ๊ณต โ ๋
์คํจ โ mtx_a.unlock() (back off), ์ ์ sleep, ๋ค์ ์๋
์ด ๋ฐฉ์์ ์์๋ฅผ ๊ณ ์ ํ์ง ์์๋ deadlock-free.
9.3.3 Timeout (No Preemption ๋ถ๋ถ ๊นจ๊ธฐ)
1
2
3
4
5
6
if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
// ... ๋ฝ ์ฑ๊ณต
mtx.unlock();
} else {
// ํ์์์ โ ๋ค๋ฅธ ์ฒ๋ฆฌ
}
ํ์์์์ด ๋ฝ์ ํ๊ฒ ๋ง๋ค์ด ์๊ตฌ ๋๊ธฐ ํํผ. ๋จ์ ์ timeout ํ ์ด๋ป๊ฒ ๋ณต๊ตฌํ ์ง ์ค๊ณ ๋ถ๋ด.
9.3.4 Lock-free (Mutual Exclusion ์์ฒด ํํผ)
๋ฝ ์์ฒด๊ฐ ์์ผ๋ฉด deadlock ์์. ๋จ lock-free ์๋ฃ๊ตฌ์กฐ ์ค๊ณ ๋น์ฉ ํผ.
9.4 Deadlock ๊ฐ์ง
๊ฒ์ถ ๋๊ตฌ:
- Visual Studio Concurrency Visualizer
- Intel Inspector
- ThreadSanitizer (TSan, Clang/GCC) โ ๋์ ๋ถ์
- Helgrind (Valgrind) โ Linux
์ด๋ค์ ์ ๊ธ ์์๋ฅผ ์ถ์ ํด์ ์ํ ๋๊ธฐ ๊ฐ๋ฅ์ฑ์ ๊ฒฝ๊ณ ํฉ๋๋ค.
10. LivelockยทStarvation โ Deadlock์ด ์๋ ๋ค๋ฅธ ์ ์ฒด
10.1 Livelock
๋ฝ์ ์ ์ก๊ณ ์์ง๋ง ์งํ ์ ๋จ. ๋ ์ค๋ ๋๊ฐ ์๋ก ์๋ณดํ๋ค๊ฐ ์์ํ reset.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ์: ์ข์ ๋ณต๋์์ ๋ ์ฌ๋์ด ๋ง์ฃผ์ณ์ ์๋ก ๊ฐ์ ๋ฐฉํฅ์ผ๋ก ๋นํค๋ ์ํฉ
bool tryAcquireBoth() {
if (!mtx_a.try_lock()) return false;
if (!mtx_b.try_lock()) {
mtx_a.unlock(); // back off
return false;
}
return true;
}
void worker() {
while (true) {
if (tryAcquireBoth()) {
// ... ์์
mtx_a.unlock();
mtx_b.unlock();
return;
}
// ์คํจ โ ์ฆ์ ์ฌ์๋
}
}
๋ ์ค๋ ๋๊ฐ ๋์์ ์๋ โ ๋ ๋ค ์ฒซ ๋ฝ์ ์ก์ง๋ง ๋ ๋ค ๋ ๋ฒ์งธ ๋ฝ ์คํจ โ ๋ ๋ค back off โ ๋ค์ ๋์์ ์๋โฆ ๋ฌดํ ๋ฐ๋ณต.
ํด๊ฒฐ: random back-off. ์คํจ ์ ๋ฌด์์ ์๊ฐ ๋๊ธฐ ํ ์ฌ์๋.
1
2
3
4
if (!tryAcquireBoth()) {
int delay = rand() % 100;
std::this_thread::sleep_for(std::chrono::microseconds(delay));
}
Ethernet์ CSMA/CD๊ฐ ๊ฐ์ ์๋ฆฌ(binary exponential backoff).
10.2 Starvation (๊ธฐ์)
ํน์ ์ค๋ ๋๊ฐ ์์ํ ์์์ ๋ชป ์ก์. deadlock๊ณผ ๋ฌ๋ฆฌ ๋ค๋ฅธ ์ค๋ ๋๋ ์งํ ์ค.
์์ธ:
- ์ฐ์ ์์ ์ค์ผ์ค๋ง โ ๋์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๊ณ์ ๋ค์ด์ ๋ฎ์ ์ฐ์ ์์๋ ๋ชป ํจ
- SRWLock์ writer starvation โ reader๊ฐ ๊ณ์ ๋ค์ด์ค๋ฉด writer๊ฐ ๋๊ธฐ์ด์์ ๋ชป ๋น ์ง
- ๋ถ๊ณต์ ๋ฝ(unfair lock) โ ๋ฝ release ์ ์์์ ๋๊ธฐ์ ์ ํ, ๊ฐ์ ์ค๋ ๋๋ง ๊ณ์ ์ก์
ํด๊ฒฐ: ๊ณต์ ์ฑ(fairness) ์ ์ฑ .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ๊ณต์ ํ โ FIFO
class FairMutex {
std::mutex mtx;
std::condition_variable cv;
std::queue<std::thread::id> waiters;
bool locked = false;
public:
void lock() {
std::unique_lock<std::mutex> ul(mtx);
auto id = std::this_thread::get_id();
waiters.push(id);
cv.wait(ul, [&]{ return !locked && waiters.front() == id; });
locked = true;
waiters.pop();
}
void unlock() {
std::unique_lock<std::mutex> ul(mtx);
locked = false;
cv.notify_all();
}
};
std::mutex๋ ํ์ค์ด ๊ณต์ ์ฑ์ ๋ณด์ฅํ์ง ์์ โ ๊ตฌํ์ ๋ฐ๋ผ ๋ค๋ฆ. ๊ณต์ ์ฑ์ด ํ์ํ๋ฉด ์ง์ ๊ตฌํ ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ.
11. Priority Inversion โ Pathfinder ํ์ฑ ํ์ฌ์ ์ฌ๋ก
11.1 ์๋๋ฆฌ์ค
์ธ ์ค๋ ๋๊ฐ ์๋ค๊ณ ํ์:
- H (High priority): ์ค์ํ ์์
- M (Medium priority): ๋ณดํต ์์
- L (Low priority): ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
L์ด mutex๋ฅผ ์ก๊ณ ์๋๋ฐ H๊ฐ ๊ทธ mutex๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ํฉ:
1
2
3
4
5
6
7
8
9
์๊ฐ โ
L: โโ[๋ฝ ์ก๊ณ ์์
]โโ...
H: โโ[๋ฝ ๋๊ธฐ]โโโโโโโโโโโโ
M: โโ[CPU ์ฐจ์ง, ๊ณ์ ์คํ]โโ
L: ๋ฉ์ถค (M์ ์ํด ์ ์ )
H: ๊ณ์ ๋๊ธฐ (L์ด ๋ฝ ์ ํ์ด์ค)
โ
M์ด ๋๋ ๋๊น์ง H๋ ๋ชป ์งํ
๋ฎ์ ์ฐ์ ์์ L < ์ค๊ฐ M ๋๋ฌธ์ ๋์ ์ฐ์ ์์ H๊ฐ ์ ์ฒด
๋ฌธ์ : H๊ฐ L๋ณด๋ค ๋์ ์ฐ์ ์์์ธ๋ฐ๋ M ๋๋ฌธ์ ์งํ ๋ชป ํจ.
11.2 Mars Pathfinder ์ฌ๋ก (1997)
NASA์ ํ์ฑ ํ์ฌ์ Pathfinder๊ฐ ํ์ฑ ๋์ฐฉ ํ ๋ช ์๊ฐ๋ง๋ค ์์ฒด ์ฌ๋ถํ ํ๋ ๋ฒ๊ทธ ๋ฐ์. ๋ถ์ ๊ฒฐ๊ณผ:
- bus management (high priority): ํต์ ๋ฒ์ค ๊ด๋ฆฌ
- meteorological data gathering (low priority): ๊ธฐ์ ๋ฐ์ดํฐ ์์ง
- communication (medium priority): ์ง๊ตฌ์ ํต์
๊ธฐ์ ๋ฐ์ดํฐ ์ค๋ ๋๊ฐ IPC ํ์ mutex๋ฅผ ์ก์ ์ฑ๋ก ํต์ ์ค๋ ๋(medium)์ ์ ์ ๋จ. ๊ทธ ์ฌ์ด bus management(high)๊ฐ IPC mutex๋ฅผ ๊ธฐ๋ค๋ฆผ. Watchdog timer๊ฐ bus management ๋ฉ์ถค์ ๊ฐ์งํ๋ฉด ์์คํ ์ฌ๋ถํ . ๊ฐ์ ์ผ์ด ๋ฐ๋ณต.
ํด๊ฒฐ: Priority Inheritance ํ์ฑํ (VxWorks OS์ ๊ธฐ๋ฅ์ ์๊ฒฉ์ผ๋ก ์ผฌ). ์ดํ ์ ์ ๋์.
11.3 Priority Inheritance (PI)
๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๋์ ์ฐ์ ์์ ์ค๋ ๋์ ๋ฝ์ ์ก๊ณ ์์ ๋, ์ผ์์ ์ผ๋ก ๊ทธ ์ค๋ ๋์ ์ฐ์ ์์๋ฅผ ์์.
1
2
3
4
5
6
7
L: โโ[๋ฝ ์ก์, ์ฐ์ ์์ = L]โโ
H: โโ[๋ฝ ๋๊ธฐ]โโ
โ
L์ ์ฐ์ ์์๋ฅผ H๋ก ๋์ด์ฌ๋ฆผ (PI)
L: โโ[๋ฝ ์ก์, ์ฐ์ ์์ = H]โโ (M์ด ์ ์ ๋ชป ํจ)
H: โโ[๋ฝ ๋ฐ์, ์ฐ์ ์์ = H๋ก ๋ณต๊ท]โโ
L: ์ฐ์ ์์ = L๋ก ๋ณต๊ท
POSIX๋ pthread_mutexattr_setprotocol(PTHREAD_PRIO_INHERIT) ๋ก ํ์ฑํ. Windows์ std::mutex๋ ๊ธฐ๋ณธ PI ์ง์ ์์.
11.4 Priority Ceiling Protocol (PCP)
PI์ ๋์. ๊ฐ ์์์ ceiling priority(๊ทธ ์์์ ์ก์ ์ ์๋ ๊ฐ์ฅ ๋์ ์ฐ์ ์์)๋ฅผ ๋ถ์ฌ. ๋ฝ ์ก๋ ์๊ฐ ๊ทธ ์ค๋ ๋์ ์ฐ์ ์์๋ฅผ ceiling์ผ๋ก ์์น. ๋ฝ ํด์ ์ ์๋๋๋ก.
PI๋ ๋์ , PCP๋ ์ ์ . ์ค์๊ฐ ์์คํ ์์ PCP ์ ํธ โ ๋ถ์์ด ๋ ์ฌ์.
11.5 ๋ฉด์ ์์ ๊ฐ์กฐ ํฌ์ธํธ
- โPriority Inversion์ OS ์ค์ผ์ค๋ฌ์ ๋ฝ์ด ํจ๊ป ๋ง๋๋ ๋ณ๋ฆฌโ
- โPathfinder ์ฌ๋ก๋ ๋์์ฑ ๋ฒ๊ทธ๊ฐ ์ฐ์ฃผ ๋ฏธ์ ๊น์ง ๊นจ๋จ๋ฆด ์ ์๋ค๋ ๊ตํโ
- โ์ค์๊ฐ ์์คํ (์ฐจ๋ ์ ์ด, ์๋ฃ ๊ธฐ๊ธฐ)์์ PI/PCP ํ์โ
12. Windows API vs POSIX API ๋น๊ต
12.1 Mutex
| ํญ๋ชฉ | Windows | POSIX |
|---|---|---|
| ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ | CRITICAL_SECTION | (์์ โ pthread_mutex๊ฐ ๊ธฐ๋ณธ ๋น ๋ฆ) |
| ์ปค๋ (ํ๋ก์ธ์ค ๊ฐ) | Mutex (CreateMutex) | pthread_mutex_t + PTHREAD_PROCESS_SHARED ์์ฑ |
| Reader/Writer | SRWLOCK (Vista+) | pthread_rwlock_t |
| ์ฌ์ง์ | CRITICAL_SECTION์ ๊ธฐ๋ณธ ์ฌ์ง์
| PTHREAD_MUTEX_RECURSIVE ์์ฑ |
| Robust | (์ง์ ์ง์ ์์) | PTHREAD_MUTEX_ROBUST (์์ ์ค๋ ๋ ์ฃฝ์ผ๋ฉด ๋ค๋ฅธ ์ค๋ ๋๊ฐ ํ์ ๊ฐ๋ฅ) |
12.2 Semaphore / Event / Condition Variable
| ํญ๋ชฉ | Windows | POSIX |
|---|---|---|
| Semaphore | CreateSemaphore (์ปค๋) | sem_t (named/unnamed) |
| Event | CreateEvent | (์ง์ ๋์ ์์ โ condition variable๋ก ํ๋ด) |
| Condition Variable | CONDITION_VARIABLE + SleepConditionVariable* | pthread_cond_t + pthread_cond_wait/signal/broadcast |
| Wait API | WaitForSingleObject / WaitForMultipleObjects | sem_wait / pthread_cond_wait / poll ๋ฑ ๊ฐ์ฒด๋ณ |
Windows์ WaitForMultipleObjects๋ ๋งค์ฐ ๊ฐ๋ ฅ โ ์ฌ๋ฌ ํธ๋ค์ ํ ๋ฒ์ ๋๊ธฐ. POSIX๋ ๋ณดํต select/poll/epoll์ fd ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ.
12.3 Atomic
| ํญ๋ชฉ | Windows | POSIX / GCC |
|---|---|---|
| ์ธํฌ๋ฆฌ๋จผํธ | InterlockedIncrement | __atomic_add_fetch / __sync_fetch_and_add |
| CAS | InterlockedCompareExchange | __atomic_compare_exchange_n |
| Memory Barrier | MemoryBarrier() | __atomic_thread_fence(__ATOMIC_SEQ_CST) |
| C++ ํ์ค | ๋ ๋ค std::atomic<T> (C++11) | ๋ ๋ค std::atomic<T> |
C++11 ์ดํ๋ก๋ ๋ ๋ค std::atomic<T>๋ฅผ ์ฐ๋ฉด ๋ฉ๋๋ค โ ํ๋ซํผ ์ฐจ์ด๋ฅผ ํ์ค์ด ํก์.
12.4 ๋๊ธฐํ ๋น๊ต ํ
| ๋ฉ์ปค๋์ฆ | Windows | POSIX | C++ ํ์ค |
|---|---|---|---|
| ์ฌ์ฉ์ ๋ชจ๋ mutex | CRITICAL_SECTION | pthread_mutex_t (๊ธฐ๋ณธ) | std::mutex |
| R/W lock | SRWLOCK | pthread_rwlock_t | std::shared_mutex (C++17) |
| ์ฌ๊ท mutex | CRITICAL_SECTION (๊ธฐ๋ณธ ์ฌ๊ท) | PTHREAD_MUTEX_RECURSIVE | std::recursive_mutex |
| Semaphore | CreateSemaphore | sem_t | std::counting_semaphore (C++20) |
| Condition Variable | CONDITION_VARIABLE | pthread_cond_t | std::condition_variable |
| Atomic | Interlocked* | __atomic_* | std::atomic<T> |
| Memory fence | MemoryBarrier() | __atomic_thread_fence | std::atomic_thread_fence |
| Thread create | CreateThread | pthread_create | std::thread |
13. ๋น์ฉ ์คํํธ๋ผ ์ ๋ฆฌ โ ์ด๋ ๋๊ธฐํ๊ฐ ์ผ๋ง๋ ๋น์ผ๊ฐ
์ปจํ ์คํธ ์ค์์นญ(21)์์ ๋๊ธฐํ ๊ฐ์ฒด ๋น์ฉ ์คํํธ๋ผ์ ์ผ๋ถ ๋ค๋ค๋๋ฐ, ์ฌ๊ธฐ์ race ๋ณดํธ ๊ด์ ์์ ๋ค์ ์ ๋ฆฌํฉ๋๋ค.
13.1 ๋น์ฉ ํ
| ๋ฉ์ปค๋์ฆ | ๋น์ฉ (๊ฒฝํฉ ์์) | ๋น์ฉ (๊ฒฝํฉ) | ๋น๊ณ |
|---|---|---|---|
std::atomic<T> load/store | 1~์ ns | 1~์ ns | ์บ์ ๋ผ์ธ ๊ฒฝํฉ ์ ๋ค์ ์ฆ๊ฐ |
atomic.fetch_add / CAS | 5~20 ns | 5~์์ญ ns | LOCK prefix |
CRITICAL_SECTION / std::mutex | ์์ญ ns | 1~3 ฮผs | ๊ฒฝํฉ ์ ์ปจํ ์คํธ ์ค์์นญ(21) |
SRWLOCK ์ฝ๊ธฐ | ์์ญ ns | ์๋ฐฑ ns | reader๋ผ๋ฆฌ๋ ๋น ๋ฆ |
SRWLOCK ์ฐ๊ธฐ | ์์ญ ns | 1~3 ฮผs | reader ๋ชจ๋ ๋ ๋ ๋๊น์ง ๋๊ธฐ |
Mutex ์ปค๋ ๊ฐ์ฒด | 1~3 ฮผs | 1~5 ฮผs | ํญ์ ์์คํ ์ฝ |
Semaphore ์ปค๋ | 1~3 ฮผs | 1~5 ฮผs | ๋์ผ |
Event ์ ํธ+wait | 1~3 ฮผs | 1~5 ฮผs | ๊นจ์ฐ๊ธฐ๊น์ง ์ปจํ ์คํธ ์ค์์น |
Condition Variable wait+notify | 1~3 ฮผs | 1~5 ฮผs | mutex์ ํจ๊ป |
13.2 ๋น์ฉ ๊ตฌ์ฑ ๋ถ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::atomic CAS (์ ns):
โ CPU LOCK prefix + ์บ์ ๋ผ์ธ ๋ฝ
CRITICAL_SECTION ๊ฒฝํฉ ์์ (์์ญ ns):
โ atomic CAS๋ก ๋ฝ ์ก๊ธฐ
โ ๋ฝ ํ๊ธฐ
CRITICAL_SECTION ๊ฒฝํฉ (1~3 ฮผs):
โ spin ์๋ (์ง์ ๋ spin count)
โ ์คํจ ์ ์ปค๋ ์ง์
(๋ชจ๋ ์ค์์น, ์ปจํ
์คํธ ์ค์์นญ(21))
โ ๋๊ธฐ ํ์ ์ถ๊ฐ
โ ๋ค๋ฅธ ์ค๋ ๋ ๊นจ์ด๋จ (์ปจํ
์คํธ ์ค์์น)
โ ์ ๊ธ ํ๋
Mutex ์ปค๋ ๊ฐ์ฒด (1~5 ฮผs):
โ ์์คํ
์ฝ ์ง์
โ ์ปค๋ ๊ฐ์ฒด ์ํ ํ์ธ
โ ์ ๊ธ ๋๋ ๋๊ธฐ (๋๊ธฐ ์ ์ปจํ
์คํธ ์ค์์น)
โ ์์คํ
์ฝ ๋ฆฌํด
13.3 ๊ฒฝํฉ๋ฅ ์ ๋ฐ๋ฅธ ์ ํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
๊ฒฝํฉ ๊ฑฐ์ ์์ (< 1%):
โ std::atomic ๋๋ ์ฌ์ฉ์ ๋ชจ๋ ๋ฝ (CRITICAL_SECTION, std::mutex)
โ ๋น์ฉ ๊ฑฐ์ 0
๊ฒฝํฉ ๋ณดํต (1~10%):
โ ์ฌ์ฉ์ ๋ชจ๋ ๋ฝ + spin (CRITICAL_SECTION์ spin count)
โ ์งง์ critical section์ spin์ด ํจ๊ณผ์
๊ฒฝํฉ ์ฌํจ (> 10%):
โ ์๊ณ ๋ฆฌ์ฆ ์ฌ๊ฒํ (๋ฝ ์ธ๋ถํ, lock striping)
โ critical section ํฌ๊ธฐ ์ค์ด๊ธฐ
โ lock-free ์๋ฃ๊ตฌ์กฐ ๊ณ ๋ ค
๋งค์ฐ ์งง์ critical section + ๋ฉํฐ์ฝ์ด:
โ spin lock (atomic CAS ๊ธฐ๋ฐ)
โ ๋๋ lock-free
13.4 ๋ฝ ์ธ๋ถํ (Lock Striping)
ํฐ ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ๋ฌ ๋ฝ์ผ๋ก ๋๋ ๊ฒฝํฉ ๋ถ์ฐ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ๋์ ์ โ ํ๋์ ๋ฝ
class HashMap {
std::mutex mtx;
std::vector<Bucket> buckets;
public:
void Insert(K k, V v) {
std::lock_guard lock(mtx);
buckets[hash(k) % buckets.size()].insert(k, v);
}
};
// ์ข์ ์ โ ๋ฒํท๋ณ ๋ฝ
class HashMap {
static constexpr size_t N = 16;
std::array<std::mutex, N> mtxs;
std::array<Bucket, N> buckets;
public:
void Insert(K k, V v) {
size_t i = hash(k) % N;
std::lock_guard lock(mtxs[i]); // โ 1/N๋ก ๊ฒฝํฉ ๋ถ์ฐ
buckets[i].insert(k, v);
}
};
Java์ ConcurrentHashMap์ด lock striping์ ๋ํ ์. ๋ถํ๊ฐ N๋ฐฐ๋ก ๋ถ์ฐ.
14. ์ธ๋ฆฌ์ผ์์์ Race Condition ํํผ โ ์ค๋ ๋ ๋ถ๋ฆฌยทTaskGraph
14.1 ์ธ๋ฆฌ์ผ์ ์ฒ ํ โ ๊ณต์ ์์ฒด๋ฅผ ์ค์ธ๋ค
๋ค๋ฅธ ๊ฒ์ ์์ง๋ค์ด โ๋ฝ์ ์ ๊ฑฐ๋โ ๋ฐฉํฅ์ด๋ผ๋ฉด, ์ธ๋ฆฌ์ผ์ โ๊ณต์ ๋ฅผ ๋ง๋ค์ง ์๋โ ๋ฐฉํฅ์ ํํฉ๋๋ค. ์ปจํ ์คํธ ์ค์์นญ(21)์์ ๋ณธ ๊ฒ์ฒ๋ผ ์ค๋ ๋๋ฅผ ๋ช ํํ ๋ถ๋ฆฌํ๊ณ , ๊ทธ ์ฌ์ด๋ฅผ ๋ช ๋ น ํ๋ก ์ฐ๊ฒฐ.
1
2
3
4
5
6
7
8
9
10
11
12
GameThread โ ๊ฒ์ ๋ก์ง, UObject, AActor, Tick
โ
โ ENQUEUE_RENDER_COMMAND (๋๋ค + ๋ฐ์ดํฐ ๋ณต์ฌ)
โ
RenderThread โ ๋ ๋๋ง ๋ช
๋ น ์์ฑ, scene proxy ์ฌ์ฉ
โ
โ RHI commands
โ
RHIThread โ GPU ๋๋ผ์ด๋ฒ ํธ์ถ (D3D12/Vulkan/Metal)
โ
โ
GPU
GameThread๋ง UObject๋ฅผ ๋ง์ง๋๋ค. RenderThread๋ GameThread๊ฐ ํ ํ๋ ์๋ง๋ค ์ ๋ฆฌํด ๋๊ธด scene proxy(๋ถ๋ณ ์ค๋ ์ท)๋ง ์ฌ์ฉ. ๊ทธ๋ฌ๋ฏ๋ก ๋ ์ค๋ ๋๊ฐ ๊ฐ์ UObject๋ฅผ ๋์์ ๋ง์ง๋ race๋ ๊ตฌ์กฐ์ ์ผ๋ก ์ฐจ๋จ.
14.2 ENQUEUE_RENDER_COMMAND โ ๋ช
๋ น ํ๋ก ๋ฐ์ดํฐ ๋๊ธฐ๊ธฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
// GameThread ์ฝ๋
void AMyActor::UpdateMaterial(FLinearColor NewColor)
{
FMaterialProxy* MaterialProxy = GetMaterialProxy(); // GameThread๊ฐ ๋ง๋ proxy
ENQUEUE_RENDER_COMMAND(UpdateColor)
(
[MaterialProxy, NewColor](FRHICommandListImmediate& RHICmdList)
{
// RenderThread์์ ์คํ
MaterialProxy->SetColor(NewColor);
}
);
}
๋๋ค๊ฐ ์บก์ฒํ ๊ฐ(MaterialProxy, NewColor)์ด ๋ช
๋ น ํ์ ๋ณต์ฌ๋์ด RenderThread๋ก ์ ๋ฌ. GameThread๋ ์ฆ์ ๋ฆฌํด.
ํต์ฌ โ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํด ๋๊น์ผ๋ก์จ ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ง ์ผ์ด ์๊ฒ ๋ง๋ญ๋๋ค. ๊ณต์ ์์ฒด๋ฅผ ํํผ.
14.3 TaskGraph โ ์์กด์ฑ ๊ทธ๋ํ๋ก ๋์์ฑ
ํฐ ์์ ์ ์์ task๋ก ์ชผ๊ฐ๊ณ , ์์กด์ฑ ๊ทธ๋ํ๋ฅผ ๋ง๋ค์ด task๋ค์ ๋ณ๋ ฌ ์คํ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FGraphEventRef Task1 = FFunctionGraphTask::CreateAndDispatchWhenReady(
[]() { /* ๋
๋ฆฝ ์์
1 */ },
TStatId(),
nullptr,
ENamedThreads::AnyThread
);
FGraphEventRef Task2 = FFunctionGraphTask::CreateAndDispatchWhenReady(
[]() { /* ๋
๋ฆฝ ์์
2 */ },
TStatId(),
nullptr,
ENamedThreads::AnyThread
);
// Task3๋ Task1, Task2๊ฐ ๋๋์ผ ์์
FGraphEventArray Prereqs;
Prereqs.Add(Task1); Prereqs.Add(Task2);
FGraphEventRef Task3 = FFunctionGraphTask::CreateAndDispatchWhenReady(
[]() { /* ์์กด ์์
*/ },
TStatId(),
&Prereqs,
ENamedThreads::GameThread // ๊ฒฐ๊ณผ ํตํฉ์ GameThread์์
);
๊ฐ task๊ฐ ๋ ๋ฆฝ์ ์ด๊ฑฐ๋ ์์กด์ฑ์ด ๋ช ์์ ์ด๋ผ task ์์์๋ ๋ฝ์ด ๊ฑฐ์ ์์ต๋๋ค. ๊ฒฐ๊ณผ ํตํฉ์ GameThread์์ ํ๋ฏ๋ก race ์ฐจ๋จ.
14.4 FCriticalSection / FScopeLock โ ํ์ํ ๋๋ง
1
2
3
4
5
6
7
8
9
10
11
12
13
class FMyManager {
TArray<FMyData> Data;
FCriticalSection DataCS;
public:
void Add(const FMyData& Item) {
FScopeLock Lock(&DataCS);
Data.Add(Item);
}
FMyData Get(int Index) {
FScopeLock Lock(&DataCS);
return Data[Index]; // ๋ณต์ฌ ๋ฐํ โ ๋ฝ ํ๋ฆฐ ๋ค์ ์์
}
};
FCriticalSection์ ๋ด๋ถ์ ์ผ๋ก Windows CRITICAL_SECTION ๋๋ POSIX pthread_mutex_t. FScopeLock์ std::lock_guard ๋๋ฑ.
์ธ๋ฆฌ์ผ ์ฝ๋ ์ ์ฒด๋ฅผ grepํด๋ณด๋ฉด ๋ฝ ์ฌ์ฉ์ด ๋๋ผ์ธ ์ ๋๋ก ์ ์ต๋๋ค โ ๋ณดํต ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํธ์ถ์ด๋ ๋น๋๊ธฐ I/O ๊ฒฐ๊ณผ ํตํฉ์๋ง ๋ฑ์ฅ.
14.5 FThreadSafeCounter โ atomic ์นด์ดํฐ
1
2
3
4
5
6
class FMyClass {
FThreadSafeCounter Counter; // std::atomic<int32> ๋๋ฑ
public:
int32 Increment() { return Counter.Increment(); }
int32 GetValue() const { return Counter.GetValue(); }
};
๋ด๋ถ๋ InterlockedIncrement. std::atomic<int32>์ ๊ฑฐ์ ๊ฐ์ง๋ง ์ธ๋ฆฌ์ผ์ reflectionยทserialization ํธํ์ฑ์ ์ํด ์์ฒด ํ์
์ ๊ณต.
14.6 TQueue<T, EQueueMode::Spsc> โ Lock-free ํ
1
2
3
4
5
6
7
8
9
10
11
TQueue<FCommand, EQueueMode::Spsc> CommandQueue;
// Single Producer, Single Consumer
// Producer (GameThread)
CommandQueue.Enqueue(FCommand{ ... });
// Consumer (RenderThread)
FCommand Cmd;
while (CommandQueue.Dequeue(Cmd)) {
Process(Cmd);
}
SPSC๋ ๊ฐ์ฅ ๋จ์ํ lock-free ํ โ producer์ consumer๊ฐ ๊ฐ๊ฐ ํ ๋ช ๋ฟ์ด๋ฉด atomic head/tail๋ง์ผ๋ก ์์ .
MPSC(Multi Producer Single Consumer)๋ ์ง์ โ ์ฌ๋ฌ ์์ปค โ ๋ฉ์ธ ์ค๋ ๋ ๊ฒฐ๊ณผ ๋ชจ์ผ๊ธฐ ํจํด.
14.7 ๊ฒ์ํ๋ ์ด ์ฝ๋์์ ๋ณดํต race ์ ๋ง๋จ
๊ฒ์ํ๋ ์ด ํ๋ก๊ทธ๋๋จธ๊ฐ ๋ณดํต ๋ง์ฃผ์น๋ race๋:
- AsyncTask ๊ฒฐ๊ณผ๋ฅผ GameThread๋ก ๊ฐ์ ธ์ฌ ๋ โ
AsyncTask(ENamedThreads::GameThread, ...)์ฌ์ฉ - TaskGraph๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ โ ๊ฒฐ๊ณผ ํตํฉ
- Subsystem์์ worker ์ค๋ ๋ ์ฌ์ฉ ์
๋๋ถ๋ถ์ ์ ํจํด(๋ช ๋ น ํยทTaskGraphยทproxy ๋ณต์ฌ)์ผ๋ก ํด๊ฒฐ๋๊ณ , ์ง์ mutex๋ฅผ ๋ง์ ธ์ผ ํ๋ ๊ฒฝ์ฐ๋ ๊ฑฐ์ ์์ต๋๋ค โ ๊ทธ๊ฒ ์ธ๋ฆฌ์ผ์ ๋์์ฑ ๋ชจ๋ธ ๊ฐ์ .
14.8 ์ธ๋ฆฌ์ผ ๋์์ฑ ์ ๋ฆฌ ํ
| ์๋๋ฆฌ์ค | ๋ฉ์ปค๋์ฆ | ๋น๊ณ |
|---|---|---|
| GameThread โ RenderThread | ENQUEUE_RENDER_COMMAND + scene proxy | ๋ช ๋ น ํยท๋ฐ์ดํฐ ๋ณต์ฌ |
| GameThread โ RHIThread | RenderThread ๊ฒฝ์ | ์ง์ ์ ๋ง์ง |
| ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ | TaskGraph / AsyncTask | ์์กด์ฑ ๊ทธ๋ํ |
| ๋น๋๊ธฐ ๊ฒฐ๊ณผ ํตํฉ | AsyncTask(ENamedThreads::GameThread) | ๊ฒฐ๊ณผ๋ GameThread์์ |
| ๊ณต์ ์นด์ดํฐ | FThreadSafeCounter | atomic |
| ๊ณต์ ์ปจํ ์ด๋ | FCriticalSection + FScopeLock | ๋๋ฌผ๊ฒ |
| Producer-Consumer | TQueue<T, EQueueMode::Spsc/Mpsc> | lock-free |
15. ๊ผฌ๋ฆฌ์ง๋ฌธ ์์ ๊ฒฝ๋ก
Q1. โRace Condition์ ๋ํด์ ์ด์ผ๊ธฐ ํด์ฃผ์ธ์.โ
Race Condition์ ๋ ์ด์์ ์คํ ๋จ์๊ฐ ๊ณต์ ์์์ ๋์ ์ ๊ทผํ ๋, ์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋น๊ฒฐ์ ์ ํ์์ ๋๋ค. ๋ฐ์ ์กฐ๊ฑด์ ๋ค ๊ฐ์ง โ ๊ณต์ ์์์ด ์กด์ฌํ๊ณ , ๋ ์ด์์ด ๋์ ์ ๊ทผํ๋ฉฐ, ๊ทธ์ค ์ ์ด๋ ํ๋๊ฐ ์ฐ๊ธฐ๋ฅผ ์ํํ๊ณ , OS๊ฐ ์ ๊ทผ ์์๋ฅผ ๋ณด์ฅํ์ง ์์ต๋๋ค. ์ด ๋ค ์กฐ๊ฑด์ด ๋ชจ๋ ๋ง์กฑ๋์ด์ผ race๊ฐ ์ผ์ด๋๊ณ , ์ด๋ ํ๋๋ง ๊นจ๋ race๋ ์ฌ๋ผ์ง๋๋ค.
๊ฐ์ฅ ๋จ์ํ ์๊ฐ
count++์ ๋๋ค. ์ด ํ ์ค์ด CPU ๋ช ๋ น์ผ๋ก๋ load, increment, store ์ธ ๋จ๊ณ๋ก ๋ถํด๋๊ณ , ๊ทธ ์ฌ์ด ์ด๋์๋ ์ปจํ ์คํธ ์ค์์นญ(21)์ด ์ผ์ด๋ ์ ์์ต๋๋ค. ๋ ์ค๋ ๋๊ฐ 100์์ ์์ํด ๊ฐ์ +1์ ํ ๊ฒฐ๊ณผ๊ฐ 102๊ฐ ์๋๋ผ 101์ด ๋ ์ ์๋ ๊ฒ ์ด race์ ๋ณธ์ง์ ๋๋ค.ํด๊ฒฐ์ ์ถ์ ๊ฐ๋ ์ Critical Section(์๊ณ ์์ญ) โ ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐ์ ๋๋ค. ์ด๊ฑธ ๋ณดํธํ๋ ๋๊ตฌ๊ฐ ๋๊ธฐํ ๊ฐ์ฒด(mutexยทsemaphoreยทatomic ๋ฑ)์ด๊ณ , ๋น์ฉ ์์๋ atomic CAS(์ ns) < ์ฌ์ฉ์ ๋ชจ๋ ๋ฝ(์์ญ ns~1 ฮผs) < ์ปค๋ ๊ฐ์ฒด ๋ฝ(1~3 ฮผs) ์์ ๋๋ค. ๋ ๋์๊ฐ lock-free ์๋ฃ๊ตฌ์กฐ๋ก ๋ฝ ์์ฒด๋ฅผ ํํผํ๊ธฐ๋ ํ์ง๋ง, ABA ๋ฌธ์ ยทmemory reclamation ๊ฐ์ ์ถ๊ฐ ํจ์ ์ด ์์ด ์ง์ ๋ง๋ค๊ธฐ ๋งค์ฐ ์ด๋ ต์ต๋๋ค.
Q2. โCritical Section์ด ๋ฌด์์ธ๊ฐ์?โ
Critical Section(์๊ณ ์์ญ)์ ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐ์ ๊ฐ๋ฆฌํค๋ ์ถ์ ๊ฐ๋ ์ ๋๋ค. ์์์ด ์๋๋ผ ์ฝ๋ ๊ตฌ๊ฐ์ ๊ฐ๋ ์ด๋ผ๋ ์ ์ด ํต์ฌ์ ๋๋ค. ๊ฐ์ ์์์ด๋ผ๋ ๋ณดํธ๊ฐ ํ์ํ ๊ตฌ๊ฐ์ด ์๊ณ ํ์ ์๋ ๊ตฌ๊ฐ์ด ์์ต๋๋ค.
์ข์ critical section์ ๋ค ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํฉ๋๋ค โ mutual exclusion(ํ ์ค๋ ๋๋ง ์ง์ ), progress(๋น์ด ์์ผ๋ฉด ๋๊ตฐ๊ฐ ๋ค์ด๊ฐ), bounded waiting(๋ฌดํ ๋๊ธฐ ์์), no starvation(๋ชจ๋๊ฐ ๊ฒฐ๊ตญ ๋ค์ด๊ฐ). ์ด ๋ค ๊ฐ์ง๊ฐ DijkstraยทHoare๊ฐ ์ ๋ฆฌํ ๊ณ ์ ์ ์ ์์ ๋๋ค.
์ค๋ฌด์์ critical section์ ํฌ๊ธฐ๋ฅผ ์ต์ํํ๋ ๊ฒ ํต์ฌ์ ๋๋ค โ ๋ฝ ์์์ ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ํ๋ฉด ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋ชจ๋ ๊ธฐ๋ค๋ฆฌ๋ฉฐ lock contention(๋ฝ ๊ฒฝํฉ)์ด ํญ์ฆํด ์ปจํ ์คํธ ์ค์์นญ(21)์ด ๋์ ๋ฉ๋๋ค. ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ๋ฝ ๋ฐ์์ ํ๊ณ , ๋ฝ ์์์๋ ๊ณต์ ์์ ๊ฐฑ์ ๋ง ํฉ๋๋ค.
Windows์์๋
CRITICAL_SECTIONAPI(๊ฐ์ ํ๋ก์ธ์ค ํ์ , ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ )๊ฐ ๊ฐ์ฅ ๊ฐ๋ณ๊ณ , ์ปค๋Mutex(CreateMutex)๋ ํ๋ก์ธ์ค ๊ฐ ๊ณต์ ๊ฐ๋ฅํ์ง๋ง ๋ ๋น์๋๋ค. C++ ํ์ค์์๋std::mutex+std::lock_guardRAII ํจํด์ด ํ์ค์ ๋๋ค.
Q3. โstd::mutex์ std::atomic ์ค ๋ฌด์์ ์จ์ผ ํ๋์?โ
์๋ฃ๊ตฌ์กฐ์ ๋ณต์ก๋์ ๊ฒฝํฉ๋ฅ ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค.
std::atomic์ ๋จ์ผ ๊ฐ์ ๋ํ read-modify-write๊ฐ ๋จ์ผ ๋ช ๋ น์ผ๋ก ๋๋ ๋ ์ ํฉํฉ๋๋ค. ๋น์ฉ์ ์ ns๋ก ๊ทนํ ์ ๋ ดํฉ๋๋ค. ์นด์ดํฐ, ํ๋๊ทธ, ๋จ์ผ ํฌ์ธํฐ(scene ๊ฐฑ์ ์ ํธ) ๊ฐ์ ํจํด์ด ์ ํ์ ์ ๋๋ค. ๋จ์ ์ ๋ณตํฉ ์ฐ์ฐ์ด ์ ๋๋ค๋ ๊ฒ โcount++๋ atomic์ด์ง๋งif (count > 10) count = 0;๋ atomic์ด ์๋๋๋ค.
std::mutex๋ ๋ณตํฉ ์ฐ์ฐ์ ์ ํฉํฉ๋๋ค. ์ฌ๋ฌ ๋ณ์๋ฅผ ์ผ๊ด๋๊ฒ ๊ฐฑ์ ํ๊ฑฐ๋, ์๋ฃ๊ตฌ์กฐ(mapยทlistยทvector)๋ฅผ ์์ ํ๋ ๊ฒฝ์ฐ. ๋น์ฉ์ ๊ฒฝํฉ ์์ ๋ ์์ญ ns๋ก atomic๋ณด๋ค ์ฝ๊ฐ ๋น์ธ์ง๋ง, ๊ฒฝํฉ ์ 1~3 ฮผs๋ก ๋น์ฉ์ด ๊ธ์ฆํฉ๋๋ค.์ ํ ๊ธฐ์ค:
- ๋จ์ผ ๊ฐ + ๋จ์ ์ฐ์ฐ โ atomic (counter, ref count, flag)
- ๋ณตํฉ ์ฐ์ฐ ๋๋ ์ฌ๋ฌ ๋ณ์ โ mutex
- ์ฝ๊ธฐ ๋ง๊ณ ์ฐ๊ธฐ ์ ์ โ
std::shared_mutex(R/W lock)- ๋งค์ฐ ์งง์ critical section + ๋ฉํฐ์ฝ์ด โ spin lock (atomic CAS๋ก ์ง์ ๊ตฌํ)
- ๊ณ ๊ธ โ lock-free ์๋ฃ๊ตฌ์กฐ โ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Boost.Lockfree)
ํ ๊ฐ์ง ์ฃผ์ โ atomic์ ๋๋ฌด ์๊ฒ ์ฐ๋ฉด ์คํ๋ ค ๋๋ ค์ง ์ ์์ต๋๋ค. ๋งค atomic ์ฐ์ฐ์ด ์บ์ ์ผ๊ด์ฑ ํธ๋ํฝ์ ๋ฐ์์ํค๋ฏ๋ก, ์ฝ์ด๊ฐ ๋ง์์๋ก ๋น์ฉ์ด ๋์ด๋ฉ๋๋ค. ๊ทธ๋์ hot path์ atomic์ ์ ์คํ๊ฒ.
Q4. โDeadlock์ ์ด๋ป๊ฒ ๋ง์ ์ ์๋์?โ
Deadlock์ Coffman 4์กฐ๊ฑด(์ํธ๋ฐฐ์ ยท์ ์ ์ ๋๊ธฐยท๋น์ ์ ยท์ํ ๋๊ธฐ)์ด ๋ชจ๋ ๋ง์กฑ๋ ๋ ๋ฐ์ํฉ๋๋ค. ๋ค ์กฐ๊ฑด ์ค ํ๋๋ง ๊นจ๋ deadlock์ ๋ถ๊ฐ๋ฅํด์ง๋๋ค.
๊ฐ์ฅ ์ค์ฉ์ ์ธ ํํผ ์ ๋ต์ ๋ ๊ฐ์ง์ ๋๋ค.
์ฒซ์งธ, lock ordering(๋ฝ ์์ ๊ณ ์ ) โ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ์์๋ก ๋ฝ์ ์ก์ผ๋ฉด ์ํ ๋๊ธฐ๊ฐ ์ ์๊น๋๋ค. ์ค๋ฌด์์ ๊ฐ์ฅ ํํ ํจํด. ๋ฝ์ ID(์: ๊ฐ์ฒด ์ฃผ์)๋ฅผ ๋ถ์ฌํ๊ณ ํญ์ ID ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๊ธ๋๋ค.
๋์งธ,
std::scoped_lockโ C++17์std::scoped_lock์ ์ฌ๋ฌ mutex๋ฅผ deadlock-free ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋์์ ์ ๊ธ๋๋ค. ๋ด๋ถ์ ์ผ๋ก try-lock ์๋ + back-off ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋์ํฉ๋๋ค. ๋ฝ ์์๋ฅผ ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ฉ๋๋ค.
1 2 3 4 5 void Transfer(Account& a, Account& b, int amount) { std::scoped_lock lock(a.mtx, b.mtx); // deadlock-free a.balance -= amount; b.balance += amount; }๊ทธ ์ธ ์ ๋ต์ผ๋ก timeout(
try_lock_for)์ ์จ์ ์ผ์ ์๊ฐ ํ ํฌ๊ธฐ, lock-free ์๋ฃ๊ตฌ์กฐ๋ก ๋ฝ ์์ฒด๋ฅผ ํํผ, ๊ทธ๋ฆฌ๊ณ ์ ์ ๋ถ์ ๋๊ตฌ(Visual Studio Concurrency Visualizer, ThreadSanitizer, Helgrind)๋ก deadlock ๊ฐ๋ฅ์ฑ ์ฌ์ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
Q5. โLock-free์ wait-free์ ์ฐจ์ด๋ ๋ฌด์์ธ๊ฐ์?โ
๋ ๋ค ๋ฝ์ ์ฌ์ฉํ์ง ์๋ ๋๊ธฐํ ๊ธฐ๋ฒ์ด์ง๋ง ์งํ ๋ณด์ฅ ๊ฐ๋๊ฐ ๋ค๋ฆ ๋๋ค.
Lock-free๋ ์์คํ ์ ์ฒด๋ก ๋ดค์ ๋ ์ ์ด๋ ํ ์ค๋ ๋๋ finite step ์์ ์งํ๋๋ค๋ ๋ณด์ฅ์ ๋๋ค. ํ ์ค๋ ๋๊ฐ ๋๋ ค์ ธ๋ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋งํ์ง ์์ต๋๋ค. ์ฆ deadlockยทlivelock์ด ๋ถ๊ฐ๋ฅ. ๊ทธ๋ฌ๋ ๊ฐ๋ณ ์ค๋ ๋๊ฐ starvation ๋นํ ์๋ ์์ต๋๋ค.
Wait-free๋ ๋ ๊ฐํ ๋ณด์ฅ์ผ๋ก ๋ชจ๋ ๊ฐ๋ณ ์ค๋ ๋๊ฐ finite step ์์ ์งํ๋๋ค๋ ์๋ฏธ์ ๋๋ค. ์ด๋ค ์ค๋ ๋๋ starvation ๋นํ์ง ์์ต๋๋ค.
๋๋ถ๋ถ์ ์ค์ฉ์ lock-free ์๋ฃ๊ตฌ์กฐ๋ wait-free๊น์ง๋ ๋ชป ๊ฐ๊ณ lock-free์ ๋จธ๋ญ ๋๋ค โ wait-free๋ ์ค๊ณ๊ฐ ํจ์ฌ ์ด๋ ต๊ณ ๋น์ฉ๋ ํฝ๋๋ค.
std::atomic์fetch_add๊ฐ์ ๋จ์ผ ์ฐ์ฐ์ wait-free์ง๋ง, lock-free ์คํ์ pop์ CAS ๋ฃจํ๋ผ lock-free์ผ ๋ฟ wait-free๋ ์๋๋๋ค(๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ณ์ pushํ๋ฉด ํ ์ค๋ ๋์ pop์ด ๊ณ์ ์ฌ์๋๋ ์ ์์).์งํ ๋ณด์ฅ ๊ฐ๋ ์์: blocking < obstruction-free < lock-free < wait-free.
Q6. โABA ๋ฌธ์ ๊ฐ ๋ฌด์์ธ๊ฐ์?โ
ABA ๋ฌธ์ ๋ lock-free ์๋ฃ๊ตฌ์กฐ์์ CAS(Compare-And-Swap)๊ฐ ๋ณํ๋ฅผ ๋์น๋ ํจ์ ์ ๋๋ค. ๊ฐ์ด A์์ B๋ก ๋ฐ๋์๋ค๊ฐ ๋ค์ A๋ก ๋์์์ ๋, CAS๋ ๊ฐ(์ฃผ์)๋ง ๋น๊ตํ๋ฏ๋ก โ๋ณํ ์์โ์ผ๋ก ํ๋จํฉ๋๋ค.
๊ฐ์ฅ ํํ ์๋๋ฆฌ์ค๋ lock-free ์คํ์ pop์ ๋๋ค. ์ค๋ ๋ 1์ด head=A๋ฅผ ๋ณด๊ณ CAS๋ก head๋ฅผ A->next๋ก ๋ฐ๊พธ๋ ค๋ ์ฌ์ด, ์ค๋ ๋ 2๊ฐ A๋ฅผ popํ๊ณ freeํ ๋ค ์ ๋ ธ๋๋ฅผ pushํ๋๋ฐ ์ฐ์ฐํ ๊ฐ์ ์ฃผ์๋ฅผ ๋ฐ์ head๊ฐ ๋ค์ A๋ฅผ ๊ฐ๋ฆฌํต๋๋ค. ์ค๋ ๋ 1์ CAS๋ head==A๋ก ๋ณด๊ณ ์ฑ๊ณตํ์ง๋ง, ์ค์ ๋ก๋ free๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๋ ๋๊ธ๋ง ํฌ์ธํฐ(10)๊ฐ ๋ฉ๋๋ค.
ํํผ ๊ธฐ๋ฒ์ ๋ค ๊ฐ์ง๊ฐ ์์ต๋๋ค:
- Tagged Pointer โ ํฌ์ธํฐ์ ๋ฒ์ ์นด์ดํฐ๋ฅผ ๋ถ์ฌ ๋งค ์์ ๋ง๋ค +1. x86์
LOCK CMPXCHG16B๋ก double-word CAS.- Hazard Pointer โ ๊ฐ ์ค๋ ๋๊ฐ โํ์ฌ ๋ณด๊ณ ์๋ ํฌ์ธํฐโ๋ฅผ ๋ฑ๋ก. ๋ฉ๋ชจ๋ฆฌ ํ์ ์ ๋ค๋ฅธ ์ค๋ ๋์ hazard pointer๋ฅผ ๋ชจ๋ ๊ฒ์ฌ.
- Epoch-based Reclamation โ ์ ์ญ epoch์ ์ถ์ ํด ๋ชจ๋ ์ค๋ ๋๊ฐ ๋ค์ epoch์ผ๋ก ๋์ด๊ฐ ๋ค์๋ง ๋ฉ๋ชจ๋ฆฌ ํ์. Linux RCU์ ์๋ฆฌ.
- Garbage Collection โ JavaยทC#์ฒ๋ผ GC๊ฐ ์๋ ์ธ์ด์์ ์๋์ผ๋ก ํด๊ฒฐ.
C++์ GC๊ฐ ์์ด ์ ์ธ ๊ฐ์ง๋ฅผ ์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค. ๊ทธ๋์ lock-free ์๋ฃ๊ตฌ์กฐ ์ค๊ณ๊ฐ ๋งค์ฐ ์ด๋ ต๊ณ , ์ค๋ฌด์์๋ Boost.Lockfree๋ folly ๊ฐ์ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋๋ค.
Q7. โMemory Barrier๊ฐ ์ ํ์ํ๊ฐ์?โ
ํ๋ CPU์ ์ปดํ์ผ๋ฌ๋ ์ฑ๋ฅ์ ์ํด ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ์์๋ฅผ ์ฌ๋ฐฐ์นํฉ๋๋ค. ๋จ์ผ ์ค๋ ๋ ์๋ฏธ๋ ๋ณด์กดํ์ง๋ง, ๋ฉํฐ์ค๋ ๋์์๋ ํ ์ค๋ ๋์ ์ฐ๊ธฐ ์์๊ฐ ๋ค๋ฅธ ์ค๋ ๋์์ ๋ค๋ฅด๊ฒ ๋ณด์ผ ์ ์์ต๋๋ค.
๋ํ ์์:
1 2 3 4 5 6 // ์ค๋ ๋ A data = 42; ready = true; // ์ค๋ ๋ B if (ready) use(data); // โ data๊ฐ 42๊ฐ ์๋ ์ ์์!A์ ์ปดํ์ผ๋ฌ๋ CPU๊ฐ ๋ ์ฐ๊ธฐ๋ฅผ ์ฌ๋ฐฐ์นํด์
ready = true๊ฐ ๋จผ์ ๋ณด์ผ ์ ์๊ณ , B๋ready==true์ธ๋ฐdata๋ ์์ง 42๊ฐ ์๋ ์ํ๋ฅผ ๋ด ๋๋ค.Memory Barrier(๋๋ fence) ๋ ์ด ์ฌ๋ฐฐ์น๋ฅผ ๋ง๋ ๋ช ๋ น์ ๋๋ค. C++์์๋
std::atomic์memory_order๋ฅผ ๋ช ์ํด ์ฌ์ฉํฉ๋๋ค โmemory_order_release๋ store์ ๋ถ์ฌ โ์ด์ ์ฐ๊ธฐ๊ฐ ๋ค๋ก ์ ๊ฐโ,memory_order_acquire๋ load์ ๋ถ์ฌ โ์ดํ ์ฝ๊ธฐ๊ฐ ์์ผ๋ก ์ ์ดโ์ ๋ณด์ฅํฉ๋๋ค.
1 2 3 4 5 6 7 // ์์ ๋ฒ์ data = 42; ready.store(true, std::memory_order_release); // ์ค๋ ๋ B while (!ready.load(std::memory_order_acquire)); use(data); // 42 ๋ณด์ฅrelease-acquire ํ์ด๊ฐ ๋ ์ค๋ ๋ ๊ฐ happens-before ๊ด๊ณ๋ฅผ ๋ง๋ค์ด release ์ด์ ์ ๋ชจ๋ ์ฐ๊ธฐ๊ฐ acquire ์ดํ์ ๋ชจ๋ ์ฝ๊ธฐ์์ ๋ณด์ ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ์ ์ํคํ ์ฒ๋ง๋ค ๋ค๋ฆ ๋๋ค โ x86์ TSO๋ผ ๋น๊ต์ ๊ฐํ ๋ชจ๋ธ(storeโload๋ง ์ฌ๋ฐฐ์น)์ด๋ผ์ ๋ฌด์์์ ์ผ๋ก ์์ฑํ ์ฝ๋๊ฐ x86์์ ์ ๋ ์ ์์ง๋ง, ARM์ weakํด์ ๋ชจ๋ ์ฌ๋ฐฐ์น ํ์ฉ โ ๊ฐ์ ์ฝ๋๊ฐ ARM์์ race๋ก ๊นจ์ง ์ ์์ต๋๋ค. Cross-platform ์ฝ๋๋ ๋ช ์์ memory order๊ฐ ํ์.
Q8. โPriority Inversion์ด ๋ฌด์์ธ๊ฐ์?โ
Priority Inversion์ ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์ก๊ณ ์๋ ๋ฝ์ ๋์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๊ธฐ๋ค๋ฆฌ๋๋ฐ, ์ค๊ฐ ์ฐ์ ์์ ์ค๋ ๋๊ฐ CPU๋ฅผ ์ฐจ์งํด ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์งํ ๋ชป ํ๋ ์ํฉ์ ๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ๋์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ์ค๊ฐ ์ฐ์ ์์์ ์ํด ์ ์ฒด๋๋ ์ญ์ .
๊ฐ์ฅ ์ ๋ช ํ ์ฌ๋ก๊ฐ 1997๋ NASA Pathfinder ํ์ฑ ํ์ฌ์ ์ ๋๋ค. ๊ธฐ์ ๋ฐ์ดํฐ ์ค๋ ๋(low)๊ฐ IPC ํ์ mutex๋ฅผ ์ก๊ณ ์๋ ๋์ ํต์ ์ค๋ ๋(medium)์ ์ ์ ๋๊ณ , ๊ทธ ์ฌ์ด bus management ์ค๋ ๋(high)๊ฐ IPC mutex๋ฅผ ๊ธฐ๋ค๋ ธ์ต๋๋ค. Watchdog timer๊ฐ bus management ๋ฉ์ถค์ ๊ฐ์งํ๋ฉด ์์คํ ์ด ์ฌ๋ถํ . VxWorks OS์ Priority Inheritance ๊ธฐ๋ฅ์ ์๊ฒฉ์ผ๋ก ํ์ฑํํ์ ํด๊ฒฐ๋์ต๋๋ค.
ํด๊ฒฐ์ฑ ๋ ๊ฐ์ง:
- Priority Inheritance(PI) โ ๋ฎ์ ์ฐ์ ์์ ์ค๋ ๋๊ฐ ๋ฝ์ ์ก์ ๋์, ๊ทธ ๋ฝ์ ๊ธฐ๋ค๋ฆฌ๋ ๊ฐ์ฅ ๋์ ์ฐ์ ์์๋ก ์ผ์ ์์น. ๋ฝ ํด์ ์ ์๋ ์ฐ์ ์์๋ก ๋ณต๊ท. POSIX๋
pthread_mutexattr_setprotocol(PTHREAD_PRIO_INHERIT)๋ก ํ์ฑํ.- Priority Ceiling Protocol(PCP) โ ๊ฐ ์์์ ์ต๊ณ ์ฐ์ ์์(ceiling)๋ฅผ ๋ฏธ๋ฆฌ ๋ถ์ฌ. ๋ฝ ์ก๋ ์๊ฐ ceiling์ผ๋ก ์์น. ์ ์ ์ด๊ณ ๋ถ์์ด ์ฌ์ ์ค์๊ฐ ์์คํ ์ ์ ํธ.
์ผ๋ฐ ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ฑฐ์ ๋ณด์ด์ง ์์ง๋ง, ์ค์๊ฐ ์์คํ (์ฐจ๋ ์ ์ด, ์๋ฃ ๊ธฐ๊ธฐ, ํญ๊ณต์ฐ์ฃผ) ์์๋ PI/PCP๊ฐ ํ์์ ๋๋ค.
Q9. โ์ธ๋ฆฌ์ผ์ race condition์ ์ด๋ป๊ฒ ํํผํ๋์?โ
์ธ๋ฆฌ์ผ์ ๋์์ฑ ์ฒ ํ์ โ๋ฝ์ ์ ๊ฑฐ๋โ ๊ฒ ์๋๋ผ โ๊ณต์ ๋ฅผ ์ ๋ง๋๋โ ๊ฒ์ ๋๋ค. ์ปจํ ์คํธ ์ค์์นญ(21)์์ ๋ณธ ๊ฒ์ฒ๋ผ ์ค๋ ๋๋ฅผ ๋ช ํํ ๋ถ๋ฆฌํฉ๋๋ค โ
GameThread(UObjectยทTick),RenderThread(๋ ๋ ๋ช ๋ น),RHIThread(GPU ๋๋ผ์ด๋ฒ ํธ์ถ).ํต์ฌ ๊ธฐ๋ฒ ์ :
์ฒซ์งธ, ๋ช ๋ น ํ๋ก ๋ฐ์ดํฐ ์ ๋ฌ โ
ENQUEUE_RENDER_COMMAND๋งคํฌ๋ก๋ GameThread์์ RenderThread๋ก ๋๋ค๋ฅผ ๋ณด๋ด๋ ๋๊ตฌ์ ๋๋ค. ๋๋ค๊ฐ ์บก์ฒํ ๊ฐ์ด ๋ช ๋ น ํ์ ๋ณต์ฌ๋๋ฏ๋ก ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ง ์ผ์ด ์์ต๋๋ค. ๋ฐ์ 4์กฐ๊ฑด ์ค โโก ๋์ ์ ๊ทผโ์ ๊ตฌ์กฐ์ ์ผ๋ก ์ฐจ๋จ.๋์งธ, scene proxy ํจํด โ GameThread๋ UObject ์์ฒด๋ฅผ, RenderThread๋ GameThread๊ฐ ๋งค ํ๋ ์ ์ ๋ฆฌํด ๋๊ธด ๋ถ๋ณ proxy๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ ์ค๋ ๋๊ฐ ๋์์ ๋ง์ง๋ ์ผ์ด ์์ฒ์ ์ผ๋ก ์์ต๋๋ค.
์ ์งธ, TaskGraph โ ํฐ ์์ ์ task๋ก ์ชผ๊ฐ๊ณ ์์กด์ฑ ๊ทธ๋ํ๋ฅผ ๋ง๋ค์ด ๋ณ๋ ฌ ์คํ. task ๋ด๋ถ๋ ๋ ๋ฆฝ์ด๊ฑฐ๋ ๋ช ์์ ์์กด๋ง ์์ด ๋ฝ ๊ฑฐ์ ์์. ๊ฒฐ๊ณผ๋
AsyncTask(ENamedThreads::GameThread)๋ก GameThread์์ ํตํฉ.๊ทธ๋๋ ๋ฝ์ด ํ์ํ ๋๋
FCriticalSection+FScopeLock(RAII ๊ฐ๋), atomic ์นด์ดํฐ๋กFThreadSafeCounter, lock-free ํ๋กTQueue<T, EQueueMode::Spsc/Mpsc>๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ค๋ง ์ธ๋ฆฌ์ผ ์ฝ๋์์ ๋ฝ ์ฌ์ฉ ๋น๋๋ ๋๋ผ์ธ ์ ๋๋ก ๋ฎ์ต๋๋ค โ ๋์์ฑ ๋ชจ๋ธ ์์ฒด๊ฐ race๋ฅผ ์ ๋ง๋๋ ๋ฐฉํฅ์ผ๋ก ์ค๊ณ๋๊ธฐ ๋๋ฌธ.
Q10. โSpin Lock๊ณผ Mutex ์ค ๋ฌด์์ด ๋ ์ข๋์?โ
์ํฉ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค.
Spin Lock์ ๋ฝ์ด ํ๋ฆด ๋๊น์ง busy-wait(while loop)ํ๋ ๋ฝ์ ๋๋ค. ์ปจํ ์คํธ ์ค์์นญ(21) ์์ด atomic CAS๋ง์ผ๋ก ๋๊ธฐํ. ๋ฝ ์กํ ๋๊น์ง CPU๋ฅผ ์ฐ๋ฉด์ ๋๊ธฐ.
Mutex๋ ๋ฝ์ ๋ชป ์ก์ผ๋ฉด ์ค๋ ๋๋ฅผ sleep์์ผ ๋ค๋ฅธ ์ค๋ ๋์ CPU๋ฅผ ์๋ณดํฉ๋๋ค. ๊นจ์ด๋ ๋ ์ปจํ ์คํธ ์ค์์น ๋ฐ์.
๋น๊ต:
์กฐ๊ฑด Spin Lock ์ ๋ฆฌ Mutex ์ ๋ฆฌ Critical section ๊ธธ์ด ๋งค์ฐ ์งง์ (< 1 ฮผs) ๊ธธ์ (> 10 ฮผs) CPU ์ฝ์ด ์ ๋ฉํฐ์ฝ์ด ๋จ์ผ ์ฝ์ด โ spin์ด ์ฃฝ์ ๊ฒฝํฉ๋ฅ ๋ฎ์ ๋์ ๋ฝ ์กํ ์ค๋ ๋๊ฐ ์งํ ๊ฐ๋ฅ? YES (๋ค๋ฅธ ์ฝ์ด) (sleep์ ์ด์ฐจํผ ์๋ณด) ๋จ์ผ ์ฝ์ด์์ spin lock์ ๊ฑฐ์ ํญ์ ์ํด์ ๋๋ค โ ๋ฝ์ ์ก์ ์ค๋ ๋์ ๊ธฐ๋ค๋ฆฌ๋ ์ค๋ ๋๊ฐ ๊ฐ์ CPU๋ฅผ ๊ณต์ ํ๋ฏ๋ก, ๊ธฐ๋ค๋ฆฌ๋ ์ค๋ ๋์ spin์ด ๋ฝ์ ์ก์ ์ค๋ ๋์ ์งํ์ ๋ฐฉํดํฉ๋๋ค.
๋ฉํฐ์ฝ์ด์์ ์งง์ critical section์ด๋ฉด spin์ด mutex๋ณด๋ค ๋น ๋ฆ ๋๋ค โ ์ปจํ ์คํธ ์ค์์น ๋น์ฉ(1~3 ฮผs)๋ณด๋ค spin ๋น์ฉ(์์ญ ns)์ด ์๊ธฐ ๋๋ฌธ.
์ค๋ฌด์์๋ ๋ณดํต Windows
CRITICAL_SECTION์ spin count ๊ธฐ๋ฅ์ ์๋๋ค โ ์ฒ์์๋ spin์ ์๋ํ๊ณ , ์ผ์ ํ์ ์์ ๋ชป ์ก์ผ๋ฉด ์ปค๋ ์ง์ (mutex ๋์). ๋ ๋ฐฉ์์ ์ฅ์ ์ ๊ฒฐํฉ.InitializeCriticalSectionAndSpinCount(&cs, 4000)์ด ๊ทธ API.
16. ํต์ฌ ์์ฝ ์นด๋ (์ฌ๊ฒ์ฌ)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Race Condition = ๋ ์ด์์ ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ๋์ ์ ๊ทผ,
์คํ ์์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ ๋น๊ฒฐ์ ์ ํ์.
๋ฐ์ 4์กฐ๊ฑด:
โ ๊ณต์ ์์ ์กด์ฌ
โก ๋ ์ด์์ด ๋์ ์ ๊ทผ
โข ์ ์ด๋ ํ๋๋ ์ฐ๊ธฐ (write)
โฃ ์์๋ฅผ OS๊ฐ ๋ณด์ฅํ์ง ์์
โ
๋ค ์กฐ๊ฑด ์ค ํ๋๋ง ๊นจ๋ race ์์
โ ๋น์ฉ ์: โ > โก > โข > โฃ
โ ์ธ๋ฆฌ์ผ์ โก๋ฅผ ๊นธ (GameThread ๋จ์ผํ)
โ const ๋ฐ์ดํฐ๋ โข์ ๊นธ
โ mutex๋ โฃ๋ฅผ ๊นธ
Critical Section = ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์คํ๋์ด์ผ ํ๋ ์ฝ๋ ๊ตฌ๊ฐ
์์ฑ: mutual exclusion / progress / bounded waiting / no starvation
ํฌ๊ธฐ ์ต์ํ โ ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ๋ฝ ๋ฐ์์
๋๊ธฐํ ๊ฐ์ฒด ๋น์ฉ ์คํํธ๋ผ (๋ฎ์ โ ๋์):
std::atomic load/store 1~์ ns
std::atomic CAS 5~20 ns
CRITICAL_SECTION (no ๊ฒฝํฉ) ์์ญ ns
std::mutex (no ๊ฒฝํฉ) ์์ญ ns
SRWLOCK ์ฝ๊ธฐ (no ๊ฒฝํฉ) ์์ญ ns
CRITICAL_SECTION (๊ฒฝํฉ) 1~3 ฮผs (์ปจํ
์คํธ ์ค์์น)
std::mutex (๊ฒฝํฉ) 1~3 ฮผs
Mutex ์ปค๋ ๊ฐ์ฒด 1~3 ฮผs (ํญ์ syscall)
Semaphore / Event / CV 1~3 ฮผs
Lock-free:
CAS = Compare-And-Swap (LOCK CMPXCHG)
weak vs strong (spurious failure ํ์ฉ)
ABA ๋ฌธ์ โ AโBโA ๋ชป ์์์ฑ
ํํผ: tagged pointer / hazard pointer / epoch reclamation / GC
์งํ ๋ณด์ฅ: blocking < obstruction-free < lock-free < wait-free
Memory Model (๋ช
๋ น ์ฌ๋ฐฐ์น + ์บ์ ์ผ๊ด์ฑ):
memory_order_relaxed โ ์์ X
memory_order_acquire โ load์, ์ดํ ์ ์๋น๊น
memory_order_release โ store์, ์ด์ ์ ๋ค๋ก ๋ฏธ๋ฃธ
memory_order_acq_rel โ RMW
memory_order_seq_cst โ ๊ฐ์ฅ ๊ฐํจ (๊ธฐ๋ณธ)
acquire-release ํ์ด โ happens-before ์ฑ๋ฆฝ
x86 TSO (๊ฐํจ) vs ARM weak (barrier ํ์)
๋ณ๋ฆฌ:
Deadlock = ์๋ก์ ๋ฝ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๋ฉ์ถค
4์กฐ๊ฑด: ์ํธ๋ฐฐ์ ยท์ ์ ๋๊ธฐยท๋น์ ์ ยท์ํ๋๊ธฐ
ํํผ: lock ordering / std::scoped_lock / timeout
Livelock = ๋ฝ ์ ์ก๊ณ ์๋ณด๋ง โ random back-off๋ก ํด๊ฒฐ
Starvation = ํน์ ์ค๋ ๋ ์์ํ ๋ชป ์ก์ โ ๊ณต์ ์ฑ ์ ์ฑ
Priority Inv = ๋ฎ์ ์ฐ์ ์์ ๋ฝ์ ๋์ ์ฐ์ ์์๊ฐ ๊ธฐ๋ค๋ฆผ
ํด๊ฒฐ: Priority Inheritance / PCP
์ฌ๋ก: NASA Pathfinder (1997)
Windows API:
CRITICAL_SECTION ์ฌ์ฉ์ ๋ชจ๋ ์ฐ์ , ๊ฐ์ ํ๋ก์ธ์ค
SRWLOCK R/W ๋ถ๋ฆฌ, ๊ฐ๋ฒผ์
Mutex (CreateMutex) ์ปค๋ ๊ฐ์ฒด, ํ๋ก์ธ์ค ๊ฐ
Semaphore / Event ์ปค๋ ๊ฐ์ฒด
CONDITION_VARIABLE ์กฐ๊ฑด ๋๊ธฐ
InterlockedExchange atomic ํจ์๊ตฐ
POSIX API:
pthread_mutex_t (recursive/errorcheck/robust)
pthread_rwlock_t R/W
pthread_cond_t Condition Variable
sem_t named/unnamed semaphore
์ธ๋ฆฌ์ผ ๋์์ฑ:
์ฒ ํ โ "๊ณต์ ๋ฅผ ์ ๋ง๋ ๋ค"
GameThread (UObject) / RenderThread (proxy) / RHIThread (GPU) ๋ถ๋ฆฌ
ENQUEUE_RENDER_COMMAND โ ๋ช
๋ น ํ๋ก ๋ฐ์ดํฐ ์ ๋ฌ
TaskGraph โ ์์กด์ฑ ๊ทธ๋ํ
FCriticalSection + FScopeLock (๋๋ฌพ)
FThreadSafeCounter (atomic)
TQueue<T, EQueueMode::Spsc/Mpsc> (lock-free)
์ ํ ๊ฐ์ด๋:
๋จ์ผ ๊ฐ + ๋จ์ ์ฐ์ฐ โ std::atomic
๋ณตํฉ ์ฐ์ฐ โ std::mutex
reader-heavy โ std::shared_mutex
๋งค์ฐ ์งง์ CS + ๋ฉํฐ์ฝ์ด โ spin lock
ํ๋ก์ธ์ค ๊ฐ ๊ณต์ โ ์ปค๋ Mutex / Semaphore (IPC ํ๊ท)
์๊ณ ๋ฆฌ์ฆ ์ฌ์ค๊ณ ์ฌ์ง โ lock-free ๋ผ์ด๋ธ๋ฌ๋ฆฌ (Boost.Lockfree)
๊ธฐ์ตํ ํ ์ค:
"Race๋ ๊ณต์ ยท๋์ยท์ฐ๊ธฐยท์์๋ฌด๋ณด์ฅ์ ์ฐ๋ฌผ. ๋ฝ์ ์ ์ฐ๋ ๊ฒ๋ณด๋ค ๊ณต์ ๋ฅผ ์ ๋ง๋๋ ๊ฒ ๋ ์ข์ ๋ต."
17. ํ๊ท ๋ค๋ฆฌ โ ๋ค๋ฅธ CS ํ์ผ ์ฐ๊ฒฐ
| ํ์ผ | ์ฐ๊ฒฐ ์ง์ |
|---|---|
| 01_runtime | ๊ณต์ ์์์ ์์น โ ํยท์ ์ญยท์ ์ ์์ญ์ด ๋ชจ๋ race ๋ฐ์ ํ๋ณด. ์คํ์ ์ค๋ ๋๋ณ ๋ ๋ฆฝ์ด๋ผ race ์์(20๋ฒ ํ๊ท) |
| 03_new_vs_malloc | ํ ํ ๋น์ ์์ฒด๊ฐ race condition์ ํํ ์ถ์ฒ โ ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์์ new/malloc์ ๋ด๋ถ ๋ฝ ํ์. ๊ทธ๋์ ๋ฉํฐ์ค๋ ๋ ํ ๋น์(tcmallocยทjemalloc)๊ฐ ๋ฐ๋ก ์กด์ฌ |
| 09_rtti_raii | std::lock_guardยทstd::unique_lockยทstd::scoped_lock์ด RAII์ ๋ํ ์ ์ฉ ์ฌ๋ก. ์์ธ ๋ฐ์ํด๋ unlock ๋๋ฝ ์์ |
| 10_pointer_deepdive | ๋๊ธ๋ง ํฌ์ธํฐ๊ฐ lock-free ์๋ฃ๊ตฌ์กฐ ABA ๋ฌธ์ ์ ํต์ฌ โ A๋ฅผ freeํ ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ค์ A ์ฃผ์๋ก ์กํ CAS๊ฐ ์์ |
| 11_smart_pointer | shared_ptr์ reference count๊ฐ atomic ์ฌ์ฉ ์ฌ๋ก. ์ฆ๊ฐ๋ memory_order_relaxed, ๊ฐ์๋ memory_order_acq_rel ๋๋ seq_cst๋ก ๋ง์ง๋ง ์๋ฉธ ๊ฐ์์ฑ ๋ณด์ฅ |
| 16_stl_containers | STL ์ปจํ
์ด๋๋ thread-safeํ์ง ์์ โ ํ ์ปจํ
์ด๋์ ๋ ์ค๋ ๋๊ฐ ๋์ ์ฐ๋ฉด race. concurrent ์ปจํ
์ด๋(tbb::concurrent_queue, Microsoft PPL concurrent_unordered_map ๋ฑ) ๋ณ๋ |
| 19_process_vs_thread | ์ค๋ ๋๊ฐ ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ์ ๊ณต์ ํ๋ฏ๋ก race ๋ฐ์ โ 19๋ฒ์ด race์ ํ ๋. ํ๋ก์ธ์ค๋ ๊ฒฉ๋ฆฌ๋์ด race ์์ง๋ง IPC(22)๋ก ํต์ ํ๋ฉด IPC ์์์ race ๋ฐ์ ๊ฐ๋ฅ |
| 20_stack_overflow | ์คํ์ ์ค๋ ๋๋ณ ๋ ๋ฆฝ์ด๋ผ ์คํ ๋ณ์๋ race ์์. ํ์ง๋ง ์คํ ๋ณ์์ ์ฃผ์๋ฅผ ๋ค๋ฅธ ์ค๋ ๋์ ๋๊ธฐ๋ฉด race + use-after-free ์ํ |
| 21_context_switching | race์ ์๊ฐ์ ์์ธ โ ์ปจํ
์คํธ ์ค์์นญ์ด ๋ช
๋ น ๋จ์๋ก ์์ ์์ ์ ๋ผ์ด๋ค๊ธฐ ๋๋ฌธ์ count++์ 3๋จ๊ณ ์ฌ์ด์์ ์ค์์น๊ฐ ์ผ์ด๋จ. ๋๊ธฐํ ๊ฐ์ฒด ๋น์ฉ๋ ์ปจํ
์คํธ ์ค์์นญ(21)์ ๋น์ฉ ์คํํธ๋ผ๊ณผ ์ง๊ฒฐ |
| 22_ipc | ํ๋ก์ธ์ค ๊ฐ race โ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์์ OS๊ฐ ๋๊ธฐํ ์ ํด์ฃผ๋ฏ๋ก Named Mutex/Semaphore/Event(์ปค๋ ๊ฐ์ฒด)์ ์ง์ง์ด ์ฌ์ฉ. 22๋ฒ ยง13.5์ โ๊ณต์ ๋ฉ๋ชจ๋ฆฌ + Named ๋๊ธฐํโ ํจํด์ด IPC ์ฐจ์์ race ํํผ |