[Concurrency] Atomic Operation: Memory Fence์ Memory Ordering
์ด ํฌ์คํธ๋ atomic ์ฐ์ฐ์์ ๊ณ ๋ คํด์ผํ๋ ๋ฉ๋ชจ๋ฆฌ ์์์ ๋ํด ๋ค๋ฃฌ๋ค.
์์ ์ฝ๋๋ Rust์ง๋ง, Rust๋ฅผ ๋ชฐ๋ผ๋ ๋ด์ฉ์ ์ดํดํ ์ ์์ ๊ฒ์ด๋ค.
๋ฉ๋ชจ๋ฆฌ ํ์ค(Fence)์ ์์(Ordering)๋ Atomic operation ๊ธฐ๋ฐ์ ๋์์ฑ ์ฒ๋ฆฌ์ ์์ด์ ๊ฐ์ฅ ์ค์ํ ํต์ฌ ์์๋ค. ์ด๊ฑธ ์ ๋๋ก ์์ง ๋ชปํ๊ณ ์ด๋ค๋ฉด Atomic์ ์ฐ๋ ์๋ฏธ๊ฐ ์ ํ ์๋ค.
Out Of Order ํ์
fence๋ผ๋ ๊ฒ์ด ์ ํ์ํ์ง๋ฅผ ์๋ ค๋ฉด, CPU์ ๊ตฌ์กฐ์ ์ต์ ํ ๊ธฐ๋ฒ์ ๋ํด ์์์ผ ํ๋ค.
ํ๋์ ์ปดํ์ผ๋ฌ๋ค์ ํ์์ ์ผ๋ก ์ธ์คํธ๋ญ์
์์ค์ ๋น ๋ฅธ ๋ณ๋ ฌ์ฒ๋ฆฌ๋ฅผ ์ํด ํ์ดํ๋ผ์ด๋(Pipelining)์ด๋ผ๋ ์ต์ ํ๋ฅผ ์ํํ๋ค.
๊ทธ๋ฌ๋ ค๋ฉด ์ธ์คํธ๋ญ์ ๋ค์ ๋น ๋ฅด๊ฒ ๋ณ๋ ฌ๋ก ๋๋ฆฌ๊ธฐ ์ํด์ ๋ช ๋ น์ด์ ์์๋ฅผ ๋ง์๋๋ก ์กฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
๋ง์ฝ ์ฝ๋๊ฐ ๋ค์์ ์์๋ก ์ ์๋์๋๋ผ๋
-
C = A+B
-
X = Y+Z
์ปดํ์ผ๋ฌ๊ฐ ์ด๊ฑธ ๋ค์ง์ด๋ฒ๋ฆด ์๋ ์๋ ๊ฒ์ด๋ค.
-
X = Y+Z
-
C = A+B
์ด๊ฑด ์ฑ๊ธ์ค๋ ๋ ํ๊ฒฝ์์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ๋์์ฑ ํ๊ฒฝ์์๋ ๋ฌธ์ ๊ฐ ๋๋ค.
ํ์ดํ๋ผ์ด๋ ์ธ์๋ ์ปดํ์ผ๋ฌ๋ ํ๋์จ์ด ์์ค ์ต์ ํ์์ ์ด๋ ๊ฒ ์์๋ฅผ ๊ผฌ์๋๋ ์์ธ์ ๋ช๊ฐ์ง ๋ ์๋ค.
Memory Fence
๊ทธ๋์ ์ ๋ ๊ฒ ์์๊ฐ ๋ง๊ฐ์ง๋ ์ํฉ ์์์ ์ต์ํ์ ์์ ์ฅ์น๋ฅผ ๋ง๋ จํ๋ ๊ฒ์ ๋ฉ๋ชจ๋ฆฌ ํ์ค๋ผ๊ณ ๋ถ๋ฅธ๋ค.
Arm์์๋ ์ด๊ฑธ Memory Barrier๋ผ๊ณ ๋ถ๋ฅธ๋ค.
x86์์๋ sfence/mfence/lfence์ ๊ฐ์ ๋ช ๋ น์ด๋ฅผ ํตํด ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ , ๋ค๋ฅธ ํ๋ก์ธ์๋ค๋ ๋ ์์ ์ธ ์ผ๋ จ์ ๋ช ๋ น์ด๋ก ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์ฌ๊ธฐ์ ์ค์ํ ์ ์, ๊ณ ๊ธ ์ธ์ด์์ atomic ์ฐ์ฐ๋ค์ ๋ค๋ฃฐ ๋์๋ ์ด๋ฌํ ๊ฐ๋
์ ์ด๋์ ๋ ์๊ณ ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
๋๋ค์์ ์ถ์ํ๋ atomic operation ํจ์๋ค์, Ordering์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง๋ฅผ ์ง์ ํ๋๋ก ๋์ด์๋ค.
Ordering ์ต์ ์ ์ธ์ด๋ฅผ ๋ถ๋ฌธํ๊ณ ๋ณดํต ์๋์ ๊ฐ์ ํํ๋ก ์ ์๋๋ค.
- Relaxed: ์๋ฌด ์ ์ฝ ์์
- Acquire: ์ด ๋ช ๋ น ์ดํ์ ์ ์ธ๋ read๊ฐ ์ ๋ ์ด ๋ช ๋ น ์ด์ ์ ์คํ๋์ง ์์.
- Release: ์ด ๋ช ๋ น ์ด์ ์ ์ ์ธ๋ write๊ฐ ์ ๋ ์ด ๋ช ๋ น ์ดํ์ ์คํ๋์ง ์์.
- AcqRel: ์ฝ๊ธฐ๋ Acquire, ์ฐ๊ธฐ๋ Release๋ก ์ฒ๋ฆฌ
- SecCst: ์ด ๋ช ๋ น ์๋ค์ read/write ๋ช ๋ น์ ์์๋ฅผ ์๊ฒฉํ๊ฒ ์๋ค ๊ทธ๋๋ก ์ ์ง
๊ทธ๋์ ์ฌ๋ฌ๊ฐ์ atomic์ผ๋ก ๋์์ ์ฝ๊ณ ์ฐ๋ ์์
์ ํ ๋๋ Acquire, ์ฐ๊ธฐ ์์
์ ํ ๋๋ Release๋ฅผ ์จ์ ์ธ์คํธ๋ญ์
์์๊ฐ ๊ผฌ์ด์ง ์๊ฒ ํ๋ ๊ฒ์ด ๊ธฐ๋ณธ์ด๋ค.
ํ์ง๋ง ๊ฐ atomic์ด ๋
๋ฆฝ์ ์ด๊ณ ์๋ก ์ฐธ์กฐ๊ด๊ณ๊ฐ ์๋ค๋ฉด Relaxed๋ง ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ์ฅ ๋น ๋ฅด๋ค.
SecCst๋ ๊ฐ์ฅ ์๊ฒฉํ Ordering์ธ๋ฐ, ์ด๊ฑธ ์จ์ผ ํ๋ ์ํฉ์ด ์จ๋ค๋ฉด ๋ญ๊ฐ ์ฝ๋๋ฅผ ์๋ชป ์ง ๊ฒ ์๋์ง๋ฅผ ๋จผ์ ๊ณ ๋ฏผํด๋ด์ผ ํ๋ค. ์ผ๋ฐ์ ์ธ ์ํฉ์์๋ ์ด๊ฑธ ์ฌ์ฉํ ์ผ์ด ๊ฑฐ์ ์๊ณ , ์ฐ๋๋ผ๋ Atomic์ ์ฐ๋ ์๋ฏธ๊ฐ ์๋ค.
ํ๋ฒ ์ฝ๋์ ํจ๊ป ๋์ ์๋ฆฌ์ ํ์์ฑ์ ๊ฐ๋จํ ์ค๋ช ํด๋ณด๊ฒ ๋ค.
Relaxed Ordering
Relaxed Ordering์ ๊ฐ์ฅ ๋จ์ํ ๊ตฌ์กฐ์ memory ordering์ด๋ค. ๋จ์ํ ์ฐ์ฐ์ด atomicํ๊ฒ ์คํ๋๋ค๋ ๊ฒ๋ง์ด ๋ณด์ฅ๋๋ค. ๋จ์ํ ๋งํผ ๋น ๋ฅด์ง๋ง, ์ฌ๋ฌ๊ฐ์ atomic operation์ด ์์ ๋ ๊ทธ ์คํ ์์๊ฐ ๋ณด์ฅ๋์ง ์๋๋ค๋ ๋ฌธ์ ๊ฐ ์๋ค.
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ฒ ๋ค.
use std::sync::{atomic::AtomicI64, Arc};
fn main() {
let x = Arc::new(AtomicI64::new(0));
let y = Arc::new(AtomicI64::new(0));
let thread1 = {
let x = x.clone();
let y = y.clone();
std::thread::spawn(move || {
let y_value = y.load(std::sync::atomic::Ordering::Relaxed);
x.store(y_value, std::sync::atomic::Ordering::Relaxed);
println!("y_value = {}", y_value);
})
};
let thread2 = {
let x = x.clone();
let y = y.clone();
std::thread::spawn(move || {
let x_value = x.load(std::sync::atomic::Ordering::Relaxed);
y.store(4444, std::sync::atomic::Ordering::Relaxed);
println!("x_value = {}", x_value);
})
};
thread1.join().unwrap();
thread2.join().unwrap();
}
x์ y์ ์ด๊ธฐ๊ฐ์ 0์ด๋ค.
์ฒซ๋ฒ์จฐ ์ค๋ ๋์์๋ y์ ๊ฐ์ ๊ฐ์ ธ์์ x์ ํ ๋นํ๊ณ
๋๋ฒ์งธ ์ค๋ ๋์์๋ x์ ๊ฐ์ ๊ฐ์ ธ์ค์ง๋ง ์ฌ์ฉํ์ง๋ ์๊ณ , y์ 4444๋ผ๋ ์ซ์๊ฐ์ ํ ๋นํ๋ค.
์ด๊ฑด ์ค์ ์ค๋ ๋ ์คํ ์์์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ง๋ง, x_value ๋ณ์๋ ๋ณดํต 0 ๊ฐ์ ๊ฐ์ง ๊ฒ์ด๋ผ ๊ธฐ๋ํ ๊ฒ์ด๋ค.
์๋ํ๋ฉด x ๊ฐ์ y ๊ฐ์ ์์กด์ ์ธ๋ฐ, ๋๋ฒ์งธ ์ค๋ ๋์์๋ x ๊ฐ์ ๋จผ์ ๊ฐ์ ธ์จ ๋ค์์ y์ ๊ฐ์ ํ ๋นํ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฐ๋ฐ, Relaxed Ordering์ ์ฌ์ฉํ ๋๋ x_value๊ฐ 4444๋ฅผ ๊ฐ์ง๋ ์ด์ํ ์ํฉ์ด ์๊ธธ ์๋ ์๋ค.
Relaxed Ordering์ ์ค์ ๋์์ ํ๋ซํผ์ ๋ฐ๋ผ ํฌ๊ฒ ๋ค๋ฅด๋ค.
- ์ ์ด์ strong order๋ฅผ ์งํฅํ๋ ํ๋ซํผ(x86 ๋ฑ)์์๋ Relaxed๋ฅผ ์ฌ์ฉํ๋๋ผ๋ Release-Acquire ๋ฐฉ์๊ณผ ๊ฑฐ์ ๊ฐ๊ฒ ๋์ํด์ ์ฑ๋ฅ ์ด์ ์ด ๊ฑฐ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์์ ๊ฐ์ ๋ถ์์ฉ๋ ์์ ๊ฒ์ด๋ค.
- ๋ฐ๋ฉด weak order๋ฅผ ์งํฅํ๋ ํ๋ซํผ(arm ๋ฑ)์์๋ Relaxed๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฐ์ ์ฑ๋ฅ ํฅ์์ด ์์ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์์์ ์ค๋ช ํ ๋ถ์์ฉ๋ ํจ๊ป ๋ฐ์ํ ๊ฒ์ด๋ค.
Release-Acquire Ordering
์ด๊ฒ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ํํ์ Ordering์ด๋ค.
์ด 2๊ฐ์ Ordering์ ๋ค์๊ณผ ๊ฐ์ ์ต์ ํ ๋ฐฉ์ง๋ฅผ ๋ณด์ฅํ๋ค.
-
Acquire๊ฐ ์๋ค๋ฉด, Acquire ์ดํ์ ๋ฐ์ํ ์ฐ์ฐ์ ํญ์ ๊ทธ ์ดํ์ ์คํ๋๋ ๊ฒ์ด ๋ณด์ฅ๋๋ค.
-
Release๊ฐ ์๋ค๋ฉด, Release ์ด์ ์ ๋ฐ์ํ ์ฐ์ฐ์ ํญ์ ๊ทธ ์ด์ ์ ์คํ๋๋ ๊ฒ์ด ๋ณด์ฅ๋๋ค.
๊ทธ๋์ Relaxed์์ ๋ฐ์ํ๋ ์์ ๊ผฌ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด, ์๋์ ๊ฐ์ด Release-Acquire๋ฅผ ์ ์ฉํ ์ ์๋ค.
๋ณดํต atomic์ผ๋ก read/write ์์
์ ํ๋ฉด ์ฝ์ด์ค๊ณ -์ฐ๋ ์์๊ฐ ๋๋๊น, load์ Acquire๋ฅผ ์ฃผ๊ณ store์ Release๋ฅผ ์ฃผ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
SeqCst Ordering
๊ฐ์ฅ ๊ฐ๋ ฅํ ํํ์ Ordering์ด๋ค.
์ด๊ฑธ ์ฌ์ฉํ๋ฉด ํด๋น ์ฐ์ฐ ์๋ค์ ๋ฌ๋ ค์๋ ์ฐ์ฐ๋ค์ ์์๊ฐ ์ ๋ถ ๊ณ ์ ๋๋ค. ๋ชจ๋ ์ค๋ ๋์์ ์ผ๊ด๋ ๋จ์ผ ์์๋ฅผ ๊ฐ์ํ๋ ๊ฒ์ด๋ค.
์ด๊ฑด ์ด๋ค ํ๋ซํผ์์ ์ฌ์ฉํ๋ ๋นํจ์จ์ ์ธ ์ฝ๋ ์ต์ ํ๋ฅผ ๋ณ๋๋ค. memory fence ์ธ์คํธ๋ญ์
์ด ์ฌ๋ฐฉ์ ๋ก์น ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋์ ์ค์ ์ฌ์ฉ์ ์์ ํ๋ ํธ์ด ์ข๋ค. ๋์ฒด๋ก๋ Release-Acquire๋ง์ผ๋ก๋ ์ถฉ๋ถํ๋ค.
Release-Acquire๋ก๋ ์๋๋ค๋ฉด ๋ญ๊ฐ ์ค๊ณ๊ฐ ์๋ชป๋์๋ค๊ณ ๋ณด๋ ํธ์ด ํ๋นํ๋ค.
๋ค๋ฅธ ์ค๋ ๋ ๊ฐ์ ์์ ๊ผฌ์ ๊ด์ธก
์ด๊ฑด ์ฌ๋ฌ ์ค๋ ๋์์ ์ฌ๋ฌ๊ฐ์ atomic operation์ ๊ต์ฐจํด์ ์ฌ์ฉํ ๋ ๋ฐ์ํ ์ ์๋ ์ฃ์ง์ผ์ด์ค๋ฅผ ๋ค๋ฃฌ๋ค.
์๋๋ ๊ทธ๋ฅ 2๊ฐ์ ์ค๋ ๋์์ ๊ฐ๊ฐ ๋ง์ ์ ์ํํ๋ ์ฝ๋๋ค. ๋ณ๊ฑด ์๋ค.
๊ทธ๋ฅ ์ด๋ฐ ๋๋์ผ๋ก ๋จ์ผ atomic์ ์ธ๋๋ ์๋ฌด ๋ฌธ์ ๊ฐ ์๊ณ , ์ฌ๋ฌ๊ฐ์ atomic์ ๋์์ ์ฐ๋๋ผ๋ ํฐ ๋ฌธ์ ๋ ์๋ค.
ํ์ง๋ง ์๋ฅผ ๋ค์ด ์ด๋ฐ์์ผ๋ก atomic์ ํธ๋ค๋งํ๋ฉด
์ฐ๋ฆฌ๋ number์ store๊ฐ ๋จผ์ ์คํ๋ ์ดํ์ number2์ store๊ฐ ์คํ๋ ๊ฒ์ด๋ผ๊ณ ๊ธฐ๋ํ ๊ฒ์ด๋ค.
๋ณดํต์ ๊ทธ๊ฒ ๋ง๋ค. ๋จ์ผ ์ค๋ ๋์์๋ ๊ทธ๋ ๊ฒ ๋์ํ๊ธฐ๋ ํ๋ค.
๋ฌธ์ ๋ ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์๋ ๊ทธ๊ฒ ๋ณด์ฅ๋์ง ์๋๋ค๋ ๊ฒ์ด๋ค.
๊ทธ๋์ ์๋์ ์ผ๋ก ์ฌํํ๊ธฐ๋ ๊ฝค ์ด๋ ต์ง๋ง, thread2์์๋ number๊ฐ 0์ด๋ฉด์ number2๊ฐ 20์ธ ๊ธฐ๋ฌํ ์ํฉ์ ๊ด์ธกํ ์๋ ์๋ค.
์๋๋ ์์ ๊ผฌ์ ํ์์ ์ฌํํ ์ ์๋ ์์ ์ฝ๋๋ค. ์คํํด๋๊ณ ์ข ๊ธฐ๋ค๋ฆฌ๋ฉด ํ์์ ๊ด์ธกํ ์๋ ์์ ๊ฒ์ด๋ค.
use std::sync::{atomic::AtomicI64, Arc};
fn main() {
loop {
let barrier = Arc::new(std::sync::Barrier::new(2));
let number = Arc::new(AtomicI64::new(0));
let number2 = Arc::new(AtomicI64::new(0));
let thread1 = {
let number = number.clone();
let number2 = number2.clone();
let barrier = barrier.clone();
std::thread::spawn(move || {
barrier.wait();
number.store(10, std::sync::atomic::Ordering::Relaxed);
number2.store(20, std::sync::atomic::Ordering::Relaxed);
})
};
let thread2 = {
let number = number.clone();
let number2 = number2.clone();
let barrier = barrier.clone();
std::thread::spawn(move || {
barrier.wait();
let number = number.load(std::sync::atomic::Ordering::Relaxed);
let number2 = number2.load(std::sync::atomic::Ordering::Relaxed);
if number == 0 && number2 == 20 {
println!("number = {}", number);
println!("number2 = {}", number2);
}
})
};
thread1.join().unwrap();
thread2.join().unwrap();
}
}
ํ๋ซํผ์ด๋ ํ๋ก์ธ์, ์ต์ ํ ์์ค์ ๋ฐ๋ผ์ ์ฌํ๋ฅ ์ ๋ฌ๋ผ์ง ์ ์๋ค.
์ด๊ฑด ์๋ก ๋ค๋ฅธ CPU ์บ์์ ๋ด๋ถ ๋ฒํผ๊ฐ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ์ ๋ํด ์๋ก ๋ค๋ฅธ ๊ฐ์ ๋ณด์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค.
์ฆ, ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ค๋๋ ๊ฐ์ ๋ณผ ์ ์๋ค๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ number์ number2๋ ์ ์ฅ๋๊ธด ํ์ง๋ง ์์ง ๋ค๋ฅธ ์ค๋ ๋๋ก ์ ํ๋์ง๋ ์์ ๊ฒ์ด๋ค. ์บ์ ์ ํ ์์๋ ์ ์ฅ ์์์ ๋ค๋ฅผ ์ ์๋ค.
์ด ๋ฌธ์ ๋ ๋น๋จ memory ordering์ ํ์ ๋ ๋ฌธ์ ๋ ์๋๋ค. ordering์ ๋ฐ๊พธ๋๋ผ๋ ๊ทผ๋ณธ์ ์ธ ํด๊ฒฐ์ ํ ์๋ ์๊ณ , ์ด๋ฌํ ์ํฉ์ด ๋ฌธ์ ๊ฐ ๋ ์ ์๋ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด๋ mutex, lock ๊ฐ์ ์๋จ์ผ๋ก ๋น๊ด์ ์ธ ๋ฝ์ ์ก์์ผ ํ๋ค.
์ฐธ์กฐ
https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%B0%B0%EB%A6%AC%EC%96%B4
https://velog.io/@codingskynet/C11-Memory-Model-Atomic%EB%B6%80%ED%84%B0-Lock-Free-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EA%B9%8C%EC%A7%80
https://stackoverflow.com/questions/55680665/how-to-understand-relaxed-ordering-in-stdmemory-order-c
https://doc.rust-lang.org/nomicon/atomics.html
https://preshing.com/20120930/weak-vs-strong-memory-models/