[Concurrency] ABA Problem

ABA ๋ฌธ์ œ๋Š” Lock-Free ์•Œ๊ณ ๋ฆฌ์ฆ˜, ํŠนํžˆ Compare and Swap ๊ธฐ๋ฐ˜์˜ ๊ตฌํ˜„์ฒด์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€ํ‘œ์ ์ธ ๋™์‹œ์„ฑ ๋ฒ„๊ทธ ํŒจํ„ด์ด๋‹ค.
์ด์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ์ดํ•ด ์—†์ด๋Š” ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š” Lock-Free ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ non-GC ์–ธ์–ด์—์„œ๋งŒ ๋ฐœ์ƒํ•œ๋‹ค.




๊ธฐ๋ณธ ๊ตฌํ˜„ (Linked Stack)

๋จผ์ € ๊ธฐ๋ณธ์ ์ธ Lock-Free Stack ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค. ์–ธ์–ด๋Š” Rust๊ณ , ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Arc;

struct Node {
    value: i32,
    next: *mut Node,
}

struct Stack {
    head: AtomicPtr<Node>,
}

unsafe impl Sync for Stack {}
unsafe impl Send for Stack {}

impl Stack {
    fn new() -> Self {
        Stack {
            head: AtomicPtr::new(ptr::null_mut()),
        }
    }

    fn push(&self, value: i32) {
        let new_node = Box::into_raw(Box::new(Node {
            value,
            next: ptr::null_mut(),
        }));

        let mut current_head = self.head.load(Ordering::Relaxed);

        loop {
            unsafe {
                (*new_node).next = current_head;
            }

            match self.head.compare_exchange(
                current_head,
                new_node,
                Ordering::Release,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,                   // ์„ฑ๊ณต์ ์œผ๋กœ ๊ต์ฒด๋จ
                Err(head) => current_head = head, // ์‹คํŒจ, ์ƒˆ head ์‚ฌ์šฉํ•ด ์žฌ์‹œ๋„
            }
        }
    }

    fn pop(&self) -> Option<i32> {
        let mut current_head = self.head.load(Ordering::Relaxed);

        loop {
            if current_head.is_null() {
                return None; // ์Šคํƒ์ด ๋น„์–ด ์žˆ์Œ
            }

            let next = unsafe { (*current_head).next };

            match self.head.compare_exchange(
                current_head,
                next,
                Ordering::Release,
                Ordering::Relaxed,
            ) {
                Ok(_) => {
                    let value = unsafe { (*current_head).value };
                    // ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด Box๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์†Œ๋ฉธ
                    let _ = unsafe { Box::from_raw(current_head) };
                    return Some(value);
                }
                Err(head) => current_head = head, // ์‹คํŒจ, ์ƒˆ head ์‚ฌ์šฉํ•ด ์žฌ์‹œ๋„
            }
        }
    }
}

#[tokio::main]
async fn main() {
    let stack = Arc::new(Stack::new());

    // ์ดˆ๊ธฐ ์ƒํƒœ: [1, 2] (์œ„์—์„œ๋ถ€ํ„ฐ)
    stack.push(2);
    stack.push(1);
    println!("์ดˆ๊ธฐ ์Šคํƒ: [1, 2]");

    let a = stack.pop();
    println!("ํŒ: {:?}", a); // 1
    let b = stack.pop();
    println!("ํŒ: {:?}", b); // 2
}

์ผ๋‹จ์€ ์‹ฑ๊ธ€์Šค๋ ˆ๋“œ์—์„œ ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

๊ตฌํ˜„๋„ ๊ฐ„๋‹จํžˆ ํ•œ๋ฒˆ ํ’€์–ด๋ณด๊ฒ ๋‹ค.

์‚ฌ์‹ค ๋ง‰ ๊ทธ๋ ‡๊ฒŒ ํŠน๋ณ„ํ•  ๊ฒƒ์€ ์—†๋‹ค.
Stack์ด๋ผ์„œ Pushํ• ๋•Œ๋Š” ๋งจ ์•ž์˜ head์— ์ƒˆ ๋…ธ๋“œ๋ฅผ ํ• ๋‹นํ•˜๊ณ , ์ƒˆ ๋…ธ๋“œ๊ฐ€ (๊ตฌ) head๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•œ๋‹ค.

๊ทผ๋ฐ Push/Pop์€ ๋™์‹œ์ ์œผ๋กœ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๊ณ  head๊ฐ€ ์–ธ์ œ๋“  ๋ฐ”๋€Œ์–ด์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, compare_exchange(compare and swap)์œผ๋กœ ๋ณธ์ธ์ด ์•Œ๋˜ ์ •๋ณด๊ฐ€ ์ตœ์‹ ์ผ ๋•Œ๋งŒ ์ˆ˜์ •์„ ํ•˜๋„๋ก ํ–ˆ๋‹ค.
์•„๋‹ˆ๋ฉด ๋งž์„๋•Œ๊นŒ์ง€ ๋บ‘๋บ‘์ด๋ฅผ ๋ˆ๋‹ค. ๊ทธ๋ž˜์„œ data race๊ฐ€ ์‹ฌํ•  ๋•Œ๋Š” ๋ฃจํ”„ ๋บ‘๋บ‘์ด๋Œ๋ฉด์„œ CPU๋ฅผ ํ˜น์‚ฌ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

