๋ฎคํ ์ค๋ ๊ฐ๋จํฉ๋๋ค. ์ ๊ทธ๊ณ , ๊ณต์ ์ํ๋ฅผ ์์ ํ๊ณ , ๋ค์ ์ ๊ธ์ ํด์ ํ๋ฉด ๋ฉ๋๋ค. ์ ๊ทผ์ ์ํ๋ ๋ชจ๋ ์ค๋ ๋๋ ์ฐจ๋ก๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค. ๋ฌธ์ ๋ ๋ฐ๋ก ์ด "์ฐจ๋ก๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ"์ ๋๋ค. ์ฝ์ด ์๊ฐ ๋ง๊ณ ์ฒ๋ฆฌ๋์ด ๋์ ์์คํ ์์๋ ์ง๋ ฌํ ์์ฒด๊ฐ ํ๊ณ๊ฐ ๋ฉ๋๋ค. ์ ๊ธ์ ๋ณด์ ํ ์ค๋ ๋๊ฐ ์ ์ ๋๋ฉด ๋ค๋ฅธ ๋ชจ๋ ์ค๋ ๋๊ฐ ์ ํด ์ํ๊ฐ ๋ ์ ์์ต๋๋ค. ์ฐ์ ์์ ๋ฐ์ ์ผ๋ก ์ธํด ์ฐ์ ์์๊ฐ ๋ฎ์ ์ค๋ ๋๊ฐ ์ฐ์ ์์๊ฐ ๋์ ์ค๋ ๋๋ฅผ ์ฐจ๋จํ ์๋ ์์ต๋๋ค. ์์ญ ๊ฐ์ ์ฝ์ด์์ ์ด๋น ์๋ฐฑ๋ง ๊ฐ์ ์ฐ์ฐ์ ์ฒ๋ฆฌํ๋ ์์คํ ์์๋ ๋ฎคํ ์ค ๊ฒฝํฉ์ด ๋จ์ํ ์๋๋ฅผ ์ ํ์ํค๋ ๊ฒ์ ๋์ด ์ฒ๋ฆฌ๋์ ๊ธ๊ฒฉํ ๋จ์ด๋จ๋ฆฝ๋๋ค.
๋์์ฑ ๋ ผ๋ฆฌ ๋ถ์
SMART TS XL ์์์ ์ฐ์ฐ ๊ฒฝ๋ก, ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ๋ฐ ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ถ์ ํฉ๋๋ค.
์ง๊ธ ํ์๋ฝ ํ๋ฆฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ ์ํธ ๋ฐฐ์ ๋ฅผ ์์์ ์ฐ์ฐ์ผ๋ก ๋์ฒดํฉ๋๋ค. ์ถฉ๋์ ๋ฐฉ์งํ๋ ๋์ , ์ถฉ๋์ ํ์ฉํ๊ณ ๋ณต๊ตฌํฉ๋๋ค. ๋น๊ต ๋ฐ โโ๊ตํ ์ฐ์ฐ์ ์คํจํ ์ค๋ ๋๋ ์ ๋ฐ์ดํธ๋ ์ํ๋ก ์ฌ์๋ํฉ๋๋ค. ์ด๋ค ์ค๋ ๋๋ ๋ค๋ฅธ ์ค๋ ๋๋ฅผ ์ฐจ๋จํ์ง ์์ผ๋ฉฐ, ์ ์ด๋ ํ๋์ ์ค๋ ๋๋ ํญ์ ์์ ์ ์งํํฉ๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก, ๋์ ๊ฒฝ์ ์ํฉ์์ ์ฒ๋ฆฌ๋์ด ํฌ๊ฒ ํฅ์๋๊ณ , ์ปจ๋ณด์ด ํจ๊ณผ ์์ด ์์ธก ๊ฐ๋ฅํ ์ง์ฐ ์๊ฐ์ ์ ๊ณตํ๋ฉฐ, ์ค๊ณ์ ๊ต์ฐฉ ์ํ์ ์ฐ์ ์์ ์ญ์ ์ ๋ฐฉ์งํฉ๋๋ค. ํ์ง๋ง ๊ตฌํ ๋ณต์ก์ฑ์ด ๋จ์ ์ ๋๋ค. ABA ๋ฌธ์ , ๋ฉ๋ชจ๋ฆฌ ํ์ ์ํ, ๊ฑฐ์ง ๊ณต์ , ๋ฏธ๋ฌํ ๋ฉ๋ชจ๋ฆฌ ์์ ์๊ตฌ ์ฌํญ ๋ฑ์ ๋ฝ ๊ธฐ๋ฐ ์ฝ๋์์๋ ์กด์ฌํ์ง ์๋ ํจ์ ์ ๋๋ค. ์ด ๊ธ์์๋ ๊ตฌ์ฒด์ ์ธ ์ฝ๋๋ฅผ ํตํด ์ด๋ฌํ ํจ์ ๋ค์ ๋ชจ๋ ๋ค๋ฃน๋๋ค.
๋ฝํ๋ฆฌ, ์จ์ดํธํ๋ฆฌ, ๋ฎคํ ์ค: ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ๊น์?
๊ตฌํ ๋ฐฉ์์ ์ ํํ๊ธฐ ์ ์, ์์คํ ์ ์ค์ ๋ก ํ์ํ ์งํ ๋ณด์ฅ์ด ๋ฌด์์ธ์ง ๋จผ์ ์ง๋ฌธํด์ผ ํฉ๋๋ค. ์ธ ๊ฐ์ง ๋ชจ๋ธ์ ์ค๋ ๋ ๊ฐ ๊ฒฝ์์ด ๋ฐ์ํ ๋ ๋ณด์ฅํ๋ ๋ด์ฉ์ด ์๋ก ๋ค๋ฆ ๋๋ค.
| ๋ชจ๋ธ | ์งํ ๋ณด์ฅ | ์ผ๋ฐ์ ์ธ ๋๊ธฐ ์๊ฐ | ๋ณต์ก์ฑ | ์ง์ ๊ธฐ๊ธฐ |
|---|---|---|---|---|
| ๋ฎคํ ์ค/๋ฝ ๊ธฐ๋ฐ | ์ฐจ๋จ ์ค, ์ค๋ ๋ ๋๊ธฐ | ๋ ผ์ ์ค ์์ธก ๋ถ๊ฐ๋ฅ | ๋์ | ๊ฒฝ์์ด ์ ์ ๊ณต์ ์ํ, ๊ฐ๋จํ ์ ํ์ฑ ์๊ตฌ ์ฌํญ |
| ์ ๊ธ์ฅ์น ์์ | ์์คํ ์ ์ฒด์ ์ผ๋ก, ์ ์ด๋ ํ๋์ ์ค๋ ๋๊ฐ ์งํ๋ฉ๋๋ค. | ๊ฒฝ์์ด ์น์ดํ ์ํฉ์์ ๋ฎ์ | ๋์ | ๊ณ ์ฒ๋ฆฌ๋ ํ, ์คํ, ์นด์ดํฐ |
| ๋๊ธฐ ์๊ฐ ์์ | ๊ฐ ์ค๋ ๋๋ ์ค๋ ๋๋ณ๋ก ์ ํด์ง ๋จ๊ณ ๋ด์ ์๋ฃ๋ฉ๋๋ค. | ์ ํ๋ ์ต์ ์ ๊ฒฝ์ฐ | ๋งค์ฐ ๋์ | ์ค์๊ฐ ์์คํ , ์์ ํ์, ์๊ฒฉํ ์ง์ฐ ์๊ฐ SLA |
| ์ฅ์ ๋ฌผ ์์ | ์๋ก ์งํ์ ๊ฒฝ์์๊ฐ ์์ ๋๋ง ๊ฐ๋ฅํฉ๋๋ค. | ๋ ผ์ ์์ด ๋ฎ์ | ์ค๊ธ | ๊ฑฐ๋ ๊ธฐ์ต ํ๋กํ ํ์ , ์ฐ๊ตฌ ๋งฅ๋ฝ |
๋ฝํ๋ฆฌ(Lock-free) ๋ฐฉ์ ์ ๊ณ ๋์์ฑ ์ด์ ์์คํ ์์ ์ค์ง์ ์ผ๋ก ๊ธฐ๋ณธ ์ค์ ์ ๋๋ค. ๋ง์ดํด-์ค์ฝง ํ, ํธ๋ผ์ด๋ฒ ์คํ, ๊ทธ๋ฆฌ๊ณ ๋๋ถ๋ถ์ ์ค์ MPMC ๋ง ๋ฒํผ๋ ๋ฝํ๋ฆฌ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค. ๊ทน์ฌํ ๊ฒฝ์ ์ํฉ์์๋ ๊ฐ๋ณ ์ค๋ ๋๊ฐ ์์ ๊ณ ๊ฐ๋ก ๋ฉ์ถ ์ ์์ง๋ง, ์์คํ ์ ์ฒด๋ ์ ์์ ์ผ๋ก ์๋ํฉ๋๋ค.
๋๊ธฐ ์๋(Wait-free) ๋ฐฉ์์ ์ค๋ ๋๋ณ ์ ํ๋ ์งํ๋ฅ ์ ๋ณด์ฅํ์ง๋ง, ํจ์ฌ ๋ ๋ณต์กํ ์๊ณ ๋ฆฌ์ฆ์ ์๊ตฌํฉ๋๋ค. ๋ฒ์ฉ์ ์ธ ๊ตฌ์ฑ ๋ฐฉ์์ ์กด์ฌํ์ง๋ง ์ค๋ฒํค๋๊ฐ ๋์ต๋๋ค. ๋๊ธฐ ์๋ ์๊ณ ๋ฆฌ์ฆ์ ํ๊ท ์ฒ๋ฆฌ๋๋ณด๋ค ๊ผฌ๋ฆฌ ์ง์ฐ ์๊ฐ์ด ๋ ์ค์ํ ์๊ฒฉํ ์ค์๊ฐ ํ๊ฒฝ์ ์ ํฉํฉ๋๋ค.
์ฅ์ ๋ฌผ ์๋(Obstruction-free) ๋ฐฉ์ ์ ์ค์ ์ด์ ํ๊ฒฝ์์ ์ง์ ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๋ ๋๋ญ ๋๋ค. ์ผ๋ถ ํธ๋์ญ์ ๋ฉ๋ชจ๋ฆฌ ๊ตฌํ์์ ๋ํ๋๋ฉฐ, ์๊ณ ๋ฆฌ์ฆ์ ์ ํ์ฑ์ ์ ์ฆํ ๋ ๋๋ค๋ ์ญํ ์ ํฉ๋๋ค.
๋๋ถ๋ถ์ ๊ณ ๋์์ฑ ์์คํ ์์๋ ๊ฒฝํฉ์ด ์ ๊ณ ์ ํ์ฑ์ด ๊ฐ๋จํ ๋๋ ๋ฎคํ ์ค๋ฅผ ์ฌ์ฉํ๊ณ , ๊ฒฝํฉ ์ํฉ์์์ ์ฒ๋ฆฌ๋์ด ์ค์ํ ๋๋ ๋ฝํ๋ฆฌ(lock-free) ๋ฐฉ์์ ์ฌ์ฉํ๋ฉฐ, ์ต์ ์ ๊ฒฝ์ฐ ์ค๋ ๋๋ณ ์ง์ฐ ์๊ฐ์ด ํ์์ ์ธ ๊ฒฝ์ฐ์๋ง ์จ์ดํธํ๋ฆฌ(wait-free) ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
๋ฝํ๋ฆฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ ๋ฌด์์ธ๊ฐ์?
๋ฐ์ดํฐ ๊ตฌ์กฐ๊ฐ ๋ฝํ๋ฆฌ(lock-free) ๋ผ๋ ๊ฒ์ ํด๋น ๊ตฌ์กฐ๋ฅผ ์ฐ์ฐํ๋ ๋ชจ๋ ์ค๋ ๋ ์ค ์ ์ด๋ ํ๋๋ ๋ค๋ฅธ ์ค๋ ๋์ ์์ ์ด๋ ์ค์ผ์ค๋ง ๋ฐฉ์๊ณผ ๊ด๊ณ์์ด ์ ํํ ๋จ๊ณ ๋ด์ ์ฐ์ฐ์ ์๋ฃํ๋ค๋ ๊ฒ์ ๋ณด์ฅํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ด ๊ณต์์ ์ธ ์ ์๋ ๋ค์๊ณผ ๊ฐ์ ์ ํํ ํจ์๋ฅผ ๊ฐ์ต๋๋ค. ๋ฝํ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ์ ๊ต์ฐฉ ์ํ์ ๋น ์ง ์ ์์ต๋๋ค. ํ๋์ ์ค๋ ๋๊ฐ ์ ์ง๋๊ฑฐ๋, ์ผ์ ์ค๋จ๋๊ฑฐ๋, ๋๋ฆฌ๊ฒ ์คํ๋๋๋ผ๋ ๋ค๋ฅธ ์ค๋ ๋๋ ๊ณ์ํด์ ์์ ์ ์งํํ ์ ์์ต๋๋ค.
์ด ๋ฉ์ปค๋์ฆ์ ์์์ ์ฐ์ฐ, ์ฆ ํ๋์ ๋ถํ ๋ถ๊ฐ๋ฅํ ๋จ์๋ก ์คํ๋๋ CPU ๋ช ๋ น์ด์ ๊ธฐ๋ฐํฉ๋๋ค. ๋ณดํธ์ ์ธ ๊ธฐ๋ณธ ์ฐ์ฐ์ CAS(Compare-and-Swap) ์ ๋๋ค.
CAS(location, expected, new_value):
if *location == expected:
*location = new_value
return true
else:
return false // someone else changed it first
CAS๋ ํ๋์จ์ด ์์ค์์ ์์์ ์
๋๋ค. x86์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. CMPXCHG ๋ช
๋ น์ด์
๋๋ค. ARM์์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํ๋ฉ๋๋ค. LDXR/STXR (๋ก๋ ์ ์ฉ/์ ์ฅ ์ ์ฉ)์ LL/SC(๋ก๋ ์ฐ๊ฒฐ/์ ์ฅ ์กฐ๊ฑด๋ถ) ๋ณํ์
๋๋ค. ์ค๋ ๋๋ ๊ฐ์ ์ฝ๊ณ ์ ๊ฐ์ ๊ณ์ฐํ ๋ค์, ๋ค๋ฅธ ์ค๋ ๋์์ ๊ฐ์ ๋ณ๊ฒฝํ์ง ์์ ๊ฒฝ์ฐ์๋ง CAS๋ฅผ ์ฌ์ฉํ์ฌ ํด๋น ๊ฐ์ ์ค์นํฉ๋๋ค. CAS๊ฐ ์คํจํ๋ฉด ์ค๋ ๋๋ ์ ๊ฐ์ผ๋ก ๋ค์ ์๋ํฉ๋๋ค.
C++11 ์ด์ ๋ฒ์ ์์๋ ์ด ๊ธฐ๋ฅ์ด ๋ค์๊ณผ ๊ฐ์ด ๋
ธ์ถ๋ฉ๋๋ค. std::atomic:
CPP
#include <atomic>
// Atomic increment using CAS loop
void atomic_add(std::atomic<int>& counter, int delta) {
int expected = counter.load(std::memory_order_relaxed);
while (!counter.compare_exchange_weak(
expected,
expected + delta,
std::memory_order_release,
std::memory_order_relaxed))
{
// expected is updated on failure -- retry with fresh value
}
}
compare_exchange_weak CAS ๊ธฐ๋ณธ ์์์
๋๋ค. ์คํจ ์ ์
๋ฐ์ดํธ๋ฉ๋๋ค. expected ํ์ฌ ๊ฐ์ผ๋ก ์๋ ๋ณํ๋์ด ์ฌ์๋ ๋ฃจํ๊ฐ ๊ด์ฉ์ ์ธ ํํ์ด ๋ฉ๋๋ค.
ABA ๋ฌธ์ : Lock-Free์ ๊ฐ์ฅ ์ํํ ํจ์
ABA ๋ฌธ์ ๋ ๋ฝํ๋ฆฌ ํ๋ก๊ทธ๋๋ฐ์์ ๊ฐ์ฅ ์ง๊ด์ ์ด์ง ์์ ์ ํ์ฑ ์ํ ์์์ ๋๋ค. CAS๋ ์๋ก์ด ๊ฐ์ ์ ์ฅํ๊ธฐ ์ ์ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์์ ๊ฐ์ด ์ฌ์ ํ ์กด์ฌํ๋์ง ํ์ธํฉ๋๋ค. ํ์ง๋ง ์ฝ๊ธฐ ์์ ๊ณผ CAS ํธ์ถ ์ฌ์ด์ ํด๋น ๊ฐ์ด ๋ณ๊ฒฝ๋์๋ค๊ฐ ๋ค์ ์๋๋๋ก ๋๋์๊ฐ๋์ง ์ฌ๋ถ๋ ๊ฐ์งํ ์ ์์ต๋๋ค. ๋ฉ๋ชจ๋ฆฌ ์์น๋ ๊ฒ๋ณด๊ธฐ์๋ ์๋ ๊ฐ์ฒ๋ผ ๋ณด์ด์ง๋ง, ์์คํ ์ ๊ทผ๋ณธ์ ์ธ ์ํ๋ CAS๊ฐ ๊ฐ์งํ ์ ์๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ๋์์ ์ ์์ต๋๋ค.
์๋๋ฆฌ์ค ๋จ๊ณ๋ณ ์ค๋ช
๋จ์ผ ํฌ์ธํฐ๋ฅผ ์ฌ์ฉํ๋ ๋ฝํ๋ฆฌ ์คํ์ ์๊ฐํด ๋ณด์ธ์. top:
- ์ค๋ ๋ A๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
top = Node1Node1์ ๋ค์ ํฌ์ธํฐ๋ Node2์ ๋๋ค. - ์ค๋ ๋ A๋ ์คํ์ด ์๋ฃ๋๊ธฐ ์ ์ ์ ์ ๋์์ต๋๋ค.
- ์ค๋ ๋ B๋ Node1์ ํํ๊ณ (top์ Node2๊ฐ ๋จ), ๊ทธ ๋ค์ Node2๋ฅผ ํํฉ๋๋ค(top์ null์ด ๋จ).
- ์ค๋ ๋ B๊ฐ ์ ๋ ธ๋๋ฅผ ํธ์ํ๋๋ฐ, ์ด ๋ ธ๋๋ Node1๊ณผ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ํ ๋น๋ฉ๋๋ค(์์ ๋ชฉ๋ก ํ ๋น์์์ ํํ ๋ณผ ์ ์๋ ํ์). Top = Node1(๋์ผํ ํฌ์ธํฐ, ๋ค๋ฅธ ๋ด์ฉ).
- ์ค๋ ๋ A๊ฐ ์ฌ๊ฐ๋ฉ๋๋ค. ํด๋น CAS๋ ๋ค์๊ณผ ๊ฐ์ด ๋ด
๋๋ค.
top == Node1(๊ธฐ๋๊ฐ)์ด ์ฑ๊ณตํ๋ฉด ์ค์ ๋ฉ๋๋ค.top = Node2ํ์ง๋ง Node2๋ ์ด๋ฏธ ์ฌ์ฉ ๊ฐ๋ฅํ์ต๋๋ค. ํฐ์ผ ๋ฌ๋ค.
ABA ๋ฌธ์ ๋ ๊ฐ ๋ฐ์ดํฐ ๊ตฌ์กฐ์์ ์๋ก ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๋ํ๋ฉ๋๋ค.
- ์คํCAS๋ ์ฑ๊ณตํ์ง๋ง ์ค๋๋ ๋ค์ ํฌ์ธํฐ๋ฅผ ์ค์นํ์ฌ ํด์ ํ ์ฌ์ฉ ์ค๋ฅ๋ฅผ ๋ฐ์์ํต๋๋ค.
- ์ด๋จธ๋ฆฌ๋ ๊ผฌ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ๋ ๊ฒ๋ณด๊ธฐ์๋ ๋ณํ์ง ์์์ง๋ง ๊ตฌ์กฐ๋ ๋ฐ๋์์ต๋๋ค.
- ์ฐ๊ฒฐ๋ ๋ชฉ๋ก๋ ธ๋๊ฐ ์ฌ์ ํ ์ ์์น์ ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ์ค์ ๋ก๋ ์ ๊ฑฐ๋์ด ์ฌํ ๋น๋์์ต๋๋ค.
LL/SC๋ ABA ๋ฌธ์ ๋ฅผ ํผํ ์ ์์๊น์?
๋ค, LL/SC(Load-Linked/Store-Conditional)๋ CAS๋ณด๋ค ๊ฐ๋ ฅํ ์๋ฏธ ์ฒด๊ณ๋ฅผ ์ ๊ณตํ๋ฉฐ ABA(Analytic Hierarchy Assessment)๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ๋ฐฉ์งํฉ๋๋ค. LL์ ๋ก๋๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ "์ฐ๊ฒฐ๋จ"์ผ๋ก ํ์ํฉ๋๋ค. ๋์ผํ ์ฃผ์์ ๋ํ SC๋ LL ์ดํ ํด๋น ์ฃผ์์ ์ ์ฅ ์์ ์ด ๋ฐ์ํ ๊ฒฝ์ฐ, ๊ฐ์ด ์๋ ์ํ๋ก ๋ณต์๋์๋๋ผ๋ ์คํจํฉ๋๋ค. ์์ ์ด๋ ฅ์ ํ์ฌ ๊ฐ๋ฟ๋ง ์๋๋ผ ํ๋์จ์ด ์์ค์์๋ ์ถ์ ๋ฉ๋๋ค.
ํ์ง๋ง ์ค์ ํ๋์จ์ด์์ LL/SC๋ฅผ ๊ตฌํํ๋ ๋ฐ์๋ ํ์ค์ ์ธ ํ๊ณ๊ฐ ์์ต๋๋ค. ARM ๋ฐ POWER ์ํคํ ์ฒ์์๋ ์ปจํ ์คํธ ์ค์์น๋ ์บ์ ์ ๊ฑฐ์ ๊ฐ์ ๊ฒฝ์ ์ํฉ์ด ์๋๋ผ๋ LL/SC ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ฝ๋๋ ์ค์ ์ถฉ๋์ด ์๋๋ผ๋ ์ฌ์๋ ๋ฃจํ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. x86 ์ํคํ ์ฒ์๋ ๋ค์ดํฐ๋ธ LL/SC ๊ธฐ๋ฅ์ด ์์ผ๋ฉฐ, CAS๊ฐ ํ๋์จ์ด ํ๋ฆฌ๋ฏธํฐ๋ธ์ ๋๋ค. x86 ์ฝ๋์ ๊ฒฝ์ฐ, ABA(์ ํ๋ฆฌ์ผ์ด์ ๊ธฐ๋ฐ ์บ์)๋ ์ํํธ์จ์ด์ ์ผ๋ก ๋ฐฉ์งํด์ผ ํฉ๋๋ค.
ABA ๊ฐ์ ๋ฐฉ์: ์ธ ๊ฐ์ง ์ ๊ทผ๋ฒ
1. ๋ฒ์ ์นด์ดํฐ(ํ๊ทธ๋ ํฌ์ธํฐ)
๋ฒ์ ์นด์ดํฐ๋ฅผ ํฌ์ธํฐ์ ๋์ผํ ์์ ์๋์ ์ ์ฅํฉ๋๋ค. CAS ์์ ์ด ์ฑ๊ณตํ ๋๋ง๋ค ์นด์ดํฐ๊ฐ ์ฆ๊ฐํฉ๋๋ค. ํฌ์ธํฐ๊ฐ ์๋ ๊ฐ์ผ๋ก ๋์๊ฐ๋๋ผ๋ ์นด์ดํฐ ๊ฐ์ ๋ฌ๋ผ์ง๋๋ค.
CPP
#include <atomic>
#include <cstdint>
struct TaggedPtr {
uintptr_t ptr : 48; // pointer (48-bit virtual address space)
uintptr_t tag : 16; // version counter
};
std::atomic<TaggedPtr> top;
bool cas_with_tag(std::atomic<TaggedPtr>& loc,
TaggedPtr expected,
void* new_ptr) {
TaggedPtr desired = { (uintptr_t)new_ptr, expected.tag + 1 };
return loc.compare_exchange_strong(expected, desired);
}
์ด๊ฒ์ด ์ค์ ํ์ค ์ ๊ทผ ๋ฐฉ์์ ๋๋ค. 16๋นํธ ํ๊ทธ๋ 65,536๋ฒ์ ์ฐ์ฐ ํ์ ๋ค์ ๋์์ค๋๋ฐ, ์ด๋ก ์ ์ผ๋ก๋ ์์ ํ์ง ์์ง๋ง ๋๋ถ๋ถ์ ์์คํ ์์๋ ์ค์ง์ ์ผ๋ก ์ถฉ๋ถํฉ๋๋ค. ์ค๋ ๋๊ฐ ๋์ผํ ์์น์์ ์ ํํ 65,536๋ฒ์ CAS ์ฌ์ดํด ๋์ ์ ์ ๋ ๊ฐ๋ฅ์ฑ์ ๊ทนํ ๋ฎ์ต๋๋ค.
2. ์ํ ์งํ
๊ฐ ์ค๋ ๋๋ ํ์ฌ ์ ๊ทผ ์ค์ธ ๋ ธ๋๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ์ธ "์ํ ํฌ์ธํฐ" ์งํฉ์ ์ ์งํฉ๋๋ค. ์ค๋ ๋๋ ๋ ธ๋๋ฅผ ํด์ ํ๊ธฐ ์ ์ ๋ชจ๋ ์ํ ํฌ์ธํฐ ๋ ์ง์คํธ๋ฆฌ๋ฅผ ํ์ธํ์ฌ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ํด๋น ๋ ธ๋์ ์ ๊ทผํ๊ณ ์์ง ์์์ง ํ์ธํฉ๋๋ค. ์ํ ํฌ์ธํฐ์ ๋ํ๋๋ ๋ ธ๋๋ ํด๋น ํฌ์ธํฐ๊ฐ ํด์ ๋ ๋๊น์ง ํด์ ๊ฐ ์ง์ฐ๋ฉ๋๋ค.
CPP
// Simplified hazard pointer pattern
thread_local void* hazard_ptr = nullptr;
void* safe_load(std::atomic<void*>& head) {
void* ptr;
do {
ptr = head.load(std::memory_order_acquire);
hazard_ptr = ptr; // announce we are using this
// memory fence ensures announcement is visible before validation
std::atomic_thread_fence(std::memory_order_seq_cst);
} while (ptr != head.load(std::memory_order_acquire)); // validate still valid
return ptr;
}
void safe_release() {
hazard_ptr = nullptr;
}
์ํ ํฌ์ธํฐ๋ ๋ฉ๋ชจ๋ฆฌ ํ์ ์์ค์์ ABA(์กํฐ๋ธ ์ค๋ ๋ ์๋ํ)๋ฅผ ๋ฐฉํดํฉ๋๋ค. ์ฆ, ์ด๋ค ์ค๋ ๋๋ผ๋ ํด๋น ๋ ธ๋์ ๋ํ ์ํ ํฌ์ธํฐ๋ฅผ ๋ณด์ ํ๊ณ ์๋ ๋์์๋ ๋ ธ๋๋ฅผ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๊ธฐ ์ ์ ๋ชจ๋ ์ค๋ ๋์ ์ํ ํฌ์ธํฐ๋ฅผ ๊ฒ์ฌํด์ผ ํ๋ฏ๋ก ์ค๋ ๋ ์์ ๋น๋กํ๋ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค.
3. ์๋ ๊ธฐ๋ฐ ๋ณต์(EBR)
์ค๋ ๋๋ ์ ์ญ์ ์ผ๋ก ์ถ์ ๋๋ "์ํฌํฌ" ๋จ์๋ก ์๋ํฉ๋๋ค. ์ข ๋ฃ๋ ๋ ธ๋๋ ์ํฌํฌ๋ณ๋ก ๋ฒํผ๋ง๋๋ฉฐ, ๋ชจ๋ ์ค๋ ๋๊ฐ ํด๋น ๋ ธ๋๊ฐ ์ข ๋ฃ๋ ์ํฌํฌ๋ฅผ ์ง๋๊ฐ ํ์์ผ ํด์ ๋ฉ๋๋ค. EBR์ ์ํ ํฌ์ธํฐ๋ณด๋ค ์ฌ์ฉ์ด ๊ฐํธํ๊ณ ์ฐ์ฐ๋น ์ค๋ฒํค๋๊ฐ ๋ฎ์ง๋ง, ๋๊ธฐ ๊ธฐ๊ฐ ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ ํ์ ์ด์ง๋ง ์์ธก ๋ถ๊ฐ๋ฅํ๊ฒ ์ฆ๊ฐํ ์ ์๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
์๋ฐ์์๋ AtomicStampedReference ์ฐธ์กฐ์ ์ ์ ์คํฌํ๋ฅผ ์ง์ง์ด ABA ๋ฌธ์ ๋ฅผ ์ง์ ์ ์ผ๋ก ํด๊ฒฐํฉ๋๋ค.
์๋ฐ
import java.util.concurrent.atomic.AtomicStampedReference;
AtomicStampedReference<Node> top =
new AtomicStampedReference<>(null, 0);
void push(Node newNode) {
int[] stamp = new int[1];
Node current;
do {
current = top.get(stamp);
newNode.next = current;
} while (!top.compareAndSet(current, newNode, stamp[0], stamp[0] + 1));
}
๋ฝํ๋ฆฌ ํ ๊ตฌํ
ํ๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ํ์ํ ๋ฝํ๋ฆฌ(lock-free) ๊ตฌ์กฐ์ ๋๋ค. ๋ํ์ ์ธ ๋ฝํ๋ฆฌ ํ๋ ๋ง์ดํด-์ค์ฝง(Michael-Scott) ํ๋ก, ๋ ๊ฐ์ ํฌ์ธํฐ(ํค๋์ ํ ์ผ)์ ์ผํฐ๋ ๋ ธ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋์์ ์ธ ํ ์ถ๊ฐ ๋ฐ ์ ๊ฑฐ ์์ ์ ํ์ฉํฉ๋๋ค.
SPSC ํ: ์ต์ํ์ ๋๊ธฐํ๋ก ์ต๋์ ์ฑ๋ฅ ๊ตฌํ
๋จ์ผ ์์ฐ์-๋จ์ผ ์๋น์ ํ๋ ์ฐ๊ธฐ-์ฐ๊ธฐ ๋ฐ ์ฝ๊ธฐ-์ฝ๊ธฐ ๊ฒฝํฉ์ ๋ชจ๋ ์ ๊ฑฐํฉ๋๋ค. ํ ์ค๋ ๋๋ ํ์ ๋์ ์ฐ๊ณ , ๋ค๋ฅธ ์ค๋ ๋๋ ๋์ ์ฝ์ต๋๋ค. ์์ฐ์์ ์๋น์ ๊ฐ์ ๊ณต์ ๋๋ ๊ฒ์ ๋์ ๋ํ ํฌ์ธํฐ๋ฟ์ ๋๋ค.
CPP
#include <atomic>
#include <array>
template<typename T, size_t Capacity>
class SPSCQueue {
std::array<T, Capacity> buffer;
alignas(64) std::atomic<size_t> head{0}; // consumer reads head
alignas(64) std::atomic<size_t> tail{0}; // producer writes tail
// alignas(64) puts each on a separate cache line -- prevents false sharing
public:
bool push(const T& item) {
size_t t = tail.load(std::memory_order_relaxed);
size_t next = (t + 1) % Capacity;
if (next == head.load(std::memory_order_acquire))
return false; // full
buffer[t] = item;
tail.store(next, std::memory_order_release);
return true;
}
bool pop(T& item) {
size_t h = head.load(std::memory_order_relaxed);
if (h == tail.load(std::memory_order_acquire))
return false; // empty
item = buffer[h];
head.store((h + 1) % Capacity, std::memory_order_release);
return true;
}
};
The alignas(64) ํค๋์ ํ
์ผ์ ์์๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์์๊ฐ ์์ผ๋ฉด ๋ ์์
๋ชจ๋ ๋์ผํ ์บ์ ๋ผ์ธ์ ์ ์ฅ๋ฉ๋๋ค. ํ
์ผ์ ๋ํ ๋ชจ๋ ์ฐ๊ธฐ ์์
์ ์ฝ์ด ์ฝ๊ธฐ ํค๋์์ ๋ณผ ์ ์๋ ์บ์ ๋ผ์ธ ๋ฌดํจํ๋ฅผ ์ ๋ฐํ๊ณ , ๋ฐ๋๋ก ํ
์ผ์ ๋ํ ์ฐ๊ธฐ ์์
์ด ํค๋์์ ํค๋๋ก ์ ๋ฌ๋๋ฉด ์๋ชป๋ ๊ณต์ ๊ฐ ๋ฐ์ํ์ฌ ๋
๋ฆฝ์ ์ด์ด์ผ ํ ์์
์ด ์ง๋ ฌํ๋ฉ๋๋ค.
MPMC ๋๊ธฐ์ด: ๋ค์ค ์์ฐ์ ๋ค์ค ์๋น์
์์ ๋์ ์คํ MPMC ํ๋ ํจ์ฌ ๋ ๋ณต์กํฉ๋๋ค. ์ค์ ๊ตฌํ์์๋ ๋ฝ ์์ด ์์ฐ์์ ์๋น์๋ฅผ ์กฐ์ ํ๊ธฐ ์ํด ์ฌ๋กฏ๋น ์๋ฒ์ ์ฌ์ฉํฉ๋๋ค.
CPP
#include <atomic>
#include <array>
template<typename T, size_t Capacity>
class MPMCQueue {
struct Slot {
std::atomic<size_t> sequence;
T data;
};
alignas(64) std::array<Slot, Capacity> slots;
alignas(64) std::atomic<size_t> enqueue_pos{0};
alignas(64) std::atomic<size_t> dequeue_pos{0};
public:
MPMCQueue() {
for (size_t i = 0; i < Capacity; ++i)
slots[i].sequence.store(i, std::memory_order_relaxed);
}
bool push(const T& item) {
size_t pos = enqueue_pos.fetch_add(1, std::memory_order_relaxed);
Slot& slot = slots[pos % Capacity];
size_t seq = slot.sequence.load(std::memory_order_acquire);
// wait for the slot to be ready for writing
while (seq != pos) {
seq = slot.sequence.load(std::memory_order_acquire);
}
slot.data = item;
slot.sequence.store(pos + 1, std::memory_order_release);
return true;
}
bool pop(T& item) {
size_t pos = dequeue_pos.fetch_add(1, std::memory_order_relaxed);
Slot& slot = slots[pos % Capacity];
size_t seq = slot.sequence.load(std::memory_order_acquire);
while (seq != pos + 1) {
seq = slot.sequence.load(std::memory_order_acquire);
}
item = slot.data;
slot.sequence.store(pos + Capacity, std::memory_order_release);
return true;
}
};
.NET ๋์ ํ: ๋ด๋ถ ์๋ ๋ฐฉ์
.NET์ ConcurrentQueue<T> .NET 5 ์ด์์์๋ ์ธ๊ทธ๋จผํธ ๋ฐฐ์ด ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ฐ ์ธ๊ทธ๋จผํธ๋ ํค๋ ๋ฐ ํ
์ผ ์ธ๋ฑ์ค๋ก ๊ด๋ฆฌ๋๋ ๊ณ ์ ํฌ๊ธฐ ๋ฐฐ์ด์
๋๋ค. Interlocked (ํ๋์จ์ด์ CAS์ ๋งคํ๋๋) ์ฐ์ฐ์
๋๋ค. ์ธ๊ทธ๋จผํธ๋ ํ๋ฐ์ฑ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํด ์ฐ๊ฒฐ๋ฉ๋๋ค. _nextSegment ํฌ์ธํฐ์
๋๋ค. enqueue๋ ํ์ฌ ํ
์ผ ์ธ๊ทธ๋จผํธ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๊ณ , ์ธ๊ทธ๋จผํธ ์ฒด์ธ์ด ๊ฐ๋ ์ฐจ๋ฉด ํฌ๊ธฐ๋ฅผ ๋๋ฆฝ๋๋ค. dequeue๋ ํค๋ ์ธ๊ทธ๋จผํธ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ํค๋ ์ธ๋ฑ์ค๋ฅผ ์์์ ์ผ๋ก ์ฆ๊ฐ์ํต๋๋ค.
ํต์ฌ ์ค๊ณ ๊ฒฐ์ ์ ๊ธ๋ก๋ฒ ๊ณ ์ฐฉ์ ํผํ๋ ๊ฒ์
๋๋ค. ์์ฐ์๋ ํ์ฌ ์ธ๊ทธ๋จผํธ์ ๊ผฌ๋ฆฌ ์ง์์์๋ง ๊ฒฝ์ํ๊ณ , ์๋น์๋ ๋จธ๋ฆฌ ์ง์์์๋ง ๊ฒฝ์ํฉ๋๋ค. ์ด๋ค ์์ฐ์๋ ์๋น์์ ์ง์ ๊ฒฝ์ํ์ง ์์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก ConcurrentQueue<T> ์์ฐ์-์๋น์ ํ์ดํ๋ผ์ธ์ ๋งค์ฐ ํจ์จ์ ์
๋๋ค.
๋ ์นด๋ก์ด
using System.Collections.Concurrent;
using System.Threading;
// .NET ConcurrentQueue -- lock-free, thread-safe, FIFO
var queue = new ConcurrentQueue<int>();
// Producer thread
var producer = Task.Run(() => {
for (int i = 0; i < 1_000_000; i++)
queue.Enqueue(i);
});
// Consumer thread
var consumer = Task.Run(() => {
long sum = 0;
while (!queue.IsEmpty || !producer.IsCompleted) {
if (queue.TryDequeue(out int item))
sum += item;
else
Thread.SpinWait(1); // brief spin before retry
}
return sum;
});
await Task.WhenAll(producer, consumer);
๋ฝํ๋ฆฌ ์ฝ๋์์์ ์บ์ ์ผ๊ด์ฑ๊ณผ ๊ฑฐ์ง ๊ณต์
๋ฝํ๋ฆฌ ๊ตฌํ์์ ์บ์ ์ผ๊ด์ฑ์ ๋์ ๋ณด์ด์ง ์๋ ์ฑ๋ฅ ๋ณ์์ ๋๋ค. ์ฝ์ด๊ฐ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ฐ๊ธฐ๋ฅผ ์ํํ๋ฉด ํด๋น ์์น๋ฅผ ํฌํจํ๋ 64๋ฐ์ดํธ ์บ์ ๋ผ์ธ ์ ์ฒด๊ฐ ๋ค๋ฅธ ๋ชจ๋ ์ฝ์ด์ ์บ์์์ ๋ฌดํจํ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋ค๋ฅธ ์ฝ์ด๋ค์ ์ฝ๊ธฐ ์ ์ ์ ๋ฐ์ดํธ๋ ๋ผ์ธ์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ์์์ ์ ๋ฐ์ดํธ๊ฐ ๋น๋ฒํ๊ฒ ๋ฐ์ํ๋ ๋ฝํ๋ฆฌ ์ฝ๋์์๋ ์ด๋ฌํ ์บ์ ๋ผ์ธ ๋ฌดํจํ๊ฐ ์ฃผ์ ๋น์ฉ ์์๊ฐ ๋ ์ ์์ต๋๋ค.
๊ฑฐ์ง ๊ณต์ ๋ ๋ ์ค๋ ๋๊ฐ ์บ์ ๋ผ์ธ์ ๊ณต์ ํ๋ ์๋ก ๋ค๋ฅธ ๋ณ์๋ฅผ ์์ ํ ๋ ๋ฐ์ํฉ๋๋ค. ๋ ์ค๋ ๋ ๋ชจ๋ ๋์ผํ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ๊ฒ์ด ์๋์ง๋ง, ์บ์ ์ผ๊ด์ฑ ํ๋กํ ์ฝ์ ์ ์ฒด ์บ์ ๋ผ์ธ์ ๊ฒฝ์์ด ์๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผํฉ๋๋ค.
CPP
// BAD: head and tail on the same cache line
struct BadQueue {
std::atomic<size_t> head; // bytes 0-7
std::atomic<size_t> tail; // bytes 8-15
// both fit in the same 64-byte cache line
// producer writing tail invalidates head in consumer's cache
};
// GOOD: head and tail on separate cache lines
struct GoodQueue {
alignas(64) std::atomic<size_t> head;
alignas(64) std::atomic<size_t> tail;
// each on its own cache line -- no false sharing
};
MESI ํ๋กํ ์ฝ(Modified, Exclusive, Shared, Invalid)์ x86 CPU๊ฐ ์บ์ ๋ผ์ธ ์์ ๊ถ์ ์ด๋ป๊ฒ ์กฐ์ ํ๋์ง ์ค๋ช ํฉ๋๋ค. ์ฝ์ด๊ฐ ๋ค๋ฅธ ์ฝ์ด์ ๊ณต์ ํ๋ ์์น(Shared ์ํ)์ ์ฐ๊ธฐ ์์ ์ ์ํํ๋ ค๋ฉด ์ฐ๊ธฐ ์์ฒญ์ ๋ธ๋ก๋์บ์คํธํ๊ณ , ๋ชจ๋ ๊ณต์ ์ฝ์ด๋ก๋ถํฐ ์น์ธ์ ๊ธฐ๋ค๋ฆฐ ํ Modified ์ํ๋ก ์ ํํด์ผ ํฉ๋๋ค. ์ด๋ฌํ ์ผ๊ด์ฑ ํ๋กํ ์ฝ ์๋ณต ๊ณผ์ ์ ๋ฉํฐ ์์ผ ์์คํ ์์ 50~300 ์ฌ์ดํด ์ด์์ ์๋ชจํฉ๋๋ค. ์๋ชป๋ ๊ณต์ (False sharing)๋ ๋ ๋ฆฝ์ ์ธ ์ค๋ ๋ ๋ก์ปฌ ์์ ์ ์บ์ ์ผ๊ด์ฑ ํธ๋ํฝ์ผ๋ก ์ ํ์์ผ ์ฝ์ด ์์ ๋น๋กํ์ฌ ์ ํ์ ์ผ๋ก ํ์ฅ๋์ด์ผ ํ๋ ์ฑ๋ฅ์ ์ ํ์ํต๋๋ค.
ํ์ ๊ณต์ ๊ฐ์ง: ์ฌ์ฉ perf stat -e cache-misses,L1-dcache-load-misses Linux ๋๋ Windows์ Visual Studio CPU ์ฑ๋ฅ ํ๋กํ์ผ๋ฌ์์ ํ์ธํ ์ ์์ต๋๋ค. ์บ์์ ๋ค์ด๊ฐ ์ ์๋ ๋ฉํฐ์ค๋ ๋ ํ๋ก๊ทธ๋จ์์ L1 ์บ์ ๋ฏธ์ค์จ์ด ๋๋ค๋ ๊ฒ์ ๊ฑฐ์ง ๊ณต์ (false sharing)์ ๊ฐ๋ ฅํ ์งํ์
๋๋ค.
๋ฉ๋ชจ๋ฆฌ ์์ ์ง์ : ์ ์ค์ํ ๊น์? memory_order_relaxed ํญ์ ์ถฉ๋ถํ ๊ฒ์ ์๋๋๋ค.
C + + std::atomic ์ด ์ฐ์ฐ๋ค์ C++11 ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ์์ ์ ๊ณตํ๋ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ์์ ์ง์ ๋ฐฉ์์ ๋ณด์ฌ์ค๋๋ค. ์๋ชป๋ ์์ ์ง์ ์ ABA ๋ฌธ์ ๋ค์์ผ๋ก ๋ฝํ๋ฆฌ ์ฝ๋์์ ๋ ๋ฒ์งธ๋ก ํํ ๋ฒ๊ทธ์
๋๋ค.
CPP
// The five memory orderings and when each applies:
// relaxed: no synchronization, only atomicity
// Use for: counters where only the final value matters
counter.fetch_add(1, std::memory_order_relaxed);
// acquire: reads see all writes by threads that released this location
// Use for: reading shared state protected by a flag
if (ready.load(std::memory_order_acquire)) {
use(data); // guaranteed to see all writes made before ready was set
}
// release: writes are visible to threads that acquire this location
// Use for: publishing shared state
data = compute();
ready.store(true, std::memory_order_release);
// acq_rel: acquire + release in one operation
// Use for: read-modify-write operations like CAS in the middle of a chain
node.compare_exchange_strong(expected, desired,
std::memory_order_acq_rel, // success
std::memory_order_acquire); // failure
// seq_cst: total order across all seq_cst operations, all cores
// Use for: when you need a global consistent view (but slowest)
flag.store(true, std::memory_order_seq_cst);
ํํ ์ ์ง๋ฅด๋ ์ค์: ์ฌ์ฉ relaxed ๋ฆด๋ฆฌ์ค ํ๋๊ทธ์ ๊ฒฝ์ฐ. ์ค์ ํ๋ ์ค๋ ๋ ready = true ๊ณผ relaxed ์์๊ฐ ์์ ์ฐ๊ธฐ ์์
์ด ๋ฐ๋์ ๋ค๋ฐ๋ผ์ค๋ ๊ฒ์ ๋ณด์ฅํ์ง๋ ์์ต๋๋ค. data ๋ค๋ฅธ ์ค๋ ๋์์ ์ฝ์ ๋ ๋ณผ ์ ์์ต๋๋ค. readyํ๋/ํด์ ์์ ์์ฑ์์ ํ๋
์๋ฅผ ์ฐ๊ฒฐํ๋ ์ ํ ๊ด๊ณ๋ฅผ ์์ฑํฉ๋๋ค.
ํธ๋ผ์ด๋ฒ ์คํ: C++์ ๋ฝํ๋ฆฌ ์คํ
ํธ๋ผ์ด๋ฒ ์คํ์ ๊ฐ์ฅ ๊ฐ๋จํ ๋น์๋ช
๋ฝํ๋ฆฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ์
๋๋ค. ์ด๋ ๋จ์ผ ๊ฐ์ฒด์ ๋ํ CAS ๋ฃจํ๋ฅผ ์ฌ์ฉํฉ๋๋ค. top ๋ฐ๋:
CPP
#include <atomic>
template<typename T>
class TreiberStack {
struct Node {
T data;
Node* next;
explicit Node(T d) : data(std::move(d)), next(nullptr) {}
};
std::atomic<Node*> top{nullptr};
public:
void push(T data) {
Node* node = new Node(std::move(data));
node->next = top.load(std::memory_order_relaxed);
while (!top.compare_exchange_weak(
node->next, // expected -- updated on failure
node, // desired
std::memory_order_release,
std::memory_order_relaxed))
{ /* retry */ }
}
bool pop(T& result) {
Node* node = top.load(std::memory_order_acquire);
while (node) {
if (top.compare_exchange_weak(
node,
node->next, // new top
std::memory_order_acquire,
std::memory_order_relaxed))
{
result = std::move(node->data);
// WARNING: cannot free node here safely without hazard pointers or EBR
// delete node; -- ABA hazard if another thread reads node->next
return true;
}
}
return false; // empty
}
};
๋๊ธ delete node ์ด๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋จ์ ์ญ์ ๋ ์์ ์ค๋ช
ํ ABA ์ํ ์์์
๋๋ค. ์ค์ Treiber ์คํ ํ๊ฒฝ์์ ๋
ธ๋ ํ์๋ฅผ ์ํด์๋ ์ํ ์์ ํฌ์ธํฐ, ์ํฌํฌ ๊ธฐ๋ฐ ํ์ ๋๋ ๊ฐ๋น์ง ์ปฌ๋ ์
(Java/C#์ ์ด๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํจ)์ด ํ์ํฉ๋๋ค.
Java ๋ฝํ๋ฆฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ
Java ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ก๋์
์์ค์ ๋ฝํ๋ฆฌ(lock-free) ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค. java.util.concurrent:
| ํด๋์ค | Structure | ์งํ | ๋ ธํธ |
|---|---|---|---|
AtomicReference<T> | ๋จ์ผ ๊ฐ | ์ ๊ธ์ฅ์น ์์ | ๊ฐ์ฒด ์ฐธ์กฐ์ ๋ํ CAS |
AtomicStampedReference<T> | ๊ฐ๊ฒฉ + ์ฐํ | ์ ๊ธ์ฅ์น ์์ | ๋ฒ์ ์นด์ดํฐ๋ฅผ ํตํ ABA ์๋ฐฉ |
ConcurrentLinkedQueue<T> | ๋ง์ดํด-์ค์ฝง ๋๊ธฐ์ด | ์ ๊ธ์ฅ์น ์์ | FIFO, ๋ฌด์ ํ |
ConcurrentLinkedDeque<T> | ์ ๊ธ ํด์ ๋ฐํฌ | ์ ๊ธ์ฅ์น ์์ | ์์ชฝ ๋ |
LongAdder | ๊ณ์๊ธฐ | ์ ๊ธ์ฅ์น ์์ | ์ค๋ฌด๋ฌํ, ๊ณ ์ฒ๋ฆฌ๋ ๊ณ์ |
LongAdder ํนํ ์ฃผ๋ชฉํ ๋งํ ์ ์ ๋ชจ๋ ์ค๋ ๋์์ ๊ฒฝ์ํ๋ ๋จ์ผ ์์์ ์นด์ดํฐ ๋์ , ์ค๋ ๋ ํ์ ์งํฉ์์ ๊ฐ๊ฐ ์ ๊ทผํ ์ ์๋ ์นด์ดํฐ๋ค์ ์คํธ๋ผ์ดํ ๋ฐฐ์ด์ ์ ์งํ๋ค๋ ๊ฒ์
๋๋ค. ๋ฐ๋ผ์ ๊ฒฝ์์ ํ ๊ณณ์ ์ง์ค๋์ง ์๊ณ ์คํธ๋ผ์ดํ ์ ์ฒด์ ๋ถ์ฐ๋ฉ๋๋ค. ์ดํฉ์ ์ง์ฐ ๊ณ์ฐ ๋ฐฉ์์ผ๋ก ๊ณ์ฐ๋ฉ๋๋ค. sum()์ฌ๋ฌ ์ค๋ ๋์ ๊ฑธ์ณ ๋น๋ฒํ๊ฒ ์ฆ๊ฐํ๋ ์ฐ์ฐ ์์
์ ์ํํ ๋, LongAdder ํ์ ํ๊ฒ ๋ฐ์ด๋ ์ฑ๊ณผ๋ฅผ ๋ธ๋ค AtomicLong.incrementAndGet():
์๋ฐ
import java.util.concurrent.atomic.LongAdder;
// BAD for high-concurrency counting: all threads contend on one location
AtomicLong counter = new AtomicLong(0);
counter.incrementAndGet(); // single CAS -- all threads collide
// GOOD for high-concurrency counting: contention distributed across stripes
LongAdder adder = new LongAdder();
adder.increment(); // updates thread-local stripe -- minimal contention
long total = adder.sum(); // sum all stripes lazily
๋ฐฉ๋ฒ SMART TS XL ๋ฝ ํ๋ฆฌ ์์คํ ๊ฐ๋ฐ์ ์ง์ํฉ๋๋ค.
๋ฝ ํ๋ฆฌ ์ฝ๋๋ ์ ๋๋ก ์์ฑํ๊ธฐ ๊ฐ์ฅ ์ด๋ ค์ด ์ฝ๋ ์ ํ ์ค ํ๋์ ๋๋ค. ๋ฒ๊ทธ๋ ๋น๊ฒฐ์ ์ ์ด๋ฉฐ, ํน์ ์คํ ์์์์๋ง ๋ํ๋๊ณ , ๋๊ท๋ชจ ์ด์ ํ๊ฒฝ์์๋ง ๋ฐ๊ฒฌ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ ์ ๋ถ์์ ๊ฒฝ์ ์กฐ๊ฑด์ด ๋ฐ์ํ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋์ ์คํ ์ ์ ์ฝ๋์ ๊ตฌ์กฐ์ ํน์ฑ์ ๊ฒ์ฌํจ์ผ๋ก์จ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
SMART TS XL ์ด ๋๊ตฌ๋ ๋์ ์คํ ์ฝ๋์ ์ ์ฒด ์คํ ๊ฒฝ๋ก๋ฅผ ๋ถ์ํ์ฌ ์์์ ์ฐ์ฐ์ด ๋ณดํธํ๋ ๋ฉ๋ชจ๋ฆฌ ์์น์ ์ด๋ป๊ฒ ๊ด๋ จ๋๋์ง ์ถ์ ํฉ๋๋ค. ๋ฝ ํ๋ฆฌ ์ฝ๋์ ๋ ๊ฑฐ์ ๊ตฌ์ฑ ์์ ๋๋ ๋ค์ค ์ธ์ด ์ํคํ ์ฒ๊ฐ ํผํฉ๋ ์์คํ ์ ๊ฒฝ์ฐ, ๋จ์ผ ์ธ์ด ๋๊ตฌ๋ก๋ ๋ถ๊ฐ๋ฅํ ๊ฒฝ๊ณ ๊ฐ ๊ฐ์์ฑ์ ์ ๊ณตํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ฝ ํ๋ฆฌ C++ ๊ตฌ์ฑ ์์๊ฐ ์ก์ธ์คํ๋ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ ์์ญ์ด ํด๋น ์์ญ์์ ์ฝ๋ Java ์๋น์ค์ ์ด๋ป๊ฒ ๊ด๋ จ๋๋์ง, ๋๋ ๋์ ์คํ ํ๊ฐ COBOL ๊ธฐ๋ฐ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋๋์ง ๋ฑ์ ํ์ ํ ์ ์์ต๋๋ค.
์ ์ ์ฝ๋ ๋ถ์ ๊ธฐ๋ฅ์ ๋์์ฑ ์ํ๊ณผ ๊ด๋ จ๋ ํจํด์ ์๋ณํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์ํ๋ ํ๋ ์๋ฏธ ์ฒด๊ณ๊ฐ ์๋ ์์์ ๋ก๋, ์คํจ ์ ์์ ๊ฐ์ ์ ๋ฐ์ดํธํ์ง ์๋ CAS ๋ฃจํ, ์๋ก ๋ค๋ฅธ ์ค๋ ๋์์ ์ ๊ทผํ๋ ํ๋๊ฐ ์ ๋ ฌ ํจ๋ฉ ์์ด ๊ฐ์ ์์น์ ์๋ ๊ณต์ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ฑ์ด ์์ต๋๋ค. ์ํฅ ๋ถ์ ๊ธฐ๋ฅ์ ๊ณต์ ๋์์ฑ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ์์กดํ๋ ํ์ ๊ตฌ์ฑ ์์๋ฅผ ์ถ์ ํ์ฌ ๊ตฌ์กฐ์ ์ธํฐํ์ด์ค ๋๋ ํ์ ์ ๋ต์ ๋ณ๊ฒฝํ๊ธฐ ์ ์ ๋ณ๊ฒฝ ๋ฒ์๋ฅผ ์ ํํ๊ฒ ์ง์ ํ ์ ์๋๋ก ํฉ๋๋ค.
๋ฝํ๋ฆฌ(lock-free) ๊ตฌ์ฑ ์์๊ฐ ๊ธฐ์กด ๋ฐฐ์น ์ฒ๋ฆฌ, ๋ฉ์์ง ํ์ ๋ฏธ๋ค์จ์ด ๋ฐ ์ต์ ์๋น์ค ๊ณ์ธต๊ณผ ๊ณต์กดํด์ผ ํ๋ ์ํฐํ๋ผ์ด์ฆ ์์คํ ์ ๊ฒฝ์ฐ, SMART TS XL์ ์ข ์์ฑ ๋งคํ ์ด ๋ฌธ์๋ ์์คํ ์คํ ์ ์ฒด์ ๊ฑธ์ณ ์๋ก ๋ค๋ฅธ ์ธ์ด๋ก ์์ฑ๋ ๊ตฌ์ฑ ์์๋ฅผ ์ฐ๊ฒฐํ๋ ๋์ ๋ฐ์ดํฐ ๊ฒฝ๋ก์ ์ํคํ ์ฒ์ ๊ด์ ์ ์ ๊ณตํฉ๋๋ค.
์ ๊ธ์ฅ์น๊ฐ ์๋ ๊ฒ์ด ์๋ชป๋ ์ ํ์ผ ๋
๋ฝํ๋ฆฌ ๋ฐฉ์์ด ํญ์ ๋ฎคํ ์ค ๋ฐฉ์๋ณด๋ค ๋์ ๊ฒ์ ์๋๋๋ค. ๋ฝํ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์ ๋ฆฌํ์ง๋ ์ค์ ์ํฌ๋ก๋์ ๊ฒฝํฉ ์์์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ์ค๋ ๋ ์๊ฐ ์ ๊ฑฐ๋ ๊ฒฝํฉ์ด ๋ฎ์ ๊ฒฝ์ฐ์๋ ์ ๊ตฌํ๋ ๋ฎคํ ์ค ๋ฐฉ์์ด ์์์ ์ฌ์๋ ๋ฃจํ์ ์ฝ์ด ๊ฐ ์บ์ ๋ผ์ธ ๋ฌดํจํ์ ๋ฐ๋ฅธ ์ค๋ฒํค๋๋ฅผ ์ค์ฌ์ฃผ๊ธฐ ๋๋ฌธ์ ๋ ๋น ๋ฅผ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ๋ฎคํ ์ค๋ฅผ ์ฌ์ฉํ์ญ์์ค:
- ์ค์ ๋ฐ๋๊ฐ ๋ฎ๊ฑฐ๋(4~8๊ฐ ๋ฏธ๋ง) ์ค ๋ญ์นจ ํ์์ด ๋๋ฌผ๋ค
- ์ค์ ๊ตฌ๊ฐ์ ๊ธธ๊ณ ๋ณต์กํฉ๋๋ค(๋จ๋ฉด์ ๋๋น ์ ๊ธ ๋ฐฉ์ง ๋ฃจํ๋ฅผ ์ฌ์ฉํ๋ ๋ฐ ๋น์ฉ์ด ๋ง์ด ๋ญ๋๋ค).
- ์ ํ์ฑ์ด ์ฒ๋ฆฌ๋๋ณด๋ค ๋ ์ค์ํ๋ฉฐ, ๋ฝ์ ์ฌ์ฉํ๋ฉด ์๊ณ ๋ฆฌ์ฆ์ด ๋ ๊ฐ๋จํด์ง๋๋ค.
- ์ด ํ๋ซํผ์ ํจ์จ์ ์ธ futex ๊ธฐ๋ฐ ์ ๊ธ ๊ธฐ๋ฅ์ ๊ฐ์ถ๊ณ ์์ต๋๋ค(๋ฆฌ๋
์ค).
pthread_mutex(์๋์ฐ SRWLOCK)
๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ ์ ๊ธ ํด์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ธ์:
- ์ค์ ๋ฐ๋๊ฐ ๋๊ณ ๊ธด์ฅ๊ฐ์ด ์ง์๋ฉ๋๋ค.
- ์ฐ์ฐ ์๊ฐ์ ์งง์ต๋๋ค (CAS ๋ฃจํ ์ค๋ฒํค๋๊ฐ ์ฐ์ฐ ์๊ฐ์ ๋นํด ์์ต๋๋ค).
- ๋ธ๋กํน์ ํ์ฉ๋์ง ์์ต๋๋ค (์ค์๊ฐ ์ค๋ ๋, ์ธํฐ๋ฝํธ ํธ๋ค๋ฌ, ์๊ทธ๋ ํธ๋ค๋ฌ).
- ๋ฝ์ด ๋ฝ ์์ ์ข ์์ฑ์ ์ ๋ฐํ ์ ์๋ ๋ค๋ฅธ ๋ฝ ํ๋ฆฌ ๊ตฌ์กฐ์ ํจ๊ป ๊ตฌ์ฑํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ ๋๊ธฐ ์๊ฐ ์์ด ์ฌ์ฉํ์ธ์:
- ๋ชจ๋ ์ค๋ ๋๋ ๋ค๋ฅธ ์ค๋ ๋์ ๊ด๊ณ์์ด ์ ํด์ง ์๊ฐ ๋ด์ ์๋ฃ๋์ด์ผ ํฉ๋๋ค.
- ๊ธฐ์ ์ํ๊ฐ ์ฉ๋ฉ๋ ์ ์๋ ๊ธด๊ธํ ์ค์๊ฐ ์๊ตฌ ์ฌํญ ๋๋ ์์ ํ์ ์๊ตฌ ์ฌํญ
- ์ด ์๊ณ ๋ฆฌ์ฆ์ ์ค๋ ๋๋ณ๋ก ์ ํ๋ ๋จ๊ณ ์๋งํผ ์๋ฃ๋๋๋ก ๊ตฌ์ฑ๋ ์ ์์ต๋๋ค.
๋ฝํ๋ฆฌ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ์ ๋ชจ๋ ๊ณณ์์ ๋ฝํ๋ฆฌ๋ฅผ ์ ํํ๋ ๊ฒ์ด ์๋๋ผ, ๋ฝํ๋ฆฌ์ ์์ฑ, ์ฆ ๋น์ฐจ๋จ ์งํ, ๊ต์ฐฉ ์ํ ์ ๊ฑฐ, ์ ์ ํ์ฉ ๋ฑ์ด ์์คํ ์ ์ค์ ์๊ตฌ ์ฌํญ๊ณผ ์ ํํ ์ผ์นํ๋ ๊ณณ์์ ๋ฝํ๋ฆฌ๋ฅผ ์ ํํ๋ ๊ฒ์ ๋๋ค.