๊ณ ๋™์‹œ์„ฑ ์‹œ์Šคํ…œ์—์„œ ์ž ๊ธˆ ์—†๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๊ตฌํ˜„

๋ฝํ”„๋ฆฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ: C++, Java ๋ฐ .NET ์˜ˆ์ œ๋ฅผ ํฌํ•จํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๋ฎคํ…์Šค๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ž ๊ทธ๊ณ , ๊ณต์œ  ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ , ๋‹ค์‹œ ์ž ๊ธˆ์„ ํ•ด์ œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ ‘๊ทผ์„ ์›ํ•˜๋Š” ๋ชจ๋“  ์Šค๋ ˆ๋“œ๋Š” ์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๋ฐ”๋กœ ์ด "์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ"์ž…๋‹ˆ๋‹ค. ์ฝ”์–ด ์ˆ˜๊ฐ€ ๋งŽ๊ณ  ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๋†’์€ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ง๋ ฌํ™” ์ž์ฒด๊ฐ€ ํ•œ๊ณ„๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ž ๊ธˆ์„ ๋ณด์œ ํ•œ ์Šค๋ ˆ๋“œ๊ฐ€ ์„ ์ ๋˜๋ฉด ๋‹ค๋ฅธ ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ์œ ํœด ์ƒํƒœ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ์„ ์ˆœ์œ„ ๋ฐ˜์ „์œผ๋กœ ์ธํ•ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์Šค๋ ˆ๋“œ๊ฐ€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์Šค๋ ˆ๋“œ๋ฅผ ์ฐจ๋‹จํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์‹ญ ๊ฐœ์˜ ์ฝ”์–ด์—์„œ ์ดˆ๋‹น ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ์—ฐ์‚ฐ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์—์„œ๋Š” ๋ฎคํ…์Šค ๊ฒฝํ•ฉ์ด ๋‹จ์ˆœํžˆ ์†๋„๋ฅผ ์ €ํ•˜์‹œํ‚ค๋Š” ๊ฒƒ์„ ๋„˜์–ด ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๊ธ‰๊ฒฉํžˆ ๋–จ์–ด๋œจ๋ฆฝ๋‹ˆ๋‹ค.

๋™์‹œ์„ฑ ๋…ผ๋ฆฌ ๋ถ„์„

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:

  1. ์Šค๋ ˆ๋“œ A๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. top = Node1Node1์˜ ๋‹ค์Œ ํฌ์ธํ„ฐ๋Š” Node2์ž…๋‹ˆ๋‹ค.
  2. ์Šค๋ ˆ๋“œ A๋Š” ์‹คํ–‰์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— ์„ ์ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  3. ์Šค๋ ˆ๋“œ B๋Š” Node1์„ ํŒํ•˜๊ณ (top์€ Node2๊ฐ€ ๋จ), ๊ทธ ๋‹ค์Œ Node2๋ฅผ ํŒํ•ฉ๋‹ˆ๋‹ค(top์€ null์ด ๋จ).
  4. ์Šค๋ ˆ๋“œ B๊ฐ€ ์ƒˆ ๋…ธ๋“œ๋ฅผ ํ‘ธ์‹œํ•˜๋Š”๋ฐ, ์ด ๋…ธ๋“œ๋Š” Node1๊ณผ ๋™์ผํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค(์ž์œ  ๋ชฉ๋ก ํ• ๋‹น์ž์—์„œ ํ”ํžˆ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ˜„์ƒ). Top = Node1(๋™์ผํ•œ ํฌ์ธํ„ฐ, ๋‹ค๋ฅธ ๋‚ด์šฉ).
  5. ์Šค๋ ˆ๋“œ 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 ๋ฃจํ”„ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—ฐ์‚ฐ ์‹œ๊ฐ„์— ๋น„ํ•ด ์ž‘์Šต๋‹ˆ๋‹ค).
  • ๋ธ”๋กœํ‚น์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์‹ค์‹œ๊ฐ„ ์Šค๋ ˆ๋“œ, ์ธํ„ฐ๋ŸฝํŠธ ํ•ธ๋“ค๋Ÿฌ, ์‹œ๊ทธ๋„ ํ•ธ๋“ค๋Ÿฌ).
  • ๋ฝ์ด ๋ฝ ์ˆœ์„œ ์ข…์†์„ฑ์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ๋ฝ ํ”„๋ฆฌ ๊ตฌ์กฐ์™€ ํ•จ๊ป˜ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์—†์ด ์‚ฌ์šฉํ•˜์„ธ์š”:

  • ๋ชจ๋“  ์Šค๋ ˆ๋“œ๋Š” ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์™€ ๊ด€๊ณ„์—†์ด ์ •ํ•ด์ง„ ์‹œ๊ฐ„ ๋‚ด์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์•„ ์ƒํƒœ๊ฐ€ ์šฉ๋‚ฉ๋  ์ˆ˜ ์—†๋Š” ๊ธด๊ธ‰ํ•œ ์‹ค์‹œ๊ฐ„ ์š”๊ตฌ ์‚ฌํ•ญ ๋˜๋Š” ์•ˆ์ „ ํ•„์ˆ˜ ์š”๊ตฌ ์‚ฌํ•ญ
  • ์ด ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์Šค๋ ˆ๋“œ๋ณ„๋กœ ์ œํ•œ๋œ ๋‹จ๊ณ„ ์ˆ˜๋งŒํผ ์™„๋ฃŒ๋˜๋„๋ก ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฝํ”„๋ฆฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ์€ ๋ชจ๋“  ๊ณณ์—์„œ ๋ฝํ”„๋ฆฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฝํ”„๋ฆฌ์˜ ์†์„ฑ, ์ฆ‰ ๋น„์ฐจ๋‹จ ์ง„ํ–‰, ๊ต์ฐฉ ์ƒํƒœ ์ œ๊ฑฐ, ์„ ์  ํ—ˆ์šฉ ๋“ฑ์ด ์‹œ์Šคํ…œ์˜ ์‹ค์ œ ์š”๊ตฌ ์‚ฌํ•ญ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š” ๊ณณ์—์„œ ๋ฝํ”„๋ฆฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.