pop๋„ ๋ฐฉ๋ฒ•๋ก ์€ ๋น„์Šทํ•˜๋‹ค.

head๋ฅผ ๋„ฃ๊ณ  ๋ฐ€์–ด๋‚ด๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, head๋ฅผ ๋นผ๊ณ  ๋Œ์–ด๋‹น๊ธฐ๋Š”๊ฒŒ ๋‹ค๋ฅผ ๋ฟ์ด๋‹ค.




ABA ๋ฌธ์ œ

๊ทธ๋Ÿผ ๋ญ๊ฐ€ ๋ฌธ์ œ์ผ๊นŒ? ๊ทธ๋ƒฅ ๋ด์„œ๋Š” ์ž˜ ๋™์ž‘ํ•  ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.
ํ•˜์ง€๋งŒ ์ด๋ฉด์—๋Š” ์ƒ๊ฐ๋ณด๋‹ค ๋‚œ๊ฐํ•œ ๊ทธ.. ๋ญ์‹œ๊ธฐ๊ฐ€ ์กด์žฌํ•ด์„œ ๊ผฌ์ด๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์ด๋ ‡๋‹ค.

  1. ์ฒซ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ pop/push์— ์•ž์„œ head ๋ฐ์ดํ„ฐ๋ฅผ Loadํ•œ๋‹ค. = A(v1)

  2. ์ฒซ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ผ์„ ๋๋‚ด๊ธฐ ์ „์—, ๋‘๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ head๋ฅผ popํ•œ๋‹ค. = (์—†์Œ)

  3. ๋‘๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ B๋ฅผ pushํ•œ๋‹ค. = B

  4. ๋‘๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ A๋ฅผ pushํ•œ๋‹ค. = A(v2)->B

  5. ์ฒซ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ A(v1)์„ A(v2)์™€ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์ง€ ๋ชปํ•ด(๊ฐ€์ •) A(v1)๋ฅผ ์ž˜๋ชป๋œ ํ˜•ํƒœ๋กœ CAS ์ฒ˜๋ฆฌํ•ด๋ฒ„๋ฆฐ๋‹ค.

  6. ์—ฌ๊ธฐ์„œ C๋ฅผ pushํ•  ๊ฒฝ์šฐ์—๋Š” C->A(v1)??, A(v2)->B ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์—ฐ๊ฒฐ์ด ๊ผฌ์—ฌ๋ฒ„๋ฆฐ๋‹ค.

  7. pop์„ ํ•  ๊ฒฝ์šฐ์—๋Š” ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ณ  head๋ฅผ ๋น„์›Œ๋ฒ„๋ฆฌ๋‚˜, A(v2)->B๊ฐ€ ๋ˆ„์ˆ˜๋œ ํ˜•ํƒœ๋กœ ๋ถ• ๋– ๋ฒ„๋ฆฐ๋‹ค.





๊ทธ๋ฆผ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ์ด๋ ‡๋‹ค.

์ฐธ๊ณ ๋กœ A -> B -> A ์ด๋Ÿฐ ์„ค๋ช…์ด ์œ ๋ž˜๊ฐ€ ๋˜์–ด์„œ ABA ๋ฌธ์ œ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

๊ทผ๋ฐ
*์ฒซ๋ฒˆ์งธ ์Šค๋ ˆ๋“œ๊ฐ€ A(v1)์„ A(v2)์™€ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์ง€ ๋ชปํ•ด(๊ฐ€์ •) *



**๋ฌธ์ œ: ๋ฉ”๋ชจ๋ฆฌ ์žฌ์‚ฌ์šฉ **



GC ์–ธ์–ด




์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์ด๊ฒƒ์ €๊ฒƒ ์žˆ์ง€๋งŒ, ๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๊ฒƒ์€ 2๊ฐ€์ง€ ์ •๋„๋‹ค.


ํ•ด๊ฒฐ์ฑ… 1: Tagged Pointer

์ผ๋ฐ˜์ ์ธ ํ•ด๊ฒฐ์ฑ… ์ค‘ ํ•˜๋‚˜๋Š”, ์•„์˜ˆ ์ฃผ์†Œ ๊ณต๊ฐ„์—๋‹ค๊ฐ€ ์ถ”๊ฐ€์ ์ธ ํƒœ๊ทธ๊ฐ’์„ ๋„ฃ์–ด์„œ ํ• ๋‹น๋˜๊ฑฐ๋‚˜ ์ˆ˜์ •๋ ๋•Œ๋งˆ๋‹ค ๊ฐ’์ด ๊ณ ์œ ์„ฑ์„ ๊ฐ€์ง€๊ฒŒ๋” ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์‹ค์ œ ์ฃผ์†Œ ๊ณต๊ฐ„์ด ๊ฐ™๋”๋ผ๋„, ์ผ๋ถ€ ๋น„ํŠธ๊ฐ€ ๋‹ฌ๋ผ์„œ CAS ์—ฐ์‚ฐ์ด ์‹คํŒจํ•  ๊ฒƒ์ด๋‹ค.
์–ด์ฐจํ”ผ CAS๋Š” ์ฃผ์†Œ ์ž์ฒด๋ฅผ ์˜๋ฏธ์žˆ๊ฒŒ ๋ณด๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ฃผ์†Œ๋ผ๋Š” ์ •์ˆ˜๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

64๋น„ํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ๋น„ํŠธ ๊ณต๊ฐ„์ด ์ข€ ๋‚จ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ผผ์ˆ˜๋‹ค. ๋ณดํ†ต 10-20๋น„ํŠธ ์ •๋„๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋กœ ์‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.




ํ•ด๊ฒฐ์ฑ… 2: ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ํ•ด์ œ ์ง€์—ฐ์‹œํ‚ค๊ธฐ

ABA ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์›์ธ ์ค‘ ํ•˜๋‚˜๋Š”, ํ• ๋‹น์ด ํ•ด์ œ๋œ ๋’ค์— ๊ทธ ์ฃผ์†Œ๋ฅผ ๋‹ค์‹œ ์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋Ÿฌ๋ฉด... ํ•ด์ œ๋ฅผ ์•ˆ์‹œํ‚ค๋ฉด ๊ทธ ์ฃผ์†Œ๋ฅผ ๋‹ค์‹œ ์•ˆ์ฃผ๋‹ˆ๊นŒ ๋˜๋Š”๊ฑฐ ์•„๋‹Œ๊ฐ€?

๊ทธ๋ž˜์„œ ์•„์˜ˆ ๊ฐ์ฒด์˜ ์‚ญ์ œ๋ฅผ ๋ฏธ๋ค„์„œ ์žฌ์‚ฌ์šฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๋ช‡๊ฐ€์ง€ ์กด์žฌํ•œ๋‹ค.


**Hazard Pointer **

๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์ง€์—ฐ์˜ ๋Œ€ํ‘œ์ ์ธ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์ด๋‹ค.
ํ˜„์žฌ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”, ํ•ด์ œ๋˜์ง€ ๋ง์•„์•ผ ํ•˜๋Š” ์ฃผ์†Œ ๋ชฉ๋ก์„ Hazard Pointer๋ผ๊ณ  ํ•ด์„œ ์ค‘์•™ํ™”ํ•ด์„œ ๊ด€๋ฆฌํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  Hazard Pointer ๋ชฉ๋ก์— ์†ํ•ด์žˆ๋Š” ์ฃผ์†Œ๋Š” ํ• ๋‹นํ•ด์ œ๋ฅผ ํ•˜์ง€ ์•Š๊ฒŒ๋” ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

https://chfhrqnfrhc.tistory.com/entry/ABA-Problem-1
๊ทผ๋ฐ ๊ทธ๋ƒฅ ํ•ด์ œ๋ฅผ ๋ฏธ๋ฃจ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ˆ„์ˆ˜๊ฐ€ ๋ ํ…Œ๋‹ˆ, RetireList๋ผ๋Š” ํ•ด์ œ์šฉ ํ’€์„ ๋˜ ์Šค๋ ˆ๋“œ๋ณ„๋กœ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ฆฌํ•œ๋‹ค.
pop์œผ๋กœ ์ธํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๊ทธ๊ฑธ ํ•ด์ œํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๊ฐ์ฒด๋ฅผ RetireList๋กœ ์˜ฎ๊ธด ๋‹ค์Œ์— Hazard Pointer ๋ฆฌ์ŠคํŠธ์™€ ๋น„๊ตํ•ด์„œ ๊ดœ์ฐฎ์œผ๋ฉด lazyํ•˜๊ฒŒ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ถ”๊ฐ€์ ์ธ ์„ฑ๋Šฅ ์†Œ๋ชจ๊ฐ€ ์ข€ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ์ง€๋งŒ, ABA์™€ ๋ฉ”๋ชจ๋ฆฌ ์žฌ์‚ฌ์šฉ ๋ฌธ์ œ๋Š” ํšŒํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค.



์ฐธ์กฐ
https://chfhrqnfrhc.tistory.com/m/entry/ABA-Problem-1
https://minikin.me/blog/solving-the-aba-problem-in-rust-tagged-pointers
https://en.wikipedia.org/wiki/ABA_problem
https://en.wikipedia.org/wiki/Hazard_pointer
https://chfhrqnfrhc.tistory.com/entry/ABA-Problem-1