<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[MaxLog]]></title><description><![CDATA[MaxLog]]></description><link>https://maximizemaxwell.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1737026808790/a38a0805-4e60-4a3c-a0ca-c41787a572d1.png</url><title>MaxLog</title><link>https://maximizemaxwell.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 21:25:39 GMT</lastBuildDate><atom:link href="https://maximizemaxwell.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[락프리 데이터 구조와 알고리즘]]></title><description><![CDATA[여기서는 락프리 데이터 구조를 설명한다. 락프리(lock-free) 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다.
왜 락프리인가?
전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다:

성능 저하: 락 경합(lock contention)으로 인한 대기 시간

데드락: 여러 스레드가 서로의 락을 기다리는 상황

우선순위 역전: 낮은 우선순위 스레드가 높은 우선순위 스레드를 ...]]></description><link>https://maximizemaxwell.com/lockfree</link><guid isPermaLink="true">https://maximizemaxwell.com/lockfree</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 27 Jul 2025 11:56:07 GMT</pubDate><content:encoded><![CDATA[<p>여기서는 락프리 데이터 구조를 설명한다. <strong>락프리(lock-free)</strong> 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다.</p>
<h2 id="heading-7jmcioudve2uhoumroyduoqwgd8">왜 락프리인가?</h2>
<p>전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다:</p>
<ul>
<li><p><strong>성능 저하</strong>: 락 경합(lock contention)으로 인한 대기 시간</p>
</li>
<li><p><strong>데드락</strong>: 여러 스레드가 서로의 락을 기다리는 상황</p>
</li>
<li><p><strong>우선순위 역전</strong>: 낮은 우선순위 스레드가 높은 우선순위 스레드를 블로킹</p>
</li>
<li><p><strong>컨텍스트 스위칭</strong>: 락 대기 중 발생하는 오버헤드</p>
</li>
</ul>
<p>락프리 알고리즘은 이러한 문제들을 아토믹한 연산을 통해 해결한다.</p>
<hr />
<h1 id="heading-65297zse66asioykpo2dnq">락프리 스택</h1>
<p>락프리 스택은 선두 요소에 대한 push와 pop 조작만 가진 리스트로 구성된다. 가장 대표적인 구현은 Treiber 스택이다.</p>
<p>참고 자료: <a target="_blank" href="https://en.wikipedia.org/wiki/Treiber_stack">위키피디아-Treiber 스택</a></p>
<h2 id="heading-treiber">Treiber 스택</h2>
<p>Treiber 스택은 락프리 스택 알고리즘의 하나로 여러 스레드가 동시에 접근해도 락 없이 안전하게 동작하는 스택이다.</p>
<p>한 문장으로 아이디어를 요약하자면 이렇다.</p>
<p><strong>내가 작업을 시작했을 때의 상태가 그대로라면 내 작업을 진행한다. 누군가가 중간에 이를 바꿔 상태가 바뀌었다면 처음부터 다시 시도한다.</strong></p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::ptr::null_mut;
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicPtr, Ordering};

<span class="hljs-comment">/// Lock-free 스택의 노드 구조체</span>
<span class="hljs-comment">/// </span>
<span class="hljs-comment">/// 단일 연결 리스트의 노드로, 각 노드는 다음 노드에 대한 원자적 포인터와</span>
<span class="hljs-comment">/// 실제 데이터를 포함한다.</span>
<span class="hljs-comment">/// </span>
<span class="hljs-comment">/// # Type Parameters</span>
<span class="hljs-comment">/// * `T` - 스택에 저장될 데이터의 타입</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Node</span></span>&lt;T&gt; {
    <span class="hljs-comment">/// 다음 노드를 가리키는 원자적 포인터</span>
    <span class="hljs-comment">/// AtomicPtr을 사용하여 동시성 환경에서 안전한 포인터 조작을 보장</span>
    next: AtomicPtr&lt;Node&lt;T&gt;&gt;,
    <span class="hljs-comment">/// 노드가 저장하는 실제 데이터</span>
    data: T,
}

<span class="hljs-comment">/// Lock-free 스택 구현</span>
<span class="hljs-comment">/// </span>
<span class="hljs-comment">/// Treiber 스택 알고리즘을 사용한 락프리 스택 알고리즘</span>
<span class="hljs-comment">/// 여러 스레드가 동시에 push/pop 작업을 수행 가능</span>

<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">LockFreeStack</span></span>&lt;T&gt; {
    <span class="hljs-comment">/// 스택의 최상단 노드를 가리키는 아토믹 포인터</span>
    head: AtomicPtr&lt;Node&lt;T&gt;&gt;,
}

<span class="hljs-keyword">impl</span>&lt;T&gt; LockFreeStack&lt;T&gt; {
    <span class="hljs-comment">/// 새로운 빈 스택을 생성</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # Returns</span>
    <span class="hljs-comment">/// 빈 lock-free 스택 인스턴스</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>() -&gt; <span class="hljs-keyword">Self</span> {
        LockFreeStack {
            head: AtomicPtr::new(null_mut()),
        }
    }

    <span class="hljs-comment">/// 스택에 새로운 요소를 추가</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # Arguments</span>
    <span class="hljs-comment">/// * `v` - 추가할 데이터</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # Algorithm</span>
    <span class="hljs-comment">/// 1. 새 노드를 생성</span>
    <span class="hljs-comment">/// 2. CAS(Compare-And-Swap) 루프를 통해 head 업데이트 시도</span>
    <span class="hljs-comment">/// 3. 성공할 때까지 재시도</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # 동작 과정</span>
    <span class="hljs-comment">/// 1. 현재 head를 읽음</span>
    <span class="hljs-comment">/// 2. 새 노드의 next를 현재 head로 설정</span>
    <span class="hljs-comment">/// 3. CAS로 head를 새 노드로 아토믹하게 교체</span>
    <span class="hljs-comment">/// 4. 다른 스레드가 먼저 변경했다면 1부터 재시도</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">push</span></span>(&amp;<span class="hljs-keyword">self</span>, v: T) {
        <span class="hljs-keyword">let</span> node = <span class="hljs-built_in">Box</span>::new(Node {
            next: AtomicPtr::new(null_mut()),
            data: v,
        });

        <span class="hljs-comment">// Box를 raw 포인터로 변환</span>
        <span class="hljs-comment">// 이 시점부터 메모리 관리는 수동으로 해야 함</span>
        <span class="hljs-keyword">let</span> ptr = <span class="hljs-built_in">Box</span>::into_raw(node);

        <span class="hljs-comment">// CAS 루프: head가 변경되지 않을 때까지 반복</span>
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 현재 head 읽기</span>
            <span class="hljs-comment">// Acquire: 이 읽기 이후의 모든 메모리 연산이 이 읽기 이후에 발생하도록 보장</span>
            <span class="hljs-keyword">let</span> head = <span class="hljs-keyword">self</span>.head.load(Ordering::Acquire);

            <span class="hljs-comment">// 새 노드의 next를 현재 head로 설정</span>
            <span class="hljs-keyword">unsafe</span> {
                (*ptr).next.store(head, Ordering::Release);
            }

            <span class="hljs-comment">// CAS: head가 여전히 같은 값이면 ptr로 교체</span>
            <span class="hljs-comment">// compare_exchange_weak는 spurious failure를 허용하여 성능 향상</span>
            <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span>.head.compare_exchange_weak(
                head,
                ptr,
                Ordering::Release,  <span class="hljs-comment">// 성공 시: 다른 스레드가 새 head를 볼 수 있도록 Release</span>
                Ordering::Acquire   <span class="hljs-comment">// 실패 시: 다른 스레드의 변경사항을 읽기 위해 Acquire</span>
            ) {
                <span class="hljs-literal">Ok</span>(_) =&gt; <span class="hljs-keyword">break</span>,     <span class="hljs-comment">// 성공: 루프 종료</span>
                <span class="hljs-literal">Err</span>(_) =&gt; <span class="hljs-keyword">continue</span>, <span class="hljs-comment">// 실패: 재시도</span>
            }
        }
    }

    <span class="hljs-comment">/// 스택에서 최상단 요소를 제거하고 반환</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # Returns</span>
    <span class="hljs-comment">/// * `Some(T)` - 스택이 비어있지 않은 경우 최상단 요소</span>
    <span class="hljs-comment">/// * `None` - 스택이 비어있는 경우</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # Algorithm</span>
    <span class="hljs-comment">/// 1. 현재 head 읽기</span>
    <span class="hljs-comment">/// 2. head가 null이면 None 반환</span>
    <span class="hljs-comment">/// 3. CAS를 통해 head를 다음 노드로 업데이트 시도</span>
    <span class="hljs-comment">/// 4. 성공할 때까지 재시도</span>
    <span class="hljs-comment">/// </span>
    <span class="hljs-comment">/// # 주의사항</span>
    <span class="hljs-comment">/// pop 연산 중 다른 스레드의 간섭으로 인해 </span>
    <span class="hljs-comment">/// 여러 번의 재시도가 필요할 수 있음</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">pop</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;T&gt; {
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 현재 head 읽기</span>
            <span class="hljs-keyword">let</span> head = <span class="hljs-keyword">self</span>.head.load(Ordering::Acquire);

            <span class="hljs-comment">// 스택이 비어있는지 확인</span>
            <span class="hljs-keyword">if</span> head == null_mut() {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
            }

            <span class="hljs-comment">// head의 다음 노드 읽기</span>
            <span class="hljs-comment">// 여기서 ABA 문제가 발생할 수 있음 (아래 설명 참조)</span>
            <span class="hljs-keyword">let</span> next = <span class="hljs-keyword">unsafe</span> { (*head).next.load(Ordering::Acquire) };

            <span class="hljs-comment">// CAS: head를 next로 교체 시도</span>
            <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span>.head.compare_exchange_weak(
                head,
                next,
                Ordering::Release,  <span class="hljs-comment">// 성공 시: 메모리 순서 보장</span>
                Ordering::Acquire   <span class="hljs-comment">// 실패 시: 다른 스레드의 변경사항 읽기</span>
            ) {
                <span class="hljs-literal">Ok</span>(_) =&gt; {
                    <span class="hljs-comment">// 성공: 이전 head 노드를 Box로 변환하여 자동 메모리 해제</span>
                    <span class="hljs-keyword">let</span> boxed_node = <span class="hljs-keyword">unsafe</span> { <span class="hljs-built_in">Box</span>::from_raw(head) };
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(boxed_node.data);
                }
                <span class="hljs-literal">Err</span>(_) =&gt; <span class="hljs-keyword">continue</span>, <span class="hljs-comment">// 실패: 재시도</span>
            }
        }
    }
}

<span class="hljs-comment">/// 스택이 drop될 때 모든 노드의 메모리를 해제</span>
<span class="hljs-keyword">impl</span>&lt;T&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> LockFreeStack&lt;T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-comment">// 모든 노드를 순회하며 메모리 해제</span>
        <span class="hljs-comment">// drop 시점에는 다른 스레드가 접근하지 않으므로 Relaxed 순서 사용</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> current = <span class="hljs-keyword">self</span>.head.load(Ordering::Relaxed);

        <span class="hljs-keyword">while</span> current != null_mut() {
            <span class="hljs-keyword">let</span> node = <span class="hljs-keyword">unsafe</span> { <span class="hljs-built_in">Box</span>::from_raw(current) };
            current = node.next.load(Ordering::Relaxed);
            <span class="hljs-comment">// node는 스코프를 벗어나면서 자동으로 drop됨</span>
        }
    }
}

<span class="hljs-comment">/// Send trait 구현: T가 Send면 LockFreeStack&lt;T&gt;도 Send</span>
<span class="hljs-comment">/// </span>
<span class="hljs-comment">/// 다른 스레드로 안전하게 전송 가능함을 나타냄</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T: <span class="hljs-built_in">Send</span>&gt; <span class="hljs-built_in">Send</span> <span class="hljs-keyword">for</span> LockFreeStack&lt;T&gt; {}

<span class="hljs-comment">/// Sync trait 구현: T가 Send면 LockFreeStack&lt;T&gt;는 Sync</span>
<span class="hljs-comment">/// </span>
<span class="hljs-comment">/// 여러 스레드에서 동시에 참조 가능함</span>
<span class="hljs-comment">/// T가 Send여야 하는 이유: pop()이 T를 다른 스레드로 이동시킬 수 있기 때문</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T: <span class="hljs-built_in">Send</span>&gt; <span class="hljs-built_in">Sync</span> <span class="hljs-keyword">for</span> LockFreeStack&lt;T&gt; {}

<span class="hljs-meta">#[cfg(test)]</span>
<span class="hljs-keyword">mod</span> tests {
    <span class="hljs-keyword">use</span> super::*;
    <span class="hljs-keyword">use</span> std::sync::Arc;
    <span class="hljs-keyword">use</span> std::thread;

    <span class="hljs-meta">#[test]</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_single_thread</span></span>() {
        <span class="hljs-keyword">let</span> stack = LockFreeStack::new();

        <span class="hljs-comment">// Push 테스트</span>
        stack.push(<span class="hljs-number">1</span>);
        stack.push(<span class="hljs-number">2</span>);
        stack.push(<span class="hljs-number">3</span>);

        <span class="hljs-comment">// Pop 테스트 (LIFO 순서)</span>
        <span class="hljs-built_in">assert_eq!</span>(stack.pop(), <span class="hljs-literal">Some</span>(<span class="hljs-number">3</span>));
        <span class="hljs-built_in">assert_eq!</span>(stack.pop(), <span class="hljs-literal">Some</span>(<span class="hljs-number">2</span>));
        <span class="hljs-built_in">assert_eq!</span>(stack.pop(), <span class="hljs-literal">Some</span>(<span class="hljs-number">1</span>));
        <span class="hljs-built_in">assert_eq!</span>(stack.pop(), <span class="hljs-literal">None</span>);
    }

    <span class="hljs-meta">#[test]</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_concurrent_operations</span></span>() {
        <span class="hljs-keyword">let</span> stack = Arc::new(LockFreeStack::new());
        <span class="hljs-keyword">let</span> num_threads = <span class="hljs-number">4</span>;
        <span class="hljs-keyword">let</span> operations_per_thread = <span class="hljs-number">1000</span>;

        <span class="hljs-comment">// 여러 스레드에서 동시에 push/pop 수행</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> handles = <span class="hljs-built_in">vec!</span>[];

        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..num_threads {
            <span class="hljs-keyword">let</span> stack_clone = Arc::clone(&amp;stack);
            <span class="hljs-keyword">let</span> handle = thread::spawn(<span class="hljs-keyword">move</span> || {
                <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..operations_per_thread {
                    stack_clone.push(i * operations_per_thread + j);
                    stack_clone.pop();
                }
            });
            handles.push(handle);
        }

        <span class="hljs-comment">// 모든 스레드 종료 대기</span>
        <span class="hljs-keyword">for</span> handle <span class="hljs-keyword">in</span> handles {
            handle.join().unwrap();
        }
    }
}
</code></pre>
<h2 id="heading-66mu66qo66asioyinoyena">메모리 순서</h2>
<p>Rust의 원자적 연산에서 사용되는 메모리 순서는 다음과 같다:</p>
<ul>
<li><p><strong>Relaxed</strong>: 가장 약한 순서 보장. 단일 원자적 변수에 대한 순서만 보장</p>
</li>
<li><p><strong>Acquire</strong>: 이 연산 이후의 모든 메모리 연산이 이 연산 이후에 발생하도록 보장</p>
</li>
<li><p><strong>Release</strong>: 이 연산 이전의 모든 메모리 연산이 이 연산 이전에 발생하도록 보장</p>
</li>
<li><p><strong>AcqRel</strong>: Acquire와 Release의 조합</p>
</li>
<li><p><strong>SeqCst</strong>: 가장 강한 순서 보장. 모든 스레드에서 동일한 순서를 관찰</p>
</li>
</ul>
<p>락프리 스택에서는 주로 Acquire/Release 쌍을 사용하여 데이터 경쟁을 방지한다.</p>
<hr />
<h1 id="heading-65297zse66as7jeq7isc7j2yiousuoygnoygka">락프리에서의 문제점</h1>
<h2 id="heading-aba">ABA 문제</h2>
<p>락프리 스택은 대부분의 경우 문제가 없으나 특정한 조건에서 ABA 문제가 발생할 수 있다.</p>
<p>ABA 문제는 다음과 같은 예로 설명될 수 있는데</p>
<p><img src="https://i.imgur.com/pI2OPr2.png" alt /></p>
<p>그림에서는 2개의 스레드가 락프리 스택에 push와 pop을 수행하고 있다.</p>
<p>초기 락프리 스택에는 3개의 데이터가 존재하고 각 노드의 주소를 A, B, C라고 하자.</p>
<p><strong>시각 0</strong>: 스레드 1은 head의 주소 A와 A의 다음 노드 주소인 B를 기억한다.</p>
<p><strong>시각 1</strong>: 스레드 2가 2번의 pop 조작을 수행한다. 그러면 노드 A와 B는 스택에서 제거되고 메모리는 비게(free) 된다.</p>
<p><strong>시각 2</strong>: 스레드 2가 새로운 데이터를 push한다. 이때 프리 영역이 된 노드 A가 재사용되면 스택은 A→C라는 상태가 된다.</p>
<p><strong>시각 3</strong>: 스레드 1이 pop 조작 이후 CAS 조작을 수행하면 head는 외관상 바뀌지 않지만 해제된 노드B를 이용해 업데이트를 수행하게 된다.</p>
<p>락프리 스택에서는 CAS 조작에 따라 head가 업데이트되지 않았음을 확인한 이후에 데이터의 push와 pop이 이루어지는데, 메모리 영역이 재사용되면 문제가 발생한다.</p>
<p>head가 외관상 바뀌지 않지만 의미적으로는 다른 연산이 수행되었을 수 있는 것이다. 이처럼 ABA문제는 A가 실제로는 중도에 다른 상태(B)로 바뀌었음에도 처음과 끝은 A이므로 처음과 연산 종료 후 결과를 통해서는 업데이트 여부를 제대로 파악할 수 없다는 문제이다.</p>
<h3 id="heading-aba-1">ABA 문제가 발생하는 이유</h3>
<p>ABA 문제가 발생하는 이유는 업데이트 유무가 CAS 처리에 의한 값 비교로 수행되기 때문이다. 값 비교로 작업이 수행되기 때문에 해당 메모리에 어떤 쓰기 작업이 있었는지는 검증하는 과정이 없었다.</p>
<p>그렇다면 문제는 간단해진다. 메모리의 쓰기 작업을 감지할 수 있도록 하면 된다. 이는 Load-Link/Store-Conditional 명령을 이용하면 구현할 수 있다.</p>
<h3 id="heading-aba-2">ABA 문제 해결 방법</h3>
<p>ABA 문제를 해결하는 방법은 여러 가지가 있는데 <strong>LL/SC (Load-Link/Store-Conditional)</strong>: 하드웨어 수준에서 메모리 변경 감지하는 등의 방법이 존재한다.</p>
<h2 id="heading-66ma7yuw7iqk66ci65oc7jeq7iscioywuoyhsoyxkcdrjidtlzwg66y47kcc">멀티스레드에서 참조에 대한 문제</h2>
<p>락프리 구조는 멀티스레드에서 데이터 삭제에 대한 문제를 일으키기도 한다. 아래 예시는 멀티스레드 참조에 관한 문제를 그림으로 보여준다.</p>
<p><img src="https://i.imgur.com/6HQ6ZO7.png" alt /></p>
<p>초기 상태에서 리스트는 A→B→C로 연결되어 있다.</p>
<p><strong>시각 0</strong>: 스레드 1이 선두 노드를 참조했다고 가정하자.</p>
<p><strong>시각 1</strong>: 스레드 2가 pop조작을 수행하고, 선두 노드를 파기한다. 그런데 이때 스레드 1의 참조 x가 댕글링 포인터가 되어버린다.</p>
<p>물론 러스트의 경우 소유권 규칙에 따라 위와 같은 작동을 하는 코드가 컴파일러 선에서 막히기 때문에 실제로 작동될 일은 없으나 가비지 컬렉션(GC)가 없는 언어의 경우 문제가 될 수 있다.</p>
<hr />
<h1 id="heading-65297zse66as7j2yiou2houlma">락프리의 분류</h1>
<p>락프리는 원래 뮤텍스와 같은 구조들(즉, 락을 말한다)에 의존하지 않는다는 의미로 넓게 사용되었으나 락프리의 정의를 더 좁게 가져가자면</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>분류</td><td>배타락 사용</td><td>라이브락 발생 가능</td><td>starvation 발생 가능</td><td>진행 보장</td></tr>
</thead>
<tbody>
<tr>
<td>배타 락프리</td><td>X</td><td>O</td><td>O</td><td>없음</td></tr>
<tr>
<td>락프리</td><td>X</td><td>X</td><td>O</td><td>시스템 전체 진행 보장</td></tr>
<tr>
<td>웨이트 프리</td><td>X</td><td>X</td><td>X</td><td>모든 스레드 진행 보장</td></tr>
</tbody>
</table>
</div><h2 id="heading-65297zse66asiou2houlmcdrjzqg7jwm7jwe67o06riw">락프리 분류 더 알아보기</h2>
<h3 id="heading-obstruction-free">배타 락프리 (Obstruction-Free)</h3>
<ul>
<li><p>가장 약한 진행 보장</p>
</li>
<li><p>다른 스레드가 없을 때만 진행 보장</p>
</li>
<li><p>실용적이지 않아 잘 사용되지 않음</p>
</li>
</ul>
<h3 id="heading-lock-free">락프리 (Lock-Free)</h3>
<ul>
<li><p>시스템 전체적으로 항상 적어도 하나의 스레드는 진행</p>
</li>
<li><p>개별 스레드는 starvation 가능</p>
</li>
<li><p>실용적이면서도 구현 가능한 수준</p>
</li>
</ul>
<h3 id="heading-wait-free">웨이트 프리 (Wait-Free)</h3>
<ul>
<li><p>가장 강한 진행 보장</p>
</li>
<li><p>모든 스레드가 유한한 단계 내에 완료</p>
</li>
<li><p>구현이 매우 어렵고 성능 오버헤드가 큼</p>
</li>
</ul>
<p>배타 락프리, 웨이트 프리와 구분되어 부르는 락프리는 라이브락이 발생하지 않는, 락에 의존하지 않는 구조로 정의할 수 있겠다.</p>
<p>가장 보편적으로 락프리라 함은 배타 락을 사용하지 않는 구조, 즉 배타 락프리에 더 가깝다고 할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[소프트웨어 트랜잭셔널 메모리]]></title><description><![CDATA[소프트웨어 트랜잭셔널 메모리
동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 비관적 락(Negative Lock) 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다.
하지만 이와는 다른 접근 방식이 있다. 바로 낙관적 락(Optimistic Lock) 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학...]]></description><link>https://maximizemaxwell.com/stm</link><guid isPermaLink="true">https://maximizemaxwell.com/stm</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 20 Jul 2025 10:57:27 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-7iam7zse7yq47juo7ja0io2kuouenoyereyflouekcdrqztrqqjrpqw">소프트웨어 트랜잭셔널 메모리</h1>
<p>동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 <strong>비관적 락(Negative Lock)</strong> 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다.</p>
<p>하지만 이와는 다른 접근 방식이 있다. 바로 <strong>낙관적 락(Optimistic Lock)</strong> 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학을 가지고 있다.</p>
<h2 id="heading-64kz6rsa7kcbioygkeq3vdog7yq4656c7j6t7iwu64sqiouplouqqoumra">낙관적 접근: 트랜잭셔널 메모리</h2>
<p><strong>소프트웨어 트랜잭셔널 메모리(Software Transactional Memory, STM)</strong>는 이러한 낙관적 접근을 구현한 대표적인 기술이다. STM은 크리티컬 섹션의 코드를 투기적으로 실행한 후, 실행 과정에서 다른 스레드와의 경합이 감지되지 않았을 때만 그 결과를 메모리에 실제로 커밋한다. 마치 데이터베이스의 트랜잭션처럼 작동하는 것이다.</p>
<p>이러한 방식은 락을 기다리는 시간을 줄이고, 데드락 가능성을 원천적으로 차단할 수 있다는 장점이 있다.</p>
<h2 id="heading-7zwy65oc7juo7ja07jmaioygjo2uho2kuoybqoywtcdqtaztmiq">하드웨어와 소프트웨어 구현</h2>
<p>트랜잭셔널 메모리는 구현 방식에 따라 두 가지로 나뉜다.</p>
<ul>
<li><p><strong>Hardware Transactional Memory (HTM)</strong>: CPU 레벨에서 직접 지원하는 방식</p>
</li>
<li><p><strong>Software Transactional Memory (STM)</strong>: 소프트웨어적으로 구현하는 방식</p>
</li>
</ul>
<p>HTM은 성능면에서 우수하지만 하드웨어 지원이 필요하다는 제약이 있다. 반면 STM은 순수하게 소프트웨어로 구현되어 범용성이 높으며, 실제로 Haskell이나 Clojure 같은 함수형 언어에서 핵심 동시성 메커니즘으로 채택되고 있다.</p>
<h2 id="heading-tl2-stm">TL2: 실용적인 STM 구현</h2>
<p>STM을 실제로 구현하는 방법은 여러 가지가 있지만, 그 중에서도 <strong>Transaction Locking II (TL2)</strong> 알고리즘은 성능과 구현 복잡도 사이의 균형을 잘 맞춘 대표적인 방법이다.</p>
<p>그런고로 이 글에서는 STM의 특징을 좀 더 알아보고, TL2의 구현에 대해 설명할 것이다.</p>
<hr />
<h1 id="heading-stm">STM의 특징</h1>
<p>STM은 뮤텍스와 같은 비관적 락과는 성질이 다르다. STM은 중요한 특징 네 가지를 가지고 있는데</p>
<ol>
<li><p>트랜잭션 중의 코드가 2회 이상 실행될 가능성이 있다.</p>
<ul>
<li>경합이 발견된 후에는 트랜잭션을 재시도하므로 2회 이상 실행될 수 있다.</li>
</ul>
</li>
<li><p>트랜잭션 중에 부작용이 있는 코드는 실행하지 않는다.</p>
<ul>
<li>트랜잭션이 여러 번 발생하면 그에 따라 특정 코드가 여러 번 실행될 수 있어 의도치 않은 결과를 만들 수 있는데,</li>
</ul>
</li>
<li><p>데드락이 발생하지 않는다.</p>
<ul>
<li>경합이 감지되면 쓰고, 그렇지 않으면 재시도하기 때문에 식사하는 철학자와 같은 문제를 해결할 수 있다.</li>
</ul>
</li>
<li><p>여러 트랜잭션 처리를 합성할 수 있다.</p>
<ul>
<li><p>STM에서는 임의의 트랜잭션 처리를 임의로 조합해 실행 가능하다.</p>
</li>
<li><p>뮤텍스에서는 데드락, 락프리에서는 전용 데이터 구조 문제 때문에 임의로 조합하기 어렵지만, STM에서는 어떤 조작도 임의로 조합 가능하다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h1 id="heading-tl2">TL2 알고리즘</h1>
<p>TL2의 타입은 아래와 같다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>타입</td><td>용도</td></tr>
</thead>
<tbody>
<tr>
<td>Memory</td><td>메모리 초기화, 락, 버전 관리에 이용</td></tr>
<tr>
<td>STM</td><td>실제로 트랜잭션을 실행하기 위해 이용</td></tr>
<tr>
<td>ReadTrans</td><td>Read 트랜잭션 시 메모리 읽기에 이용</td></tr>
<tr>
<td>WriteTrans</td><td>Write 트랜잭션 시 메모리 읽기, 쓰기에 사용</td></tr>
<tr>
<td>STMResult</td><td>트랜잭션 반환값 타입</td></tr>
<tr>
<td></td></tr>
</tbody>
</table>
</div><h2 id="heading-7l2u65oc">코드</h2>
<p>러스트로 구현된 TL2 알고리즘은 다음과 같다.</p>
<p><a target="_blank" href="http://tl2.rs"><code>tl2.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-comment">// TL2.rs - TL2 (Transactional Locking 2) 알고리즘 구현</span>
<span class="hljs-keyword">use</span> std::cell::UnsafeCell;
<span class="hljs-keyword">use</span> std::collections::HashMap;
<span class="hljs-keyword">use</span> std::collections::HashSet;
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicU64, Ordering, fence};

<span class="hljs-comment">// ===== 상수 정의 =====</span>
<span class="hljs-comment">// 스트라이프 크기: 메모리를 8바이트 단위로 관리</span>
<span class="hljs-keyword">const</span> STRIPE_SIZE: <span class="hljs-built_in">usize</span> = <span class="hljs-number">8</span>; 

<span class="hljs-comment">// 전체 메모리 크기: 512바이트</span>
<span class="hljs-keyword">const</span> MEM_SIZE: <span class="hljs-built_in">usize</span> = <span class="hljs-number">512</span>; 

<span class="hljs-comment">// ===== 메모리 구조체 정의 =====</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Memory</span></span> {
    mem: <span class="hljs-built_in">Vec</span>&lt;<span class="hljs-built_in">u8</span>&gt;,             <span class="hljs-comment">// 실제 데이터를 저장하는 메모리 영역</span>
    lock_ver: <span class="hljs-built_in">Vec</span>&lt;AtomicU64&gt;, <span class="hljs-comment">// 각 스트라이프의 락과 버전을 저장 (최상위 비트: 락, 나머지: 버전)</span>
    global_clock: AtomicU64,  <span class="hljs-comment">// 전역 버전 클락 (모든 트랜잭션이 공유하는 논리적 시간)</span>
    shift_size: <span class="hljs-built_in">u32</span>,          <span class="hljs-comment">// 주소를 스트라이프 인덱스로 변환하기 위한 시프트 크기</span>
}

<span class="hljs-keyword">impl</span> Memory {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>() -&gt; <span class="hljs-keyword">Self</span> {
        <span class="hljs-comment">// 메모리 영역을 0으로 초기화</span>
        <span class="hljs-keyword">let</span> mem = [<span class="hljs-number">0</span>].repeat(MEM_SIZE);

        <span class="hljs-comment">// 스트라이프 크기는 2의 거듭제곱이어야 함 (8 = 2^3)</span>
        <span class="hljs-comment">// trailing_zeros()는 2진수에서 끝에서부터 0의 개수를 반환 (8 = 1000₂ → 3)</span>
        <span class="hljs-keyword">let</span> shift = STRIPE_SIZE.trailing_zeros(); 

        <span class="hljs-comment">// 각 스트라이프에 대한 락/버전 정보를 초기화</span>
        <span class="hljs-comment">// 스트라이프 개수 = MEM_SIZE / STRIPE_SIZE = 512 / 8 = 64개</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> lock_ver = <span class="hljs-built_in">Vec</span>::new();
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..MEM_SIZE &gt;&gt; shift {  <span class="hljs-comment">// 64개의 스트라이프</span>
            lock_ver.push(AtomicU64::new(<span class="hljs-number">0</span>));  <span class="hljs-comment">// 초기값: 락 해제 상태, 버전 0</span>
        }

        Memory {
            mem,
            lock_ver,
            global_clock: AtomicU64::new(<span class="hljs-number">0</span>),  <span class="hljs-comment">// 전역 클락을 0으로 초기화</span>
            shift_size: shift,
        }
    }

    <span class="hljs-comment">// 전역 버전 클락을 1 증가시키고 이전 값을 반환</span>
    <span class="hljs-comment">// AcqRel 순서를 사용하여 메모리 순서 보장</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">inc_global_clock</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">u64</span> {
        <span class="hljs-keyword">self</span>.global_clock.fetch_add(<span class="hljs-number">1</span>, Ordering::AcqRel)
    }

    <span class="hljs-comment">// 지정된 주소의 현재 버전을 반환</span>
    <span class="hljs-comment">// 최상위 비트(락 비트)를 제거하고 순수 버전만 반환</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">get_addr_ver</span></span>(&amp;<span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">u64</span> {
        <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.shift_size;  <span class="hljs-comment">// 주소를 스트라이프 인덱스로 변환</span>
        <span class="hljs-keyword">let</span> n = <span class="hljs-keyword">self</span>.lock_ver[idx].load(Ordering::Relaxed);
        n &amp; !(<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">63</span>)  <span class="hljs-comment">// 최상위 비트를 0으로 마스킹하여 버전만 추출</span>
    }

    <span class="hljs-comment">// 지정된 주소가 락되어 있지 않고 버전이 rv 이하인지 확인</span>
    <span class="hljs-comment">// TL2의 핵심: 락 비트와 버전을 동시에 확인</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_not_modify</span></span>(&amp;<span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>, rv: <span class="hljs-built_in">u64</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.shift_size;
        <span class="hljs-keyword">let</span> n = <span class="hljs-keyword">self</span>.lock_ver[idx].load(Ordering::Relaxed);
        <span class="hljs-comment">// 락이 걸려있으면 최상위 비트가 1이므로 n &gt; rv가 됨</span>
        <span class="hljs-comment">// 락이 없어도 버전이 rv보다 크면 n &gt; rv가 됨</span>
        n &lt;= rv
    }

    <span class="hljs-comment">// 지정된 주소의 락을 원자적으로 획득 시도</span>
    <span class="hljs-comment">// fetch_update를 사용하여 compare-and-swap 연산 수행</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock_addr</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.shift_size;
        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span>.lock_ver[idx].fetch_update(
            Ordering::Relaxed, <span class="hljs-comment">// 쓰기 시의 메모리 순서</span>
            Ordering::Relaxed, <span class="hljs-comment">// 읽기 시의 메모리 순서</span>
            |val| {
                <span class="hljs-comment">// 현재 값의 최상위 비트(락 비트) 확인</span>
                <span class="hljs-keyword">let</span> lock_bit = val &amp; (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">63</span>);
                <span class="hljs-keyword">if</span> lock_bit == <span class="hljs-number">0</span> { 
                    <span class="hljs-comment">// 락이 해제되어 있으면 락 비트를 설정</span>
                    <span class="hljs-literal">Some</span>(val | (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">63</span>)) 
                } <span class="hljs-keyword">else</span> { 
                    <span class="hljs-comment">// 이미 락이 걸려있으면 실패</span>
                    <span class="hljs-literal">None</span> 
                }
            },
        ) {
            <span class="hljs-literal">Ok</span>(_) =&gt; <span class="hljs-literal">true</span>,   <span class="hljs-comment">// 락 획득 성공</span>
            <span class="hljs-literal">Err</span>(_) =&gt; <span class="hljs-literal">false</span>, <span class="hljs-comment">// 락 획득 실패</span>
        }
    }

    <span class="hljs-comment">// 지정된 주소의 락을 해제</span>
    <span class="hljs-comment">// fetch_and를 사용하여 최상위 비트만 0으로 설정</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">unlock_addr</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) {
        <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.shift_size;
        <span class="hljs-keyword">self</span>.lock_ver[idx].fetch_and(!(<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">63</span>), Ordering::Relaxed);
    }
}

<span class="hljs-comment">// ===== 읽기 전용 트랜잭션 구조체 =====</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ReadTrans</span></span>&lt;<span class="hljs-symbol">'a</span>&gt; {
    read_ver: <span class="hljs-built_in">u64</span>,   <span class="hljs-comment">// 이 트랜잭션의 읽기 버전 (시작 시점의 전역 클락)</span>
    is_abort: <span class="hljs-built_in">bool</span>,  <span class="hljs-comment">// 충돌 감지 시 true로 설정</span>
    mem: &amp;<span class="hljs-symbol">'a</span> Memory, <span class="hljs-comment">// 메모리에 대한 불변 참조</span>
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; ReadTrans&lt;<span class="hljs-symbol">'a</span>&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(mem: &amp;<span class="hljs-symbol">'a</span> Memory) -&gt; <span class="hljs-keyword">Self</span> {
        ReadTrans {
            is_abort: <span class="hljs-literal">false</span>,
            <span class="hljs-comment">// 트랜잭션 시작 시점의 전역 클락을 읽어서 일관된 스냅샷 확보</span>
            read_ver: mem.global_clock.load(Ordering::Acquire),
            mem,
        }
    }

    <span class="hljs-comment">// 메모리에서 데이터 읽기 (낙관적 읽기)</span>
    <span class="hljs-comment">// Double-checked locking 패턴 사용</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">load</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;[<span class="hljs-built_in">u8</span>; STRIPE_SIZE]&gt; {
        <span class="hljs-comment">// 이미 충돌이 감지된 경우 즉시 중단</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.is_abort {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-comment">// 주소가 스트라이프 경계에 정렬되어 있는지 확인</span>
        <span class="hljs-built_in">assert_eq!</span>(addr &amp; (STRIPE_SIZE - <span class="hljs-number">1</span>), <span class="hljs-number">0</span>);

        <span class="hljs-comment">// 첫 번째 검사: 메모리가 락되어 있지 않고 읽기 버전 이하인지 확인</span>
        <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
            <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-comment">// 메모리 펜스: 위의 검사 후 실제 읽기 전에 순서 보장</span>
        fence(Ordering::Acquire);

        <span class="hljs-comment">// 실제 메모리 데이터 복사</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> mem = [<span class="hljs-number">0</span>; STRIPE_SIZE];
        <span class="hljs-keyword">for</span> (dst, src) <span class="hljs-keyword">in</span> mem
            .iter_mut()
            .zip(<span class="hljs-keyword">self</span>.mem.mem[addr..addr + STRIPE_SIZE].iter())
        {
            *dst = *src;
        }

        <span class="hljs-comment">// 강한 메모리 펜스: 읽기 완료 후 두 번째 검사 전에 순서 보장</span>
        fence(Ordering::SeqCst);

        <span class="hljs-comment">// 두 번째 검사: 읽기 도중 다른 트랜잭션이 수정했는지 확인</span>
        <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
            <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-literal">Some</span>(mem)
    }
}

<span class="hljs-comment">// ===== 쓰기 트랜잭션 구조체 =====</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">WriteTrans</span></span>&lt;<span class="hljs-symbol">'a</span>&gt; {
    read_ver: <span class="hljs-built_in">u64</span>,                                <span class="hljs-comment">// 읽기 버전 (트랜잭션 시작 시점)</span>
    read_set: HashSet&lt;<span class="hljs-built_in">usize</span>&gt;,                     <span class="hljs-comment">// 읽은 주소들의 집합</span>
    write_set: HashMap&lt;<span class="hljs-built_in">usize</span>, [<span class="hljs-built_in">u8</span>; STRIPE_SIZE]&gt;, <span class="hljs-comment">// 쓸 주소와 값들의 맵</span>
    locked: <span class="hljs-built_in">Vec</span>&lt;<span class="hljs-built_in">usize</span>&gt;,                           <span class="hljs-comment">// 락을 획득한 주소들의 리스트</span>
    is_abort: <span class="hljs-built_in">bool</span>,                               <span class="hljs-comment">// 충돌 감지 플래그</span>
    mem: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> Memory,                          <span class="hljs-comment">// 메모리에 대한 가변 참조</span>
}

<span class="hljs-comment">// 트랜잭션이 소멸될 때 자동으로 모든 락 해제</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> WriteTrans&lt;<span class="hljs-symbol">'a</span>&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-comment">// 획득한 모든 락을 해제하여 데드락 방지</span>
        <span class="hljs-keyword">for</span> addr <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.locked.iter() {
            <span class="hljs-keyword">self</span>.mem.unlock_addr(*addr);
        }
    }
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; WriteTrans&lt;<span class="hljs-symbol">'a</span>&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(mem: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> Memory) -&gt; <span class="hljs-keyword">Self</span> {
        WriteTrans {
            read_set: HashSet::new(),
            write_set: HashMap::new(),
            locked: <span class="hljs-built_in">Vec</span>::new(),
            is_abort: <span class="hljs-literal">false</span>,
            <span class="hljs-comment">// 트랜잭션 시작 시점의 전역 클락 획득</span>
            read_ver: mem.global_clock.load(Ordering::Acquire),
            mem,
        }
    }

    <span class="hljs-comment">// 메모리에 쓰기 (지연된 쓰기 - write-set에 저장만)</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">store</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>, val: [<span class="hljs-built_in">u8</span>; STRIPE_SIZE]) {
        <span class="hljs-comment">// 주소 정렬 확인</span>
        <span class="hljs-built_in">assert_eq!</span>(addr &amp; (STRIPE_SIZE - <span class="hljs-number">1</span>), <span class="hljs-number">0</span>);
        <span class="hljs-comment">// write-set에 추가 (실제 메모리 쓰기는 커밋 시에)</span>
        <span class="hljs-keyword">self</span>.write_set.insert(addr, val);
    }

    <span class="hljs-comment">// 메모리에서 읽기 (write-set 우선 확인)</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">load</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;[<span class="hljs-built_in">u8</span>; STRIPE_SIZE]&gt; {
        <span class="hljs-comment">// 충돌 감지 시 즉시 중단</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.is_abort {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-comment">// 주소 정렬 확인</span>
        <span class="hljs-built_in">assert_eq!</span>(addr &amp; (STRIPE_SIZE - <span class="hljs-number">1</span>), <span class="hljs-number">0</span>);

        <span class="hljs-comment">// read-set에 주소 추가 (커밋 시 검증용)</span>
        <span class="hljs-keyword">self</span>.read_set.insert(addr);

        <span class="hljs-comment">// write-set에 있는 경우 해당 값을 반환 (자신이 쓴 값 읽기)</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(m) = <span class="hljs-keyword">self</span>.write_set.get(&amp;addr) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(*m);
        }

        <span class="hljs-comment">// write-set에 없는 경우 실제 메모리에서 읽기 (ReadTrans와 동일한 로직)</span>
        <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
            <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        fence(Ordering::Acquire);

        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> mem = [<span class="hljs-number">0</span>; STRIPE_SIZE];
        <span class="hljs-keyword">for</span> (dst, src) <span class="hljs-keyword">in</span> mem
            .iter_mut()
            .zip(<span class="hljs-keyword">self</span>.mem.mem[addr..addr + STRIPE_SIZE].iter())
        {
            *dst = *src;
        }

        fence(Ordering::SeqCst);

        <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
            <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-literal">Some</span>(mem)
    }

    <span class="hljs-comment">// write-set의 모든 주소에 대해 락 획득 시도</span>
    <span class="hljs-comment">// 모든 락을 획득할 수 있어야만 true 반환 (all-or-nothing)</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock_write_set</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">for</span> (addr, _) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.mem.lock_addr(*addr) {
                <span class="hljs-comment">// 락 획득 성공 시 locked 리스트에 추가</span>
                <span class="hljs-keyword">self</span>.locked.push(*addr);
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// 하나라도 락 획득 실패 시 즉시 false 반환</span>
                <span class="hljs-comment">// Drop trait에 의해 기존 락들은 자동 해제됨</span>
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
        }
        <span class="hljs-literal">true</span>
    }

    <span class="hljs-comment">// read-set의 모든 주소에 대해 검증 수행</span>
    <span class="hljs-comment">// 읽었던 데이터가 여전히 유효한지 확인</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">validate_read_set</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">for</span> addr <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.read_set.iter() {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.write_set.contains_key(addr) {
                <span class="hljs-comment">// write-set에 있는 주소는 자신이 락을 가지고 있음</span>
                <span class="hljs-comment">// 버전만 검사 (다른 트랜잭션이 중간에 수정했는지)</span>
                <span class="hljs-keyword">let</span> ver = <span class="hljs-keyword">self</span>.mem.get_addr_ver(*addr);
                <span class="hljs-keyword">if</span> ver &gt; <span class="hljs-keyword">self</span>.read_ver {
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// write-set에 없는 주소는 락과 버전 모두 검사</span>
                <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(*addr, <span class="hljs-keyword">self</span>.read_ver) {
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
                }
            }
        }
        <span class="hljs-literal">true</span>
    }

    <span class="hljs-comment">// 트랜잭션 커밋: 모든 쓰기를 실제 메모리에 반영</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">commit</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, ver: <span class="hljs-built_in">u64</span>) {
        <span class="hljs-comment">// 1. write-set의 모든 데이터를 실제 메모리에 복사</span>
        <span class="hljs-keyword">for</span> (addr, val) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
            <span class="hljs-keyword">let</span> addr = *addr <span class="hljs-keyword">as</span> <span class="hljs-built_in">usize</span>;
            <span class="hljs-keyword">for</span> (dst, src) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.mem.mem[addr..addr + STRIPE_SIZE].iter_mut().zip(val) {
                *dst = *src;
            }
        }

        <span class="hljs-comment">// 2. 메모리 펜스로 쓰기 완료 보장</span>
        fence(Ordering::Release);

        <span class="hljs-comment">// 3. 모든 주소의 락 해제 및 새 버전으로 업데이트</span>
        <span class="hljs-keyword">for</span> (addr, _) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
            <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.mem.shift_size;
            <span class="hljs-comment">// 락 비트를 0으로, 버전을 새 버전으로 설정</span>
            <span class="hljs-keyword">self</span>.mem.lock_ver[idx].store(ver, Ordering::Relaxed);
        }

        <span class="hljs-comment">// 4. locked 리스트 초기화 (Drop에서 중복 해제 방지)</span>
        <span class="hljs-keyword">self</span>.locked.clear();
    }
}

<span class="hljs-comment">// ===== STM 결과 타입 =====</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">STMResult</span></span>&lt;T&gt; {
    <span class="hljs-literal">Ok</span>(T),    <span class="hljs-comment">// 성공</span>
    Retry,    <span class="hljs-comment">// 재시도 필요 (충돌 감지)</span>
    Abort,    <span class="hljs-comment">// 중단 (취소)</span>
}

<span class="hljs-comment">// ===== 메인 STM 구조체 =====</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">STM</span></span> {
    mem: UnsafeCell&lt;Memory&gt;, <span class="hljs-comment">// 내부 가변성을 위한 UnsafeCell 사용</span>
}

<span class="hljs-comment">// 스레드 간 공유를 위한 안전성 마커</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span> <span class="hljs-built_in">Sync</span> <span class="hljs-keyword">for</span> STM {}
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span> <span class="hljs-built_in">Send</span> <span class="hljs-keyword">for</span> STM {}

<span class="hljs-keyword">impl</span> STM {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>() -&gt; <span class="hljs-keyword">Self</span> {
        STM {
            mem: UnsafeCell::new(Memory::new()),
        }
    }

    <span class="hljs-comment">// 읽기 전용 트랜잭션 실행</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">read_transaction</span></span>&lt;F, R&gt;(&amp;<span class="hljs-keyword">self</span>, f: F) -&gt; <span class="hljs-built_in">Option</span>&lt;R&gt;
    <span class="hljs-keyword">where</span>
        F: <span class="hljs-built_in">Fn</span>(&amp;<span class="hljs-keyword">mut</span> ReadTrans) -&gt; STMResult&lt;R&gt;,
    {
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 1. 새로운 읽기 트랜잭션 생성 (현재 전역 클락으로 스냅샷)</span>
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> tr = ReadTrans::new(<span class="hljs-keyword">unsafe</span> { &amp;*<span class="hljs-keyword">self</span>.mem.get() });

            <span class="hljs-comment">// 2. 사용자 함수 실행 (투기적 실행)</span>
            <span class="hljs-keyword">match</span> f(&amp;<span class="hljs-keyword">mut</span> tr) {
                STMResult::Abort =&gt; <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>,
                STMResult::Retry =&gt; {
                    <span class="hljs-keyword">if</span> tr.is_abort {
                        <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 충돌 감지 시 재시도</span>
                    }
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>; <span class="hljs-comment">// 사용자가 명시적으로 재시도 요청</span>
                }
                STMResult::<span class="hljs-literal">Ok</span>(val) =&gt; {
                    <span class="hljs-keyword">if</span> tr.is_abort {
                        <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 충돌 감지 시 재시도</span>
                    } <span class="hljs-keyword">else</span> {
                        <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(val); <span class="hljs-comment">// 성공적으로 완료</span>
                    }
                }
            }
        }
    }

    <span class="hljs-comment">// 읽기-쓰기 트랜잭션 실행 (TL2의 핵심 로직)</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">write_transaction</span></span>&lt;F, R&gt;(&amp;<span class="hljs-keyword">self</span>, f: F) -&gt; <span class="hljs-built_in">Option</span>&lt;R&gt;
    <span class="hljs-keyword">where</span>
        F: <span class="hljs-built_in">Fn</span>(&amp;<span class="hljs-keyword">mut</span> WriteTrans) -&gt; STMResult&lt;R&gt;,
    {
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 1. 새로운 쓰기 트랜잭션 생성</span>
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> tr = WriteTrans::new(<span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.mem.get() });

            <span class="hljs-comment">// 2. 사용자 함수 실행 (투기적 실행 단계)</span>
            <span class="hljs-keyword">let</span> result;
            <span class="hljs-keyword">match</span> f(&amp;<span class="hljs-keyword">mut</span> tr) {
                STMResult::Abort =&gt; <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>,
                STMResult::Retry =&gt; {
                    <span class="hljs-keyword">if</span> tr.is_abort {
                        <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 충돌 감지 시 재시도</span>
                    }
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
                }
                STMResult::<span class="hljs-literal">Ok</span>(val) =&gt; {
                    <span class="hljs-keyword">if</span> tr.is_abort {
                        <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 투기적 실행 중 충돌 감지</span>
                    }
                    result = val;
                }
            }

            <span class="hljs-comment">// 3. write-set의 모든 주소에 대해 락 획득 시도</span>
            <span class="hljs-keyword">if</span> !tr.lock_write_set() {
                <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 락 획득 실패 시 재시도</span>
            }

            <span class="hljs-comment">// 4. 전역 클락 증가 및 새 버전 번호 할당</span>
            <span class="hljs-keyword">let</span> ver = <span class="hljs-number">1</span> + tr.mem.inc_global_clock();

            <span class="hljs-comment">// 5. read-set 검증</span>
            <span class="hljs-comment">// 조건1: read_ver + 1 == ver (다른 트랜잭션이 커밋하지 않음)</span>
            <span class="hljs-comment">// 조건2: validate_read_set() 성공 (읽었던 데이터가 여전히 유효)</span>
            <span class="hljs-keyword">if</span> tr.read_ver + <span class="hljs-number">1</span> != ver &amp;&amp; !tr.validate_read_set() {
                <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 검증 실패 시 재시도</span>
            }

            <span class="hljs-comment">// 6. 커밋 수행 및 락 해제</span>
            tr.commit(ver);

            <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(result); <span class="hljs-comment">// 성공적으로 완료</span>
        }
    }
}

<span class="hljs-comment">// ===== 편의성을 위한 매크로 =====</span>

<span class="hljs-comment">// 메모리 읽기용 매크로 - 실패 시 자동으로 Retry 반환</span>
<span class="hljs-meta">#[macro_export]</span>
<span class="hljs-built_in">macro_rules!</span> load {
    ($t:ident, $a:expr) =&gt; {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(v) = ($t).load($a) {
            v
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> tl2::STMResult::Retry;
        }
    };
}

<span class="hljs-comment">// 메모리 쓰기용 매크로 - 단순히 store 호출</span>
<span class="hljs-meta">#[macro_export]</span>
<span class="hljs-built_in">macro_rules!</span> store {
    ($t:ident, $a:expr, $v:expr) =&gt; {
        $t.store($a, $v)
    };
}
</code></pre>
<p><a target="_blank" href="http://main.rs"><code>main.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-comment">// main.rs - TL2 STM을 사용한 식사하는 철학자 문제 해결</span>
<span class="hljs-keyword">use</span> std::sync::Arc;
<span class="hljs-keyword">use</span> std::{thread, time};
<span class="hljs-keyword">mod</span> tl2;

<span class="hljs-comment">// ===== 상수 정의 =====</span>
<span class="hljs-keyword">const</span> NUM_PHILOSOPHERS: <span class="hljs-built_in">usize</span> = <span class="hljs-number">8</span>; <span class="hljs-comment">// 철학자 및 포크의 개수</span>

<span class="hljs-comment">// ===== 철학자 스레드 함수 =====</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">philosopher</span></span>(stm: Arc&lt;tl2::STM&gt;, n: <span class="hljs-built_in">usize</span>) {
    <span class="hljs-comment">// 각 철학자는 고유한 번호 n을 가지며, 왼쪽과 오른쪽 포크를 사용</span>

    <span class="hljs-comment">// 포크의 메모리 주소 계산</span>
    <span class="hljs-comment">// 각 포크는 8바이트씩 할당되므로 8 * n 주소에 위치</span>
    <span class="hljs-keyword">let</span> left = <span class="hljs-number">8</span> * n; <span class="hljs-comment">// 철학자 n의 왼쪽 포크 주소</span>
    <span class="hljs-keyword">let</span> right = <span class="hljs-number">8</span> * ((n + <span class="hljs-number">1</span>) % NUM_PHILOSOPHERS); <span class="hljs-comment">// 오른쪽 포크 주소 (원형 배치)</span>

    <span class="hljs-comment">// 각 철학자는 500,000번 반복하여 식사 시도</span>
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">500000</span> {
        <span class="hljs-comment">// ===== 포크 획득 단계 =====</span>
        <span class="hljs-comment">// 양쪽 포크를 모두 들 수 있을 때까지 반복 시도</span>
        <span class="hljs-keyword">while</span> !stm
            .write_transaction(|tr| {
                <span class="hljs-comment">// 트랜잭션 내에서 양쪽 포크의 상태를 확인</span>
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> f1 = load!(tr, left); <span class="hljs-comment">// 왼쪽 포크 읽기 (매크로 사용)</span>
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> f2 = load!(tr, right); <span class="hljs-comment">// 오른쪽 포크 읽기</span>

                <span class="hljs-comment">// 포크 상태 확인: 0 = 사용 가능, 1 = 사용 중</span>
                <span class="hljs-keyword">if</span> f1[<span class="hljs-number">0</span>] == <span class="hljs-number">0</span> &amp;&amp; f2[<span class="hljs-number">0</span>] == <span class="hljs-number">0</span> {
                    <span class="hljs-comment">// 양쪽 포크가 모두 비어있는 경우</span>

                    <span class="hljs-comment">// 포크를 사용 중으로 표시 (1로 설정)</span>
                    f1[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>;
                    f2[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>;

                    <span class="hljs-comment">// 메모리에 변경사항 저장 (write-set에 추가)</span>
                    store!(tr, left, f1);
                    store!(tr, right, f2);

                    <span class="hljs-comment">// 포크 획득 성공을 반환</span>
                    tl2::STMResult::<span class="hljs-literal">Ok</span>(<span class="hljs-literal">true</span>)
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-comment">// 하나라도 사용 중인 경우 획득 실패</span>
                    tl2::STMResult::<span class="hljs-literal">Ok</span>(<span class="hljs-literal">false</span>)
                }
            })
            .unwrap()
        <span class="hljs-comment">// Option을 언래핑 (None은 트랜잭션 중단을 의미)</span>
        {
            <span class="hljs-comment">// while 루프: false가 반환되면 계속 시도</span>
            <span class="hljs-comment">// 다른 철학자가 포크를 사용 중이면 대기</span>
        }

        <span class="hljs-comment">// ===== 식사 시뮬레이션 =====</span>
        <span class="hljs-comment">// 실제로는 여기서 철학자가 생각하고 식사하는 시간을 시뮬레이션할 수 있음</span>
        <span class="hljs-comment">// 현재 구현에서는 즉시 포크를 내려놓음</span>

        <span class="hljs-comment">// ===== 포크 반납 단계 =====</span>
        <span class="hljs-comment">// 식사 완료 후 양쪽 포크를 모두 내려놓음</span>
        stm.write_transaction(|tr| {
            <span class="hljs-comment">// 현재 포크 상태를 읽어옴</span>
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> f1 = load!(tr, left);
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> f2 = load!(tr, right);

            <span class="hljs-comment">// 포크를 사용 가능 상태로 변경 (0으로 설정)</span>
            f1[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;
            f2[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;

            <span class="hljs-comment">// 메모리에 변경사항 저장</span>
            store!(tr, left, f1);
            store!(tr, right, f2);

            <span class="hljs-comment">// 반환값은 사용하지 않으므로 ()</span>
            tl2::STMResult::<span class="hljs-literal">Ok</span>(())
        });

        <span class="hljs-comment">// 포크 반납은 항상 성공해야 하므로 unwrap() 사용</span>
        <span class="hljs-comment">// (철학자는 자신이 가진 포크만 내려놓으므로 충돌 없음)</span>
    }
}

<span class="hljs-comment">// ===== 관측자 스레드 함수 =====</span>
<span class="hljs-comment">// 시스템의 일관성을 지속적으로 모니터링</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">observer</span></span>(stm: Arc&lt;tl2::STM&gt;) {
    <span class="hljs-comment">// 10,000번 반복하여 상태 관측</span>
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">10000</span> {
        <span class="hljs-comment">// ===== 현재 포크 상태 스냅샷 획득 =====</span>
        <span class="hljs-comment">// 읽기 전용 트랜잭션으로 모든 포크의 일관된 상태를 읽음</span>
        <span class="hljs-keyword">let</span> chopsticks = stm
            .read_transaction(|tr| {
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = [<span class="hljs-number">0</span>; NUM_PHILOSOPHERS];

                <span class="hljs-comment">// 모든 포크의 상태를 순차적으로 읽기</span>
                <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_PHILOSOPHERS {
                    v[i] = load!(tr, <span class="hljs-number">8</span> * i)[<span class="hljs-number">0</span>]; <span class="hljs-comment">// i번째 포크의 상태</span>
                }

                tl2::STMResult::<span class="hljs-literal">Ok</span>(v)
            })
            .unwrap(); <span class="hljs-comment">// 읽기 트랜잭션은 항상 성공해야 함</span>

        <span class="hljs-comment">// 현재 포크 상태를 출력 (디버깅 및 모니터링용)</span>
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{:?}"</span>, chopsticks);

        <span class="hljs-comment">// ===== 일관성 검사 =====</span>
        <span class="hljs-comment">// 불변식: 사용 중인 포크의 총 개수는 항상 짝수여야 함</span>
        <span class="hljs-comment">// 이유: 각 철학자는 항상 2개의 포크를 동시에 사용하므로</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> n = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> &amp;chopsticks {
            <span class="hljs-keyword">if</span> *c == <span class="hljs-number">1</span> {
                <span class="hljs-comment">// 사용 중인 포크 개수 세기</span>
                n += <span class="hljs-number">1</span>;
            }
        }

        <span class="hljs-comment">// 홀수 개의 포크가 사용 중이면 일관성 위반</span>
        <span class="hljs-keyword">if</span> n &amp; <span class="hljs-number">1</span> != <span class="hljs-number">0</span> {
            <span class="hljs-comment">// n % 2 != 0과 동일</span>
            <span class="hljs-built_in">panic!</span>(<span class="hljs-string">"inconsistent: {} forks in use"</span>, n);
        }

        <span class="hljs-comment">// 100마이크로초 대기 (관측 주기 조절)</span>
        <span class="hljs-keyword">let</span> us = time::Duration::from_micros(<span class="hljs-number">100</span>);
        thread::sleep(us);
    }
}

<span class="hljs-comment">// ===== 메인 함수 =====</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// STM 인스턴스 생성 (Arc로 래핑하여 스레드 간 공유)</span>
    <span class="hljs-keyword">let</span> stm = Arc::new(tl2::STM::new());
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = <span class="hljs-built_in">Vec</span>::new();

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Starting {} philosophers..."</span>, NUM_PHILOSOPHERS);

    <span class="hljs-comment">// ===== 철학자 스레드들 생성 및 시작 =====</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_PHILOSOPHERS {
        <span class="hljs-keyword">let</span> s = stm.clone(); <span class="hljs-comment">// STM 참조 복제 (Arc의 참조 카운트 증가)</span>

        <span class="hljs-comment">// 각 철학자를 별도 스레드에서 실행</span>
        <span class="hljs-keyword">let</span> th = std::thread::spawn(<span class="hljs-keyword">move</span> || philosopher(s, i));
        v.push(th); <span class="hljs-comment">// 스레드 핸들 저장 (나중에 join하기 위해)</span>
    }

    <span class="hljs-comment">// ===== 관측자 스레드 생성 및 시작 =====</span>
    <span class="hljs-keyword">let</span> obs_stm = stm.clone();
    <span class="hljs-keyword">let</span> obs = std::thread::spawn(<span class="hljs-keyword">move</span> || observer(obs_stm));

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"All threads started. Monitoring for inconsistencies..."</span>);

    <span class="hljs-comment">// ===== 모든 철학자 스레드 완료 대기 =====</span>
    <span class="hljs-keyword">for</span> th <span class="hljs-keyword">in</span> v {
        th.join().unwrap(); <span class="hljs-comment">// 각 철학자 스레드가 완료될 때까지 대기</span>
    }

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"All philosophers finished eating."</span>);

    <span class="hljs-comment">// ===== 관측자 스레드 완료 대기 =====</span>
    obs.join().unwrap();

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Program completed successfully - no inconsistencies detected!"</span>);
}
</code></pre>
<h2 id="heading-memory">Memory</h2>
<p>Memory 타입에는 트랜잭션을 수행할 기본 함수를 정의한다. TL2는 512 바이트 메모리를 <strong>스트라이프(8바이트 구역)</strong> 라는 단위로 64개로 나누어 관리한다.</p>
<p>여기서 각 스트라이프의 락/버전 정보느 64비트로 구성되는데 최상위 비트에 락 상태를 기입하고 나머지 63비트에 버전 번호를 기입한다. 이 구조로 인해 단일 아토믹 연산으로 락 상태와 버전을 동시에 확인할 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_not_modify</span></span>(&amp;<span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>, read_version: <span class="hljs-built_in">u64</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-keyword">let</span> current_value = <span class="hljs-keyword">self</span>.lock_ver[addr &gt;&gt; <span class="hljs-number">3</span>].load(Ordering::Relaxed);
    current_value &lt;= read_version
}
</code></pre>
<p>위의 함수와 같이 락이 걸려있으면 최상위 비트 값이 1이 되어 매우 커지므로 <code>current_value &gt; read_version</code>이 되어 수정이 감지된다.</p>
<h2 id="heading-readtrans">ReadTrans</h2>
<p>읽기는 다음 코드와 같이 작동한다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">load</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;[<span class="hljs-built_in">u8</span>; STRIPE_SIZE]&gt; {
    <span class="hljs-comment">// 1. 첫 번째 일관성 검사</span>
    <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
        <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
    }

    fence(Ordering::Acquire);

    <span class="hljs-comment">// 2. 실제 메모리 읽기</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> data = [<span class="hljs-number">0</span>; STRIPE_SIZE];
    data.copy_from_slice(&amp;<span class="hljs-keyword">self</span>.mem.mem[addr..addr + STRIPE_SIZE]);

    fence(Ordering::SeqCst);

    <span class="hljs-comment">// 3. 두 번째 일관성 검사</span>
    <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(addr, <span class="hljs-keyword">self</span>.read_ver) {
        <span class="hljs-keyword">self</span>.is_abort = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
    }

    <span class="hljs-literal">Some</span>(data)
}
</code></pre>
<ol>
<li><p>첫 번쨰 검사: 읽기 전에 메모리가 락X, 버전이 <code>read_ver</code> 이하인지 확인</p>
</li>
<li><p>메모리 읽기: 실제 데이터를 복사, 메모리 펜스로 순서 보장</p>
</li>
<li><p>두 번째 검사: 읽기 과정에서 다른 트랜잭션이 수정되었는지 확인 이 방식으로 락 없이도 일관된 읽기를 보장한. 다른 트랜잭션이 중간에 수정했다면 두 번째 검사에서 감지되어 재시도한다.</p>
</li>
</ol>
<h2 id="heading-writetrans">WriteTrans</h2>
<p>WriteTrans는 읽기-쓰기 트랜잭션을 처리하는 가장 복잡한 구조체다. 지연된 쓰기와 2단계 커밋 프로토콜을 구현한다.</p>
<h3 id="heading-7kea7jew65ccioytsoq4sa">지연된 쓰기</h3>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">store</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>, val: [<span class="hljs-built_in">u8</span>; STRIPE_SIZE]) {
    <span class="hljs-keyword">self</span>.write_set.insert(addr, val);
}
</code></pre>
<p><code>store</code> 연산은 실제 메모리에 즉시 쓰지 않고 <code>write_set</code>에만 저장한다. 실제 쓰기는 트랜잭션이 성공적으로 커밋될 때까지 지연된다. 이렇게 하면 중간에 충돌이 발생해도 실제 메모리는 변경되지 않아 롤백이 간단하다.</p>
<h3 id="heading-read-your-write">Read-Your-Write 의미</h3>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">load</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, addr: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;[<span class="hljs-built_in">u8</span>; STRIPE_SIZE]&gt; {
    <span class="hljs-keyword">self</span>.read_set.insert(addr);

    <span class="hljs-comment">// write-set에 있으면 해당 값 반환</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(data) = <span class="hljs-keyword">self</span>.write_set.get(&amp;addr) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(*data);
    }

    <span class="hljs-comment">// 없으면 실제 메모리에서 읽기 (ReadTrans와 동일)</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>트랜잭션 내에서 자신이 쓴 값을 읽을 때는 <code>write_set</code>의 값을 반환한다. 이를 통해 트랜잭션 내에서 일관된 메모리 뷰를 제공한다. 동시에 읽은 주소는 <code>read_set</code>에 기록하여 나중에 검증할 때 사용한다.</p>
<h2 id="heading-stm-1">STM</h2>
<p>STM 구조체는 전체 트랜잭션 시스템의 진입점이다. TL2의 핵심인 5단계 커밋 프로토콜을 구현한다.</p>
<h3 id="heading-5">5단계 커밋 프로토콜</h3>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">write_transaction</span></span>&lt;F, R&gt;(&amp;<span class="hljs-keyword">self</span>, f: F) -&gt; <span class="hljs-built_in">Option</span>&lt;R&gt; {
    <span class="hljs-keyword">loop</span> {
        <span class="hljs-comment">// 1단계: 투기적 실행</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> tr = WriteTrans::new(<span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.mem.get() });
        <span class="hljs-keyword">let</span> result = <span class="hljs-keyword">match</span> f(&amp;<span class="hljs-keyword">mut</span> tr) {
            STMResult::<span class="hljs-literal">Ok</span>(val) <span class="hljs-keyword">if</span> !tr.is_abort =&gt; val,
            _ =&gt; <span class="hljs-keyword">continue</span>,
        };

        <span class="hljs-comment">// 2단계: 락 획득</span>
        <span class="hljs-keyword">if</span> !tr.lock_write_set() { <span class="hljs-keyword">continue</span>; }

        <span class="hljs-comment">// 3단계: 전역 클락 증가</span>
        <span class="hljs-keyword">let</span> ver = <span class="hljs-number">1</span> + tr.mem.inc_global_clock();

        <span class="hljs-comment">// 4단계: read-set 검증</span>
        <span class="hljs-keyword">if</span> tr.read_ver + <span class="hljs-number">1</span> != ver &amp;&amp; !tr.validate_read_set() {
            <span class="hljs-keyword">continue</span>;
        }

        <span class="hljs-comment">// 5단계: 커밋</span>
        tr.commit(ver);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(result);
    }
}
</code></pre>
<p><strong>1단계 - 투기적 실행</strong>: 사용자 코드를 실행하여 <code>read_set</code>과 <code>write_set</code>을 구성한다. 실제 메모리는 수정하지 않고 시뮬레이션만 한다.</p>
<p><strong>2단계 - 락 획득</strong>: <code>write_set</code>의 모든 주소에 대해 락을 획득한다. All-or-Nothing 원칙으로 하나라도 실패하면 모든 락을 해제하고 재시도한다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock_write_set</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-keyword">for</span> (addr, _) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.mem.lock_addr(*addr) {
            <span class="hljs-keyword">self</span>.locked.push(*addr);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;  <span class="hljs-comment">// 하나라도 실패하면 전체 실패</span>
        }
    }
    <span class="hljs-literal">true</span>
}
</code></pre>
<p><strong>3단계 - 전역 클락 증가</strong>: 전역 클락을 원자적으로 증가시켜 새로운 버전 번호를 할당한다. 이 버전 번호는 이 트랜잭션의 커밋 시간을 나타낸다.</p>
<p><strong>4단계 - read-set 검증</strong>: 가장 중요한 최적화가 포함된 단계다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">if</span> tr.read_ver + <span class="hljs-number">1</span> != ver &amp;&amp; !tr.validate_read_set()
</code></pre>
<p>이 조건의 핵심은 <strong>빠른 경로(Fast Path)</strong>다. 만약 <a target="_blank" href="http://tr.read"><code>tr.read</code></a><code>_ver + 1 == ver</code>라면 이 트랜잭션이 시작된 이후 다른 트랜잭션이 커밋하지 않았다는 의미이므로, 복잡한 <code>read_set</code> 검증을 생략할 수 있다.</p>
<p>그렇지 않다면 세밀한 검증을 수행한다:</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">validate_read_set</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-keyword">for</span> addr <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.read_set.iter() {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.write_set.contains_key(addr) {
            <span class="hljs-comment">// 자신이 락을 가진 주소: 버전만 확인</span>
            <span class="hljs-keyword">let</span> current_ver = <span class="hljs-keyword">self</span>.mem.get_addr_ver(*addr);
            <span class="hljs-keyword">if</span> current_ver &gt; <span class="hljs-keyword">self</span>.read_ver { <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// 다른 주소: 락과 버전 모두 확인</span>
            <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.mem.test_not_modify(*addr, <span class="hljs-keyword">self</span>.read_ver) {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
        }
    }
    <span class="hljs-literal">true</span>
}
</code></pre>
<p>자신이 락을 가진 주소와 그렇지 않은 주소를 다르게 처리한다. 자신이 락을 가진 경우 다른 트랜잭션이 수정할 수 없으므로 버전만 확인하면 된다.</p>
<p><strong>5단계 - 커밋</strong>: 실제 메모리에 쓰기를 수행하고 락을 해제한다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">commit</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, ver: <span class="hljs-built_in">u64</span>) {
    <span class="hljs-comment">// 1. 실제 메모리에 쓰기</span>
    <span class="hljs-keyword">for</span> (addr, val) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
        <span class="hljs-keyword">self</span>.mem.mem[*addr..*addr + STRIPE_SIZE].copy_from_slice(val);
    }

    fence(Ordering::Release);

    <span class="hljs-comment">// 2. 락 해제 및 버전 업데이트</span>
    <span class="hljs-keyword">for</span> (addr, _) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.write_set.iter() {
        <span class="hljs-keyword">let</span> idx = addr &gt;&gt; <span class="hljs-keyword">self</span>.mem.shift_size;
        <span class="hljs-keyword">self</span>.mem.lock_ver[idx].store(ver, Ordering::Relaxed);
    }
}
</code></pre>
<p>메모리 쓰기 완료 후 락 비트를 0으로, 버전을 새 버전으로 원자적으로 설정한다.</p>
<h2 id="heading-tl2-1">TL2의 핵심 특성</h2>
<h3 id="heading-642w65oc6529iouwqeynga">데드락 방지</h3>
<p>TL2는 구조적으로 데드락이 불가능하다. 전통적인 락에서는 스레드 A가 락 X를 가지고 락 Y를 기다리고, 스레드 B가 락 Y를 가지고 락 X를 기다리는 상황이 발생할 수 있다.</p>
<p>하지만 TL2에서는 All-or-Nothing 락 획득 방식으로 부분적 락 획득 상태가 존재하지 않는다. 필요한 모든 락을 획득할 수 없으면 기존 락들을 모두 해제하고 처음부터 재시도한다.</p>
<h3 id="heading-aba">ABA 문제 해결</h3>
<p>단순한 락 확인만으로는 ABA 문제가 발생할 수 있다. 값이 A에서 B로 변경된 후 다시 A로 돌아왔을 때, 값만 보면 변화가 없는 것처럼 보인다. TL2는 버전 번호를 통해 이를 해결한다. 값이 같아도 버전이 증가했다면 변경이 감지된다.</p>
<h3 id="heading-64as7j2aioupmeylnoyesq">높은 동시성</h3>
<p>읽기 트랜잭션은 락을 사용하지 않아 여러 읽기가 동시에 실행될 수 있다. 쓰기 트랜잭션도 커밋 단계에서만 락을 보유하므로 락 경합이 최소화된다.</p>
<h2 id="heading-7iud7iks7zwy64quioyyoo2vmeyekcdrrljsojzsl5dshjzsnzgg64z7j6r">식사하는 철학자 문제에서의 동작</h2>
<p>철학자 문제에서 TL2의 동작을 구체적으로 보면, 각 철학자가 두 포크를 동시에 잡으려 할 때:</p>
<ol>
<li><p><strong>투기적 실행</strong>: 두 포크 상태를 확인하고 둘 다 비어있으면 사용 중으로 설정하는 계획을 세운다.</p>
</li>
<li><p><strong>락 획득</strong>: 두 포크에 대한 편집권을 얻으려 시도한다. 하나라도 실패하면 포기하고 재시도한다.</p>
</li>
<li><p><strong>검증</strong>: 포크 상태를 읽었을 때와 지금이 일치하는지 확인한다.</p>
</li>
<li><p><strong>커밋</strong>: 성공하면 실제로 포크 상태를 변경하고 편집권을 반납한다.</p>
</li>
</ol>
<p>이 과정을 통해 데드락 없이 안전하게 동시성 문제를 해결할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[공평한 배타 제어]]></title><description><![CDATA[공평한 배타 제어
여기서는 공평한 배타 제어에 대해 설명한다.
먼저 컨텐션(contention) 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다.
이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 비균일 메모리 접근(Non-Uniform Memory Access, NUMA) 와 같...]]></description><link>https://maximizemaxwell.com/6ro17yj7zwciouwso2dgcdsojzslrq</link><guid isPermaLink="true">https://maximizemaxwell.com/6ro17yj7zwciouwso2dgcdsojzslrq</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 13 Jul 2025 11:56:56 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-6ro17yj7zwciouwso2dgcdsojzslrq">공평한 배타 제어</h1>
<p>여기서는 공평한 배타 제어에 대해 설명한다.</p>
<p>먼저 <strong>컨텐션(contention)</strong> 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다.</p>
<p>이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 <strong>비균일 메모리 접근(Non-Uniform Memory Access, NUMA)</strong> 와 같은 환경에서는 메모리와 CPU의 위치에 따라 메모리에 대한 접근 속도가 다르다. 예를 들어, CPU가 자신과 가까운 메모리에 접근할 때는 빠르지만, 원격 노드의 메모리에 접근할 때는 상대적으로 느리다.</p>
<p>이런 접근 속도의 차이는 락을 획득하는 난이도에도 영향을 미친다. 락이 저장된 메모리 위치와 가까운 CPU에서 실행되는 스레드는 락을 더 빨리 획득할 수 있는 반면, 원격 노드의 스레드는 상대적으로 불리한 위치에 있게 된다. 결과적으로 특정 스레드가 락을 반복적으로 획득하는 편향이 발생할 수 있다.</p>
<p>이러한 문제를 해결하기 위해 <strong>공평한 락(fair lock)</strong> 이 필요하다. 컨텐션이 높은 환경에서 공평한 락은 모든 스레드가 순서대로 락을 획득할 수 있도록 보장함으로써, NUMA로 인한 불균형을 완화하고 시스템의 예측 가능성을 높이며 기아 현상을 방지하는 데 도움이 된다.</p>
<h2 id="heading-7jw97zwcioqzte2pieyeseydhcdrs7tsnqxtlzjripqg6529">약한 공평성을 보장하는 락</h2>
<p>약한 공평성을 보장하는 배타 제어 알고리즘은 다음과 같이 동작한다.</p>
<ol>
<li><p>락을 우선적으로 획득할 수 있는 스레드가 설정됨</p>
</li>
<li><p>우선 스레드는 순서대로 바뀜</p>
</li>
</ol>
<p>예를 들면 스레드 1이 우선 스레드로 설정되면 스레드 1은 반드시 락을 획득한다. 락이 해제되면 다음 스레드가 우선 스레드가 되도록 설정된다. 다음 스레드가 스레드 2라고 하자. 그런데 여기서 스레드 2가 락 획득을 시도하지 않으면 어떻게 될까? 나머지 스레드들이 락 획득을 경합하고 다음 스레드 3을 우선 스레드로 설정한다.</p>
<p>약한 공평성을 보장하는 알고리즘을 코드로 구현하면 다음과 같다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::cell::UnsafeCell;
<span class="hljs-comment">// use std::ops::{Deref, DerefMut};</span>
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicBool, AtomicUsize, Ordering, fence};

<span class="hljs-comment">// 스레드 최대 수를 사전에 결정</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> NUM_LOCK: <span class="hljs-built_in">usize</span> = <span class="hljs-number">8</span>;

<span class="hljs-comment">//NUM_LOCK의 여분을 구하는 비트마스크</span>
<span class="hljs-keyword">const</span> MASK: <span class="hljs-built_in">usize</span> = NUM_LOCK - <span class="hljs-number">1</span>;

<span class="hljs-comment">// fair lock 구조체</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FairLock</span></span>&lt;T&gt; {
    <span class="hljs-comment">// n번째 스레드가 락을 획득하려고 시행중인지 나타내는 벡터</span>
    waiting: <span class="hljs-built_in">Vec</span>&lt;AtomicBool&gt;,
    <span class="hljs-comment">// 스핀락을 위한 변수</span>
    lock: AtomicBool,
    <span class="hljs-comment">// 락 획득을 우선해야 하는 스레드를 나타내는 변수</span>
    turn: AtomicUsize,
    <span class="hljs-comment">// 보호 대상 데이터</span>
    data: UnsafeCell&lt;T&gt;,
}

<span class="hljs-comment">// 락 자동 해제 및 보호 대상 데이터로의 접근을 수행하기 위한 타입</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FairLockGuard</span></span>&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    fair_lock: &amp;<span class="hljs-symbol">'a</span> FairLock&lt;T&gt;,
    idx: <span class="hljs-built_in">usize</span>,
}
</code></pre>
<p>그리고 다음 코드로 초기화와 락을 수행할 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">impl</span>&lt;T&gt; FairLock&lt;T&gt; {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(v: T) -&gt; <span class="hljs-keyword">Self</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> vec = <span class="hljs-built_in">Vec</span>::new();
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOCK {
            vec.push(AtomicBool::new(<span class="hljs-literal">false</span>));
        }
        FairLock {
            waiting: vec,
            lock: AtomicBool::new(<span class="hljs-literal">false</span>),
            turn: AtomicUsize::new(<span class="hljs-number">0</span>),
            data: UnsafeCell::new(v),
        }
    }

    <span class="hljs-comment">// 락 함수</span>
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock</span></span>(&amp;<span class="hljs-keyword">self</span>, idx: <span class="hljs-built_in">usize</span>) -&gt; FairLockGuard&lt;T&gt; {
        <span class="hljs-comment">// idx가 최대 수 미만인지</span>
        <span class="hljs-built_in">assert!</span>(idx &lt; NUM_LOCK);

        <span class="hljs-comment">// 자신의 스레드를 락 획득 시행 중으로 설정</span>
        <span class="hljs-keyword">self</span>.waiting[idx].store(<span class="hljs-literal">true</span>, Ordering::Relaxed);
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 다른 스레드가 false를 설정한 경우면 락 획득</span>
            <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.waiting[idx].load(Ordering::Relaxed) {
                <span class="hljs-keyword">break</span>;
            }
            <span class="hljs-comment">// 공유 변수를 이용해 락 획득 테스트</span>
            <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.lock.load(Ordering::Relaxed) {
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>(_) = <span class="hljs-keyword">self</span>.lock.compare_exchange_weak(
                    <span class="hljs-literal">false</span>,
                    <span class="hljs-literal">true</span>,
                    Ordering::Relaxed,
                    Ordering::Relaxed,
                ) {
                    <span class="hljs-keyword">break</span>; <span class="hljs-comment">// 락 획득 성공</span>
                }
            }
        }
        fence(Ordering::Acquire);

        FairLockGuard {
            fair_lock: <span class="hljs-keyword">self</span>,
            idx,
        }
    }
</code></pre>
<p>다음은 언락을 구현한 코드이다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> FairLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-keyword">let</span> fl = <span class="hljs-keyword">self</span>.fair_lock; <span class="hljs-comment">// fair_lock 참조 획득</span>
        <span class="hljs-comment">// 자신의 스레드를 락 획득 시도 중이 아닌 상태로 설정</span>
        fl.waiting[<span class="hljs-keyword">self</span>.idx].store(<span class="hljs-literal">false</span>, Ordering::Relaxed);

        <span class="hljs-comment">// 현재 우선 스레드가 자신이면 다음 스레드로 넘김</span>
        <span class="hljs-keyword">let</span> turn = fl.turn.load(Ordering::Relaxed);
        <span class="hljs-keyword">let</span> next_turn = <span class="hljs-keyword">if</span> turn == <span class="hljs-keyword">self</span>.idx {
            (turn + <span class="hljs-number">1</span>) &amp; MASK <span class="hljs-comment">// 다음 스레드로 넘김</span>
        } <span class="hljs-keyword">else</span> {
            turn <span class="hljs-comment">// 현재 스레드가 아니면 그대로 유지</span>
        };

        <span class="hljs-keyword">if</span> fl.waiting[next].load(Ordering::Relaxed) {
            <span class="hljs-comment">// 다음 우선 스레드가 락 획득 중이면 전달</span>
            fl.turn.store(next_turn, Ordering::Relaxed);
            fl.waiting[next].store(<span class="hljs-literal">true</span>, Ordering::Release);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// 다음 우선 스레드가 락 획득 중이 아니면</span>
            <span class="hljs-comment">// 그 다음 스레드를 우선 스레드로 설정하고 락 해제</span>
            fl.turn.store((next + <span class="hljs-number">1</span>) &amp; MASK, Ordering::Relaxed);
            fl.lock.store(<span class="hljs-literal">false</span>, Ordering::Release);
        }
    }
}
</code></pre>
<p>까지가 락 획득 및 해제 알고리즘이다.</p>
<p>다음은 FairLock과 FairLockGaurd 타입에서 구현해야 할 트레이트다.</p>
<pre><code class="lang-rust"><span class="hljs-comment">// FairLock 타입은 스레드 사이에서 공유 가능하도록 설정</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T&gt; <span class="hljs-built_in">Sync</span> <span class="hljs-keyword">for</span> FairLock&lt;T&gt; {}
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T&gt; <span class="hljs-built_in">Send</span> <span class="hljs-keyword">for</span> FairLock&lt;T&gt; {}

<span class="hljs-comment">// 보호 대상 데이터의 이뮤터블 참조 제외</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; Deref <span class="hljs-keyword">for</span> FairLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">Target</span></span> = T;

    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; &amp;Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;*<span class="hljs-keyword">self</span>.fair_lock.data.get() }
    }
}

<span class="hljs-comment">// 보호 대상 데이터의 뮤터블 참조 제외</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; DerefMut <span class="hljs-keyword">for</span> FairLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref_mut</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; &amp;<span class="hljs-keyword">mut</span> Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.fair_lock.data.get() }
    }
}
</code></pre>
<p>Sync를 통해 스레드 사이에서 데이터를 공유할 수 있게 되었고, Send를 통해 스레드에서 소유권을 송수신할 수 있게 되었다.</p>
<p>공평한 락을 이용하는 예는 다음과 같다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">const</span> NUM_LOOP: <span class="hljs-built_in">usize</span> = <span class="hljs-number">1000</span>;
<span class="hljs-keyword">const</span> NUM_THREADS: <span class="hljs-built_in">usize</span> = <span class="hljs-number">4</span>;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> lock = Arc::new(FairLock::new(<span class="hljs-number">0</span>));
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = <span class="hljs-built_in">Vec</span>::new();

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_THREADS {
        <span class="hljs-keyword">let</span> lock0 = lock.clone();
        <span class="hljs-keyword">let</span> t = std::thread::spawn(<span class="hljs-keyword">move</span> || {
            <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOOP {
                <span class="hljs-comment">// * -&gt; _, NUM*LOOP -&gt; NUM_LOOP</span>
                <span class="hljs-comment">// 스레드 번호를 전달</span>
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> data = lock0.lock(i);
                *data += <span class="hljs-number">1</span>;
            }
        });
        v.push(t);
    }

    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> v {
        t.join().unwrap();
    }

    <span class="hljs-built_in">println!</span>(
        <span class="hljs-string">"Count = {} (expected = {})"</span>,
        *lock.lock(<span class="hljs-number">0</span>), <span class="hljs-comment">// FairLockGuard를 사용하여 락을 획득하고 데이터에 접근</span>
        NUM_THREADS * NUM_LOOP
    );
}
</code></pre>
<h2 id="heading-7yuw7lyt6529">티켓락</h2>
<p>약한 공평성을 보장하는 락은 유한 횟수 안에서 반드시 락을 획득한다. 그런데 <em>반드시</em> 락을 획득하는 것은 아니어도 경합을 줄이는 알고리즘도 존재하는데 <strong>티켓락</strong>도 그 중 하나이다.</p>
<p>티켓락의 구현은 다음과 같다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::cell::UnsafeCell;
<span class="hljs-keyword">use</span> std::ops::{Deref, DerefMut};
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicUsize, Ordering, fence};

<span class="hljs-comment">// 티켓락 타입</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">TicketLock</span></span>&lt;T&gt; {
    ticket: AtomicUsize, <span class="hljs-comment">// 티켓</span>
    turn: AtomicUsize,   <span class="hljs-comment">// 실행 가능한 티켓</span>
    data: UnsafeCell&lt;T&gt;,
}

<span class="hljs-comment">// 락 해제, 보호 대상 데이터 접근</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">TicketLockGaurd</span></span>&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    lock: &amp;<span class="hljs-symbol">'a</span> TicketLock&lt;T&gt;,
}

<span class="hljs-keyword">impl</span>&lt;T&gt; TicketLock&lt;T&gt; {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(v: T) -&gt; <span class="hljs-keyword">Self</span> {
        TicketLock {
            ticket: AtomicUsize::new(<span class="hljs-number">0</span>),
            turn: AtomicUsize::new(<span class="hljs-number">0</span>),
            data: UnsafeCell::new(v),
        }
    }
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; TicketLockGaurd&lt;T&gt; {
        <span class="hljs-comment">// 티켓 획득</span>
        <span class="hljs-keyword">let</span> t = <span class="hljs-keyword">self</span>.ticket.fetch_add(<span class="hljs-number">1</span>, Ordering::SeqCst);
        <span class="hljs-comment">// 소유권 티켓의 순서가 될 때까지 스핀</span>
        <span class="hljs-keyword">while</span> <span class="hljs-keyword">self</span>.turn.load(Ordering::Relaxed) != t {}
        fence(Ordering::Acquire);
        TicketLockGaurd { lock: <span class="hljs-keyword">self</span> }
    }
}

<span class="hljs-comment">// 락 획득 후 자동으로 해제된다.</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> TicketLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-comment">// 다음 티켓 실행 가능하도록 설정</span>
        <span class="hljs-keyword">self</span>.ticket_lock.turn.fetch_add(<span class="hljs-number">1</span>, Ordering::Release);
    }
}
</code></pre>
<p>정리하자면 티켓락은 ticket과 turn이라는 2개 공유 변수가 있는데, ticket은 다음 티켓의 번호를 기억하는 변수이고, turn은 현재 실행이 허가된 티켓 번호다.</p>
<p>구현된 티켓 락의 함수에서, ticket획득 시 스핀을 수행하지 않는 것을 볼 수 있다. 이것이 컨텐션을 줄이는 역할을 한다.</p>
<h1 id="heading-mcs">MCS 락</h1>
<p>티켓락에서는 아토믹 명령으로 접근하는 변수와 스핀 도중 접근하는 변수가 같았다. 그런데 메모리에 아토믹하게 접근하면 해당 메모리의 CPU 캐시가 배타적으로 설정되므로 대기 스레드의 해당 메모리를 읽을 떄 오버헤드가 발생할 가능성이 있다.</p>
<p>MCS락에서는 CAS 명령으로 큐의 가장 마지막에 스레드용 노드를 추가하고 스핀으로 감시할 변수는 별도로 준비한다. 즉, CAS에서 접근하는 변수는 아토믹하게 업데이트하지만 스핀으로 접근하는 변수는 일반적인 메모리 접근 명령을 이용한다.</p>
<h2 id="heading-cas-mcs">CAS가 MCS에서 갖는 의미</h2>
<p>MCS락에서 CAS는 두 가지 중요한 역할을 한다.</p>
<ol>
<li><p><strong>swap 연산</strong>: <code>self.last.swap(ptr, Ordering::Relaxed)</code>를 통해 자신을 큐의 마지막에 아토믹하게 추가한다. 이때 이전 마지막 노드의 포인터를 받아온다.</p>
</li>
<li><p><strong>compare_exchange 연산</strong>: 락을 해제할 때 <code>self.mcs_</code><a target="_blank" href="http://lock.last.compare"><code>lock.last.compare</code></a><code>_exchange(...)</code>를 사용한다. 이는 "현재 자신이 마지막 노드인가?"를 확인하고, 맞다면 큐를 비우는 작업을 아토믹하게 수행한다.</p>
</li>
</ol>
<p>핵심은 이런 CAS 연산은 큐 구조를 변경할 때만 사용하고, 각 스레드가 대기할 때는 자신의 <code>locked</code> 변수만 감시한다는 점이다. 이렇게 해서 캐시 경합을 최소화한다.</p>
<p>구현하자면 다음과 같은데</p>
<p><a target="_blank" href="http://mcs.rs"><code>mcs.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::cell::UnsafeCell;
<span class="hljs-keyword">use</span> std::ops::{Deref, DerefMut};
<span class="hljs-keyword">use</span> std::ptr::null_mut;
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicBool, AtomicPtr, Ordering, fence};

<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MCSLock</span></span>&lt;T&gt; {
    <span class="hljs-comment">// ❶</span>
    last: AtomicPtr&lt;MCSNode&lt;T&gt;&gt;, <span class="hljs-comment">// 큐의 맨 마지막</span>
    data: UnsafeCell&lt;T&gt;,         <span class="hljs-comment">// 보호 대상 데이터</span>
}

<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MCSNode</span></span>&lt;T&gt; {
    <span class="hljs-comment">// ❷</span>
    next: AtomicPtr&lt;MCSNode&lt;T&gt;&gt;, <span class="hljs-comment">// 다음 노드</span>
    locked: AtomicBool,          <span class="hljs-comment">// true이면 록 획득 중</span>
}

<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MCSLockGuard</span></span>&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    node: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> MCSNode&lt;T&gt;, <span class="hljs-comment">// 자신의 스레드 노드</span>
    mcs_lock: &amp;<span class="hljs-symbol">'a</span> MCSLock&lt;T&gt;, <span class="hljs-comment">// 큐의 가장 마지막과 보호 대상 데이터로의 참조</span>
}

<span class="hljs-comment">// 스레드끼리의 데이터 공유, 및 채널을 이용한 송수신 가능 설정</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T&gt; <span class="hljs-built_in">Sync</span> <span class="hljs-keyword">for</span> MCSLock&lt;T&gt; {}
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T&gt; <span class="hljs-built_in">Send</span> <span class="hljs-keyword">for</span> MCSLock&lt;T&gt; {}

<span class="hljs-keyword">impl</span>&lt;T&gt; MCSNode&lt;T&gt; {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>() -&gt; <span class="hljs-keyword">Self</span> {
        MCSNode {
            <span class="hljs-comment">// MCSNodeの初期化</span>
            next: AtomicPtr::new(null_mut()),
            locked: AtomicBool::new(<span class="hljs-literal">false</span>),
        }
    }
}

<span class="hljs-comment">// 보호 대상 데이터의 이뮤터블한 참조 제외</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; Deref <span class="hljs-keyword">for</span> MCSLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">Target</span></span> = T;

    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; &amp;Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;*<span class="hljs-keyword">self</span>.mcs_lock.data.get() }
    }
}

<span class="hljs-comment">// 보호 대상 데이터의 뮤터블한 참조 제외</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; DerefMut <span class="hljs-keyword">for</span> MCSLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref_mut</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; &amp;<span class="hljs-keyword">mut</span> Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.mcs_lock.data.get() }
    }
}

<span class="hljs-keyword">impl</span>&lt;T&gt; MCSLock&lt;T&gt; {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(v: T) -&gt; <span class="hljs-keyword">Self</span> {
        MCSLock {
            last: AtomicPtr::new(null_mut()),
            data: UnsafeCell::new(v),
        }
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock</span></span>&lt;<span class="hljs-symbol">'a</span>&gt;(&amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">self</span>, node: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> MCSNode&lt;T&gt;) -&gt; MCSLockGuard&lt;T&gt; {
        <span class="hljs-comment">// 자기 스레드용 노드를 초기화 ❶</span>
        node.next = AtomicPtr::new(null_mut());
        node.locked = AtomicBool::new(<span class="hljs-literal">false</span>);

        <span class="hljs-keyword">let</span> guard = MCSLockGuard {
            node,
            mcs_lock: <span class="hljs-keyword">self</span>,
        };

        <span class="hljs-comment">// 자신을 큐의 맨 마지막으로 한다 ❷</span>
        <span class="hljs-keyword">let</span> ptr = guard.node <span class="hljs-keyword">as</span> *<span class="hljs-keyword">mut</span> MCSNode&lt;T&gt;;
        <span class="hljs-keyword">let</span> prev = <span class="hljs-keyword">self</span>.last.swap(ptr, Ordering::Relaxed);

        <span class="hljs-comment">// 맨 마지막이 null이면 나무도 록을 획득하려 하지 않는 것이므로 록을 획득</span>
        <span class="hljs-comment">// null이 아닌 경우에는 자신을 큐의 맨 끝에 추가</span>
        <span class="hljs-keyword">if</span> prev != null_mut() {
            <span class="hljs-comment">// ❸</span>
            <span class="hljs-comment">// 록 획득 중이라고 설정</span>
            guard.node.locked.store(<span class="hljs-literal">true</span>, Ordering::Relaxed); <span class="hljs-comment">// ❹</span>

            <span class="hljs-comment">// 자신을 큐의 맨 끝에 추가 ❺</span>
            <span class="hljs-keyword">let</span> prev = <span class="hljs-keyword">unsafe</span> { &amp;*prev };
            prev.next.store(ptr, Ordering::Relaxed);

            <span class="hljs-comment">// 다른 스레드로부터 false로 설정될 때까지 스핀 &gt;❻</span>
            <span class="hljs-keyword">while</span> guard.node.locked.load(Ordering::Relaxed) {}
        }

        fence(Ordering::Acquire);
        guard
    }
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> MCSLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-comment">// 자신의 다음 노트가 null이고 자신이 맨 끝의 노드이면 , 맨 끝을 null로 설정한다 ❶</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.node.next.load(Ordering::Relaxed) == null_mut() {
            <span class="hljs-keyword">let</span> ptr = <span class="hljs-keyword">self</span>.node <span class="hljs-keyword">as</span> *<span class="hljs-keyword">mut</span> MCSNode&lt;T&gt;;
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>(_) = <span class="hljs-keyword">self</span>.mcs_lock.last.compare_exchange(
                <span class="hljs-comment">// ❷</span>
                ptr,
                null_mut(),
                Ordering::Release,
                Ordering::Relaxed,
            ) {
                <span class="hljs-keyword">return</span>;
            }
        }

        <span class="hljs-comment">// 자신의 다음 스레드가 lock 함수 실행 중이므로, 종료될 떄까지 대기한다 ❸</span>
        <span class="hljs-keyword">while</span> <span class="hljs-keyword">self</span>.node.next.load(Ordering::Relaxed) == null_mut() {}

        <span class="hljs-comment">// 자신의 다음 스레드를 실행 가능하게 설정 ❹</span>
        <span class="hljs-keyword">let</span> next = <span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.node.next.load(Ordering::Relaxed) };
        next.locked.store(<span class="hljs-literal">false</span>, Ordering::Release);
    }
}
</code></pre>
<h3 id="heading-lock">lock 함수의 동작 과정</h3>
<ol>
<li><p><strong>노드 초기화 (❶)</strong>: 각 스레드는 자신의 노드를 깨끗한 상태로 만든다.</p>
</li>
<li><p><strong>CAS로 큐에 추가 (❷)</strong>: <code>swap</code> 연산으로 자신을 큐의 마지막에 원자적으로 추가한다. 이전 마지막 노드의 포인터를 받아온다.</p>
</li>
<li><p><strong>대기 여부 결정 (❸)</strong>: 이전 노드가 있으면 대기해야 한다.</p>
</li>
<li><p><strong>대기 상태 설정 (❹)</strong>: 자신의 <code>locked</code>를 true로 설정한다.</p>
</li>
<li><p><strong>큐 연결 (❺)</strong>: 이전 노드의 <code>next</code>가 자신을 가리키게 한다.</p>
</li>
<li><p><strong>스핀 대기 (❻)</strong>: 자신의 <code>locked</code>가 false가 될 때까지 대기한다. 여기서는 CAS가 아닌 일반 load를 사용한다!</p>
</li>
</ol>
<h3 id="heading-drop">drop 함수의 동작 과정</h3>
<ol>
<li><p><strong>마지막 노드 확인 (❶)</strong>: 다음 노드가 없으면 자신이 마지막일 가능성이 있다.</p>
</li>
<li><p><strong>CAS로 큐 비우기 (❷)</strong>: <code>compare_exchange</code>로 "자신이 여전히 마지막이면 큐를 비운다"를 원자적으로 수행한다. 이게 실패하면 새로운 노드가 추가되는 중이란 뜻이다.</p>
</li>
<li><p><strong>다음 노드 대기 (❸)</strong>: 다음 노드가 아직 큐 연결을 완료하지 않았다면 대기한다.</p>
</li>
<li><p><strong>다음 노드 깨우기 (❹)</strong>: 다음 노드의 <code>locked</code>를 false로 설정해서 실행 가능하게 한다.</p>
</li>
</ol>
<p><a target="_blank" href="http://main.rs"><code>main.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::Arc;

<span class="hljs-keyword">const</span> NUM_LOOP: <span class="hljs-built_in">usize</span> = <span class="hljs-number">100000</span>;
<span class="hljs-keyword">const</span> NUM_THREADS: <span class="hljs-built_in">usize</span> = <span class="hljs-number">4</span>;

<span class="hljs-keyword">mod</span> mcs;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> n = Arc::new(mcs::MCSLock::new(<span class="hljs-number">0</span>));
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = <span class="hljs-built_in">Vec</span>::new();

    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_THREADS {
        <span class="hljs-keyword">let</span> n0 = n.clone();
        <span class="hljs-keyword">let</span> t = std::thread::spawn(<span class="hljs-keyword">move</span> || {
            <span class="hljs-comment">// 노드를 작성하고 록</span>
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> node = mcs::MCSNode::new();
            <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOOP {
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> r = n0.lock(&amp;<span class="hljs-keyword">mut</span> node);
                *r += <span class="hljs-number">1</span>;
            }
        });

        v.push(t);
    }

    <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> v {
        t.join().unwrap();
    }

    <span class="hljs-comment">// 노드를 작성하고 록</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> node = mcs::MCSNode::new();
    <span class="hljs-keyword">let</span> r = n.lock(&amp;<span class="hljs-keyword">mut</span> node);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"COUNT = {} (expected = {})"</span>, *r, NUM_LOOP * NUM_THREADS);
}
</code></pre>
<h1 id="heading-7kcv66as">정리</h1>
<p>공평한 배타 제어는 컨텐션이 높은 환경에서 모든 스레드가 균등하게 락을 획득할 수 있도록 보장하는 방법이다. 특히 NUMA 같은 환경에서는 메모리 접근 속도 차이로 인한 불균형이 발생할 수 있는데, 공평한 락은 이런 문제를 완화한다.</p>
<p>약한 공평성을 보장하는 락은 우선 스레드를 순서대로 지정해서 공평성을 제공한다. 티켓락은 티켓 번호를 발급받고 자신의 차례를 기다리는 방식으로 동작하며, 티켓 획득 시 스핀을 수행하지 않아 컨텐션을 줄인다.</p>
<p>MCS락은 더 나아가 캐시 경합 문제까지 해결한다. 핵심은 CAS 연산을 큐 구조 변경에만 사용하고, 각 스레드가 대기할 때는 자신만의 변수를 감시하는 것이다. 이렇게 해서 아토믹 연산으로 인한 캐시 무효화를 최소화하고, 많은 스레드가 경쟁하는 환경에서도 좋은 성능을 유지할 수 있다.</p>
<p>결과적으로 이 세 가지 락은 각각 다른 수준의 공평성과 성능 특성을 제공한다. 시스템의 요구사항과 환경에 따라 적절한 락을 선택하는 것이 중요하다.</p>
]]></content:encoded></item><item><title><![CDATA[KernelSnitch[논문 리뷰]]]></title><description><![CDATA[Paper

1. Intro
이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다.
핵심은 이러하다: "데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다"
여기서는 커널 힙 포인터 유출에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로...]]></description><link>https://maximizemaxwell.com/kernelsnitch</link><guid isPermaLink="true">https://maximizemaxwell.com/kernelsnitch</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Fri, 11 Jul 2025 03:54:41 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://www.ndss-symposium.org/ndss-paper/kernelsnitch-side-channel-attacks-on-kernel-data-structures/">Paper</a></p>
<hr />
<h2 id="heading-1-intro">1. Intro</h2>
<p>이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다.</p>
<p>핵심은 이러하다: <strong>"데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다"</strong></p>
<p>여기서는 <strong>커널 힙 포인터 유출</strong>에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로잇의 발판이 될 수 있다.</p>
<hr />
<h2 id="heading-2">2. 공격 대상 자료구조와 취약성 분석</h2>
<p>Hash Table - Futex를 중심으로 먼저 가장 중요한 공격 대상인 futex 해시 테이블을 살펴보자.</p>
<h3 id="heading-2-1-futexfast-userspace-mutex">2. 1 Futex(fast userspace mutex)란?</h3>
<ul>
<li><p>유저 공간의 빠른 동기화 매커니즘</p>
</li>
<li><p>대부분의 경우 커널 호출 없이 사용자 공간에서 처리.</p>
</li>
</ul>
<p>아래와 같이 커널에 구현되어있다.</p>
<pre><code class="lang-c"><span class="hljs-comment">// kernel/futex/futex.h</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">futex_hash_bucket</span> {</span>
    <span class="hljs-keyword">atomic_t</span> waiters;
    <span class="hljs-keyword">spinlock_t</span> lock;
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">plist_head</span> <span class="hljs-title">chain</span>;</span>
} ____cacheline_aligned_in_smp;
</code></pre>
<pre><code class="lang-c"><span class="hljs-comment">// kernel/futex/core.c</span>
<span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> {</span>
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">futex_hash_bucket</span> *<span class="hljs-title">queues</span>;</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span>            hashmask;
} __futex_data __read_mostly __aligned(<span class="hljs-number">2</span>*<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">long</span>));
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> futex_queues   (__futex_data.queues)</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> futex_hashmask (__futex_data.hashmask)</span>
</code></pre>
<h3 id="heading-22">2.2 왜 취약한가?</h3>
<pre><code class="lang-C"><span class="hljs-comment">// kernel/futex/core.c</span>
<span class="hljs-comment">// line 443~452</span>
<span class="hljs-function">struct futex_q *<span class="hljs-title">futex_top_waiter</span><span class="hljs-params">(struct futex_hash_bucket *hb, <span class="hljs-keyword">union</span> futex_key *key)</span>
</span>{
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">futex_q</span> *<span class="hljs-title">this</span>;</span>
    <span class="hljs-comment">// 리스트의 모든 요소를 순회하는 매크로</span>
    plist_for_each_entry(<span class="hljs-keyword">this</span>, &amp;hb-&gt;chain, <span class="hljs-built_in">list</span>) {
        <span class="hljs-keyword">if</span> (futex_match(&amp;<span class="hljs-keyword">this</span>-&gt;key, key))
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}
</code></pre>
<p>futex의 <code>futex_top_waiter</code>함수를 보면 리스트의 요소 개수에 따라 실행 명령어의 수가 달라질 수 있다는 것을 알 수 있다.</p>
<p><strong>Case1: Empty Bucket</strong></p>
<pre><code class="lang-txt">1. plist_for_each_entry 시작
2. 리스트가 비어있음 확인 (head-&gt;next == head)
3. 즉시 루프 종료
4. NULL 반환
실행 명령어: ~10개
</code></pre>
<p><strong>Case2: 5 elements</strong></p>
<pre><code class="lang-txt">1. plist_for_each_entry 시작
2. 첫 번째 요소 접근
   - this 포인터 로드
   - futex_match 호출 (key 비교)
   - 일치하지 않음 → 계속
3. 두 번째 요소 접근
   - 동일한 과정 반복
4. ... 5번째까지 반복
5. NULL 반환
실행 명령어: 10 + (8 × 5) = 50개
</code></pre>
<p>와 같다. 결과적으로 요소의 개수에 따라 실행 시간에 차이가 발생할 것임을 알 수 있다.</p>
<p>이 실행 시간 차이가 공격의 시작점이 된다.</p>
<h3 id="heading-23">2.3 사이클 측정하기</h3>
<p><code>futex_wake</code>의 코드를 살펴보면 다음과 같다.</p>
<pre><code class="lang-c"><span class="hljs-comment">// kernel/futex/waitwake.c</span>
<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">futex_wake</span><span class="hljs-params">(u32 __user *uaddr, <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> flags, <span class="hljs-keyword">int</span> nr_wake, u32 <span class="hljs-built_in">bitset</span>)</span> </span>{
...
    plist_for_each_entry_safe(<span class="hljs-keyword">this</span>, next, &amp;hb-&gt;chain, <span class="hljs-built_in">list</span>) {
        <span class="hljs-keyword">if</span> (futex_match (&amp;<span class="hljs-keyword">this</span>-&gt;key, &amp;key)) {
            <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>-&gt;pi_state || <span class="hljs-keyword">this</span>-&gt;rt_waiter) {
                ret = -EINVAL;
                <span class="hljs-keyword">break</span>;
            }

            <span class="hljs-comment">/* Check if one of the bits is set in both bitsets */</span>
            <span class="hljs-keyword">if</span> (!(<span class="hljs-keyword">this</span>-&gt;<span class="hljs-built_in">bitset</span> &amp; <span class="hljs-built_in">bitset</span>))
                <span class="hljs-keyword">continue</span>;

            <span class="hljs-keyword">this</span>-&gt;wake(&amp;wake_q, <span class="hljs-keyword">this</span>);
            <span class="hljs-keyword">if</span> (++ret &gt;= nr_wake)
                <span class="hljs-keyword">break</span>;
        }
    }
</code></pre>
<p><code>futex_wake</code>는 전체 리스트를 순회하고, 특별한 권한 없이도 호출 가능하므로 좋은 실행 시간 측정 도구가 될 수 있다.</p>
<h3 id="heading-24">2.4 공격하기(커널 주소 유출)</h3>
<p>공격의 목표는 <code>mm_struct</code>의 커널 주소이다. <code>mm_struct</code>는 프로세스의 메모리 관리 정보를 담고 있고, 이 주소를 알면 KASLR을 우회할 수 있다.</p>
<p>공격은 두 단계로 이루어지는데,</p>
<ol>
<li><p>해시 충돌 감지 (같은 버킷에 들어가는 주소들 수집)</p>
</li>
<li><p>역해싱으로 커널 주소 추정 (수집한 정보로 mm_struct 주소 계산) 의 과정으로 진행된다.</p>
</li>
</ol>
<h4 id="heading-241-futex">2.4.1 Futex 해시 함수의 실제 위치와 동작</h4>
<p>실제 커널 코드에서 해시 함수는 다음과 같다.</p>
<pre><code class="lang-c"><span class="hljs-comment">// kernel/futex/core.c</span>
<span class="hljs-function">struct futex_hash_bucket *<span class="hljs-title">futex_hash</span><span class="hljs-params">(<span class="hljs-keyword">union</span> futex_key *key)</span>
</span>{
    <span class="hljs-comment">// key 전체를 해시값으로 변환하고</span>
    u32 hash = jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / <span class="hljs-number">4</span>,
              key-&gt;both.offset);
    <span class="hljs-comment">// 해시값을 버킷 번호로 변환한다.</span>
    <span class="hljs-keyword">return</span> &amp;futex_queues[hash &amp; futex_hashmask];
}
</code></pre>
<p>여기서 공격의 첫 단계로 해시 충돌을 일으킨다.</p>
<h4 id="heading-242">2.4.2 비둘기집 원리로 증명되는 해시 충돌</h4>
<pre><code class="lang-C"><span class="hljs-comment">// kernel/futex/core.c</span>
<span class="hljs-comment">// line 55. 56</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> futex_queues   (__futex_data.queues)</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> futex_hashmask (__futex_data.hashmask)</span>
</code></pre>
<p>가능한 <code>futex_queues</code>는 CPU 개수에 종속적으로 결정된다.</p>
<p>따라서 비둘기집의 원리를 적용하면</p>
<ul>
<li><p>가능한 사용자 주소: $2^{48}$ 개</p>
</li>
<li><p>버킷 수: 256 ~ 4096개(시스템에 따라)</p>
</li>
</ul>
<p><strong>가능한 사용자 주소 수 &gt; 버킷 수</strong>에 따라 같은 버킷을 사용하는 서로 다른 사용자 주소의 존재성이 증명된다.</p>
<p><code>futex</code> 해시 테이블은 <code>uaddr</code>(유저 주소)와 <code>mm_struct</code>을 이용하여 해시값을 생성하므로, 서로 다른 <code>uaddr</code>에 대해 같은 <code>mm_struct</code>를 사용했을 때 해시 충돌이 발생하는 조합을 찾는다.</p>
<p>여기서 해시 충돌의 발생 조합을 찾는 과정에서 논문이 제시하는 <code>KernelSnitch</code>가 사용된다. 이를 통해 직접 커널 내부를 보지 않고도 해시 충돌 조합을 구할 수 있다.</p>
<hr />
<h2 id="heading-3-kernelsnitch"><strong>3. KernelSnitch의 원리</strong></h2>
<p>KernelSnitch는 캐시 사이드 채널을 이용해 커널 데이터 구조의 메모리 접근 여부를 사용자 공간에서 추론하는 도구다. 즉 커널이 특정 주소를 읽거나 썼는지를 CPU 캐시 상태를 통해 간접적으로 관찰할 수 있게 해준다.</p>
<p>기본 아이디어: Flush+Reload로,</p>
<p>공격자는 다음과 같은 과정을 통해 커널의 접근 여부를 관찰할 수 있다.</p>
<ol>
<li><p>특정 구조체 필드가 위치한 주소를 <code>clflush</code> 명령어로 캐시에서 제거한다.</p>
</li>
<li><p>해당 구조체를 접근하도록 커널을 유도한다. (예: futex 호출)</p>
</li>
<li><p>다시 사용자 공간에서 해당 주소를 읽고 접근 시간을 측정한다.</p>
<ul>
<li><p>캐시 접근이면 빠르게 로드됨 → 커널이 접근한 것</p>
</li>
<li><p>메모리 접근이면 느리게 로드됨 → 커널이 접근하지 않은 것</p>
</li>
</ul>
</li>
</ol>
<p>이와 같은 방법으로 커널 내부 구조체에 대한 접근 여부를 사용자 공간에서 추론할 수 있다.</p>
<p>구체적으로는 다음과 같다.</p>
<p>① 해시 충돌 탐지 시 Occupancy 측정 (Section: B. Kernel Heap Pointer Leak)</p>
<p>해당 공격에서 시간 측정은 다음과 같은 "간접적인 occupancy(버킷의 리스트 길이)" 측정 도구로 사용된다.</p>
<p>인용:</p>
<blockquote>
<p>A low occupancy level, such as for the user identifier uaddrY, indicates a different hash bucket.<br />Conversely, a higher occupancy level, e.g., uaddrZ, means that the values futex_hash(uaddrA, mmA)<br />and futex_hash(uaddrZ, mmA) match.</p>
</blockquote>
<p>의미:</p>
<ul>
<li><p>공격자가 <code>uaddrA</code>로 채운 버킷과 <code>uaddrZ</code>를 조합하여 충돌 여부를 확인한다.</p>
</li>
<li><p>이때, 커널이 리스트를 몇 개 순회하는지를 <code>futex_wake()</code> 같은 인터페이스로 호출하고,<br />  호출 시간이 오래 걸리는지로 충돌 여부를 추론한다.</p>
</li>
<li><p>즉, <strong>"같은 버킷이라면 요소가 많아지고 시간이 길어진다"</strong> 는 점을 이용하는 것이다.</p>
</li>
</ul>
<p>요약:</p>
<ul>
<li><p>더 오래 걸리면 → 같은 해시 버킷 → 충돌 발생</p>
</li>
<li><p>짧게 끝나면 → 다른 버킷 → 충돌 아님</p>
</li>
</ul>
<p>② Flush+Reload로 구조체 필드 접근 여부 측정 (Section: KernelSnitch architecture)</p>
<p>공격자는 구조체의 특정 필드를 대상으로 다음과 같은 흐름을 수행한다:</p>
<p>인용:</p>
<blockquote>
<p>We <code>clflush</code> a field of a kernel structure, invoke the syscall that may access it,<br />and then reload the same memory to measure access time.</p>
</blockquote>
<p>의미:</p>
<ul>
<li><p><code>clflush</code> → 커널 구조체 필드를 CPU 캐시에서 제거한다.</p>
</li>
<li><p>시스템콜 유도 (<code>futex_wake()</code> 등) → 커널이 해당 필드를 접근하면 다시 캐시에 올라간다.</p>
</li>
<li><p>사용자 공간에서 해당 주소를 다시 읽고 접근 시간 측정:</p>
<ul>
<li><p>캐시에 올라가 있으면 → 빠르게 읽힘 → <strong>커널이 접근했다는 뜻</strong></p>
</li>
<li><p>메모리 접근이면 → 느리게 읽힘 → <strong>접근하지 않은 것</strong></p>
</li>
</ul>
</li>
</ul>
<p>요약:</p>
<ul>
<li><p>접근 시간 짧음 → 캐시에 있음 → 커널이 접근함</p>
</li>
<li><p>접근 시간 김 → 메모리 접근 → 커널이 접근하지 않음</p>
</li>
</ul>
<hr />
<h2 id="heading-4">4. 역계산</h2>
<p>다시 본론으로 돌아와서 해시 충돌에 대한 충분한 정보를 얻었으므로 역계산을 통해 <code>mm_struct</code>를 알아낼 차례다.</p>
<p>해시 함수는 단방향 함수이므로 역계산을 하는 것은 어려워보이나, 위 과정으로부터 해시 함수의 알고리즘(jhash2)를 알고 있고 많은 사용자주소-버킷번호의 매핑을 수집했고, 해시 함수의 입력이 사용자 주소와 mm_struct 주소의 조합이라는 것도 알고 있다.</p>
<p>먼저 수집된 충돌 데이터를 정리한다. 예를 들어 주소 0x1000이 버킷 42로, 주소 0x3000도 버킷 42로 매핑되었다면, 이 두 주소는 같은 mm_struct와 조합될 때 같은 해시값을 만든다는 의미이다. 이런 식으로 10만 개의 충돌 데이터를 버킷별로 그룹화하면, 각 버킷에는 수백 개의 주소가 모인다.</p>
<p>다음으로 mm_struct가 위치할 수 있는 주소 공간을 이해해야 한다. x86_64 리눅스에서 커널 힙은 Direct Physical Mapping(DPM) 영역을 통해 접근되는데, 이 영역은 0xffff888000000000부터 0xffffc87fffffffff까지이다.</p>
<p>하지만 mm_struct는 아무 주소에나 할당되지 않는다. 슬랩 할당자의 규칙에 따라 8페이지(32KB) 단위로 정렬된 슬랩에 할당되며, 각 슬랩에는 23개의 mm_struct 객체가 들어간다.</p>
<p>이러한 제약 조건들을 활용하면 검색 공간을 2^46에서 2^35.5로 대폭 줄일 수 있다.</p>
<p>이제 실제 역계산을 수행한다. 가능한 모든 슬랩 베이스 주소를 순회하면서, 각 슬랩 내의 23개 위치에 대해 mm_struct 후보 주소를 생성한다. 각 후보 주소에 대해, 우리가 수집한 모든 충돌 패턴이 설명되는지 검증한다. 예를 들어 버킷 42에 속한 모든 주소들이 이 mm_struct 후보와 조합될 때 정말로 버킷 42로 해싱되는지 확인하는 것이다.</p>
<p>검증 과정은 매우 단순하다. 후보 mm_struct 주소와 사용자 주소로 futex_key를 재현하고, jhash2 함수로 해시값을 계산한 뒤, futex_hashmask와 AND 연산하여 버킷 번호를 구한다. 이 버킷 번호가 우리가 관찰한 버킷 번호와 일치하는지 확인한다. 모든 충돌 패턴이 일치하는 mm_struct 주소를 찾으면, 그것이 바로 우리가 찾던 커널의 정보다.</p>
<h3 id="heading-cross-cache-reuse">Cross-Cache Reuse로 공격 확장</h3>
<p>위 과정을 통해 mm_struct의 주소를 얻었다. 그럼 이제 Cross-Cache Reuse를 통해 더 위험한 공격을 수행할 수 있다. 이는 커널의 메모리 관리 메커니즘을 악용하는 기법이다.</p>
<p>리눅스 커널은 효율적인 메모리 관리를 위해 슬랩 할당자(SLUB)을 사용하는데 슬랩 할당자는 같은 크기와 타입의 객체들을 미리 할당된 메모리 슬랩에서 관리한다. 예를 들면 mm_struct 전용 캐시같은 것이다. Cross-Cache Reuse의 핵심은 한 캐시에서 해제된 메모리를 다른 캐시에서 재할당받는 것이다.</p>
<p>이를 수행하려면, 먼저 유출된 mm_struct에서 슬랩 정보를 추출한다. mm_struct를 32KB(8 페이지)로 정렬하면 슬랩 베이스 주소를 얻을 수 있다. 이 슬랩에는 23개의 mm_struct 객체가 들어있는데, 우리의 목표는 이 슬랩 전체를 해제하는 것이다.</p>
<p>슬랩을 해제하는 방법은 여러 가지가 있는데 가장 직접적인 방법으로 해당 슬랩의 모든 mm_struct를 사용하는 프로세스들을 종료시키는 것이 있다.</p>
<p>슬랩이 해제되면 이제 같은 크기의 객체로 재할당받아야 한다. 그런데 여기서 중요한 것은 mm_struct와 같은 할당자 캐시를 사용하는 객체를 선택하는 것이다. msg_msg가 같은 할당자 캐시를 사용하기 때문에, 이를 할당한다.</p>
<p>이제 msg_msg의 주소도 알아냈으며, 이를 제어할 수 있다. msg_msg를 통해 use-after-free를 트리거하거나 임의 메모리 읽기/쓰기를 수행하거나 궁극적으로는 권한 상승을 달성할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[멀티태스크와 액터 모델]]></title><description><![CDATA[멀티태스크
협조적/비협조적 멀티태스크
선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다.
협조적 멀티태스크(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식.
장점: 멀티태스크 매커니즘을 구현하기 쉽다.
단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 ...]]></description><link>https://maximizemaxwell.com/multitask-actor-model</link><guid isPermaLink="true">https://maximizemaxwell.com/multitask-actor-model</guid><category><![CDATA[multitasking]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 06 Jul 2025 11:49:52 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-66ma7yuw7yoc7iqk7ygs">멀티태스크</h1>
<h2 id="heading-7zir7kgw7kcblu5ho2ykeyhsoyggsdrqydti7dtg5zsiqttgaw">협조적/비협조적 멀티태스크</h2>
<p>선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다.</p>
<p><code>협조적 멀티태스크</code>(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식.</p>
<p>장점: 멀티태스크 매커니즘을 구현하기 쉽다.</p>
<p>단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 프로세스는 계산 리소스를 점유하게 된다.</p>
<p><code>비협조적 멀티태스크</code>(선점형, preemptive): 프로세스와 협조 없이 외부적인 작동에 따라 컨택스트 스위칭을 수행하는 멀티태스크 방식</p>
<p>장점: 비선점형에서 나타날 수 있었던 무한 루프나 블로킹 함수와 관련된 문제가 일어나지 않는다.</p>
<p>단점: 처리 시스템 구현이 어려울 수 있고, <strong>공평성</strong>을 확보하기 위해 빈번하게 컨택스트 스위칭을 수행하게 되면 비선점형에 비해 다소 오버헤드가 있을 수 있겠다.</p>
<p>여기서 공평성이란 무엇인가? 공평성은 두 가지 정의가 있는데</p>
<p><code>약한 공평성</code>: 어떤 실행 환경이 약한 공평성을 만족한다 &lt;=&gt; 어떤 프로세스가 특정 시각 이후 실행 가능한 대기 상태가 되었을 때 그 프로세스가 실행된다.</p>
<p><code>강한 공평성</code>: 어떤 실행 환경이 강한 공평성을 만족한다. &lt;=&gt; 어떤 프로세스가 특정 시각 이후 실행 가능한 대기 상대와 실행 불가능한 대기 상태의 전이를 무한히 반복할 때 최종적으로 그 프로세스는 실행된다.</p>
<p>풀어서 설명하면 <code>약한 공평성</code>은 <strong>어떤 프로세스가 계속해서 실행 가능한 상태에 있다면 언젠가는 실행된다</strong>, <code>강한 공평성</code>은 보다 강한 조건으로 단순히 언젠가 실행되는 것이 아니라 <strong>지속적으로 공정한 실행 기회를 보장받는다고</strong> 해석할 수 있다.</p>
<h1 id="heading-6re466awioykpougioutna">그린 스레드</h1>
<p>그린 스레드란 유저랜드의 소프트웨어가 독자적으로 제공한 스레드 매커니즘을 뜻한다. 그러니까 OS가 아닌 런타임 라이브러리나 VM에 의해 스케줄링되는 스레드라고 할 수 있다. 반대되는 개념으로 네이티브 스레드가 있다.</p>
<h1 id="heading-7jwh7yswiouqqounua">액터 모델</h1>
<p>액터 모델이란 동시성 프로그래밍을 위한 수학적 모델인데, <code>액터</code>라는 독립적인 계산 단위라는 개념이 존재하고, 액터는 각자의 상태와 행동을 가진다.</p>
<p>액터 모델은 비동기, 동시성 처리를 가능하게 하는 모델인데, 객체 지향에서 모든 것은 객체다 라고 생각하는 것처럼 액터 모델에선 모든 것을 액터로 취급한다.</p>
<p><img src="https://i.imgur.com/YypDb8A.png" alt /></p>
<p>각 액터는 메시지 큐를 가지고 있고 이를 경유하여 데이터를 송수신한다.</p>
<p>좀 더 자세히 알아보자면</p>
<p>액터는 다음 세 가지 요소로 구성된다.</p>
<ul>
<li><p>Behavior</p>
</li>
<li><p>State</p>
</li>
<li><p>Mailbox</p>
</li>
</ul>
<p>액터 간에는 메시지를 주고 받을 수 있고, 독립적인 메모리 공간을 갖는다. 액터는 다른 액터에게 메시지를 보내는 방법으로만 영향을 미칠 수 있기 때문에, 스레드에서 공통으로 접근하는 값이 변경되어 다른 스레드에 영향을 미칠 수 있는 것과 대비된다.</p>
<p>메시지 처리의 과정을 살펴보면,</p>
<ol>
<li><p>Main문에서 액터를 지정해 메시지를 보내면</p>
</li>
<li><p>해당 액터의 수신함에 메시지가 들어가고</p>
</li>
<li><p>수신한 액터는 이벤트가 발생해</p>
</li>
<li><p>받은 순서대로 메시지를 확인한다.</p>
</li>
<li><p>그리고 액터는 수신한 메시지에 따라 행동을 결정하여 실행한다.</p>
</li>
</ol>
<p>여기서 상태(State)는 액터 자신만 변경 가능하다. 그리고 메시지를 수신받은 액터가 취할 수 있는 행동은</p>
<ul>
<li><p>자신의 상태 변경</p>
</li>
<li><p>자식 액터 생성 및 제거</p>
</li>
<li><p>다른 액터에 메시지 전송 등이 있다.</p>
</li>
</ul>
<p><img src="https://i.imgur.com/IIUXlZT.png" alt /></p>
<p>액터 간의 상호작용을 나타낸 그림인데, 결과적으로 말하고자 하는 것은</p>
<ol>
<li><p>메모리를 공유하지 않고</p>
</li>
<li><p>메시지로만 상호작용하기 때문에 동시성 처리에서 비교적 덜 머리아프게(?) 문제를 해결할 수 있다는 것이다.</p>
</li>
</ol>
<h1 id="heading-7lc46rog7j6q66om">참고자료</h1>
<p><a target="_blank" href="https://syntaxsugar.tistory.com/entry/Actor%EC%95%A1%ED%84%B0#2.2%20Fault%20Tolerance-1">아주 잘 정리된 블로그</a></p>
<p><a target="_blank" href="https://scripties.uba.uva.nl/download?fid=679533">관련 논문</a></p>
]]></content:encoded></item><item><title><![CDATA[비동기 프로그래밍]]></title><description><![CDATA[비동기 프로그래밍이란?
비동기 프로그래밍이란 무엇인가? 작성한 순서대로 작동하는 프로그래밍 모델을 동기 프로그래밍(synchronous programming) 이라고 부른다.
반대로 비동기 프로그래밍은 작성한 순서대로만 작동하는 것은 아님을 의미하게 되겠다. 정확히는 독립해서 발생하는 이벤트에 대한 처리를 기술하기 위한 동시성 프로그래밍 기법을 총칭해서 비동기 프로그래밍이라고 한다. 비동기 프로그래밍을 이용하면 전화가 울리면 전화를 받는 것과 ...]]></description><link>https://maximizemaxwell.com/non-synchronous</link><guid isPermaLink="true">https://maximizemaxwell.com/non-synchronous</guid><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 15 Jun 2025 10:30:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749983395307/c56b8da9-1033-4d9c-9ffa-37f861e1feae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-67me64z6riwio2uhouhnoq3uouemouwjeydtouegd8">비동기 프로그래밍이란?</h1>
<p>비동기 프로그래밍이란 무엇인가? 작성한 순서대로 작동하는 프로그래밍 모델을 <strong>동기 프로그래밍(synchronous programming)</strong> 이라고 부른다.</p>
<p>반대로 비동기 프로그래밍은 작성한 순서대로만 작동하는 것은 아님을 의미하게 되겠다. 정확히는 독립해서 발생하는 이벤트에 대한 처리를 기술하기 위한 동시성 프로그래밍 기법을 총칭해서 <strong>비동기 프로그래밍</strong>이라고 한다. 비동기 프로그래밍을 이용하면 전화가 울리면 전화를 받는 것과 같이 이벤트에 대응한 작동을 구현할 수 있다.</p>
<p>비동기 프로그램을 구현하는 방법으로 콜백 함수나 시그널(인터럽트)을 이용하는 방법이 있으나 여기서는 Future, async/await를 설명한다. 러스트에서는 async/await을 이용한 비동기 라이브러리로 Tokio, smol이 있다. 또한 nix와 future크레이트를 이용할 것이다. <a target="_blank" href="https://github.com/smol-rs/smol">smol github</a></p>
<h1 id="heading-64z7iucioyenouyha">동시 서버</h1>
<p>여기서는 반복 서버, 동시 서버를 알아보고 그 구현을 설명한다.</p>
<p><strong>반복 서버(interactive server)</strong>: 클라이언트로 요청받은 순서대로 처리하는 서버 <strong>동시 서버(concurrent server)</strong>: 요청을 동시에 처리하는 서버이다.</p>
<h2 id="heading-67cy67o1ioyenouyha">반복 서버</h2>
<p>다음은 단순한 반복 서버의 예시 코드다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> ::std::io::{BufRead, BufReader, BufWriter, Read};
<span class="hljs-keyword">use</span> std::net::TcpListener;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// TCP 8000번 포트 리스닝</span>
    <span class="hljs-keyword">let</span> listener = TcpListener::bind(<span class="hljs-string">"127.0.0.1:8000"</span>).unwrap();
    <span class="hljs-comment">// 커넥션 요청을 받아들이기</span>
    <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>((stream, _)) = listener.accept() {
        <span class="hljs-comment">// 읽기, 쓰기 객체 생성</span>
        <span class="hljs-keyword">let</span> stream0 = stream.try_clone().unwrap();
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> reader = BufReader::new(stream0);
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> writer = BufWriter::new(stream);

        <span class="hljs-comment">// 클라이언트로부터 한 줄 읽기</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> buf = <span class="hljs-built_in">String</span>::new();
        reader.read_line(&amp;<span class="hljs-keyword">mut</span> buf).unwrap();
        writer.write_all(buf.as_bytes()).unwrap();
        writer.flush().unwrap();
    }
}
</code></pre>
<p>위 코드에서는 커넥션 요청을 받아 클라이언트로부터 데이터를 수신하고 송신 처리를 완료하지 않으면 다음 클라이언트의 처리를 수행할 수 없다.</p>
<h2 id="heading-64z7iucioyenouyha-1">동시 서버</h2>
<p>동시 서버는 클라이언트로부터의 커넥션 요청, 데이터 도착 등의 처리를 이벤트 단위로 세세히 분류하여 이벤트에 따라 처리할 수 있다. 네트워크 소켓이나 파일 등 IO이벤트 감시에는 리눅스에서 epoll BSD 계열에서는 kqueue라는 시스템 콜을 이용할 수 있다.</p>
<p><strong>IO 이벤트 감시</strong>는 파일 디스크립터를 감시하는 것이다. 여러 TCP 커넥션이 존재할 때 서버는 여러 개의 파일 디스크립터를 가지는데, 이 디스크립터들에 대해 읽기 쓰기 가능 여부를 epoll 등을 이용해 판정할 수 있다.</p>
<p><img src="https://i.imgur.com/4R05m6w.png" alt /></p>
<p>그림에서는 프로세스 A가 0~4까지의 파일 디스크립터를 이용하는데, 커널 내부의 프로세스와 디스크립터 정보가 저장되어 있으므로 이 정보들을 이용해 epoll등을 통한 파일 디스크립터 감시를 수행한다.</p>
<p>다음 코드는 epoll을 이용한 병렬 서버의 예시 코드이다. 다만 논블로킹 설정은 수행되지 않았다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> nix::sys::epoll::{
    EpollCreateFlags, EpollEvent, EpollFlags, EpollOp, epoll_create1, epoll_ctl, epoll_wait,
};
<span class="hljs-keyword">use</span> std::collections::HashMap;
<span class="hljs-keyword">use</span> std::io::{BufRead, BufReader, BufWriter, Write};
<span class="hljs-keyword">use</span> std::net::TcpListener;
<span class="hljs-keyword">use</span> std::os::unix::io::{AsRawFd, RawFd};

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// epoll을 이용해 여러 연결을 동시에 감시하기 위해 준비</span>
    <span class="hljs-keyword">let</span> epoll_in = EpollFlags::EPOLLIN;
    <span class="hljs-keyword">let</span> epoll_add = EpollOp::EpollCtlAdd;
    <span class="hljs-keyword">let</span> epoll_del = EpollOp::EpollCtlDel;
    <span class="hljs-comment">// 서버 소켓 생성</span>
    <span class="hljs-keyword">let</span> listener = TcpListener::bind(<span class="hljs-string">"127.0.0.1:8081"</span>).unwrap();
    <span class="hljs-comment">// epoll 인스턴스 생성</span>
    <span class="hljs-keyword">let</span> epfd = epoll_create1(EpollCreateFlags::empty()).unwrap();

    <span class="hljs-keyword">let</span> listen_fd = listener.as_raw_fd();
    <span class="hljs-comment">// 서버 소켓을 epoll에 등록: 새로운 연결이 들어올 때 알려달라고 커널에 요청</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> ev = EpollEvent::new(epoll_in, listen_fd <span class="hljs-keyword">as</span> <span class="hljs-built_in">u64</span>);
    epoll_ctl(epfd, epoll_add, listen_fd, &amp;<span class="hljs-keyword">mut</span> ev).unwrap();

    <span class="hljs-comment">// 동시성: 클라이언트별 버퍼를 HashMap에 저장</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> fd2buf = HashMap::new();
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> events = <span class="hljs-built_in">vec!</span>[EpollEvent::empty(); <span class="hljs-number">1024</span>];

    <span class="hljs-comment">// 단일 스레드에서 다수 연결을 epoll_wait로 동시에 처리</span>
    <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>(nfds) = epoll_wait(epfd, &amp;<span class="hljs-keyword">mut</span> events, -<span class="hljs-number">1</span>) {
        <span class="hljs-keyword">for</span> n <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..nfds {
            <span class="hljs-keyword">if</span> events[n].data() == listen_fd <span class="hljs-keyword">as</span> <span class="hljs-built_in">u64</span> {
                <span class="hljs-comment">// 새 클라이언트 접속</span>
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>((stream, _)) = listener.accept() {
                    <span class="hljs-keyword">let</span> fd = stream.as_raw_fd();
                    <span class="hljs-comment">// 각각의 클라이언트 연결을 독립적으로 관리</span>
                    <span class="hljs-keyword">let</span> stream0 = stream.try_clone().unwrap();
                    <span class="hljs-keyword">let</span> reader = BufReader::new(stream0);
                    <span class="hljs-keyword">let</span> writer = BufWriter::new(stream);
                    fd2buf.insert(fd, (reader, writer));
                    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"accept: fd = {}"</span>, fd);

                    <span class="hljs-comment">// 새 클라이언트 소켓을 epoll에 등록: 이후부터 이 소켓 읽기 이벤트 감시</span>
                    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> ev = EpollEvent::new(epoll_in, fd <span class="hljs-keyword">as</span> <span class="hljs-built_in">u64</span>);
                    epoll_ctl(epfd, epoll_add, fd, &amp;<span class="hljs-keyword">mut</span> ev).unwrap();
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// 기존 클라이언트로부터 데이터 도착</span>
                <span class="hljs-keyword">let</span> fd = events[n].data() <span class="hljs-keyword">as</span> RawFd;
                <span class="hljs-keyword">let</span> (reader, writer) = fd2buf.get_mut(&amp;fd).unwrap();

                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> buf = <span class="hljs-built_in">String</span>::new();
                <span class="hljs-keyword">let</span> n = reader.read_line(&amp;<span class="hljs-keyword">mut</span> buf).unwrap();

                <span class="hljs-keyword">if</span> n == <span class="hljs-number">0</span> {
                    <span class="hljs-comment">// 클라이언트가 연결을 종료한 경우</span>
                    <span class="hljs-keyword">let</span> _ev = EpollEvent::new(EpollFlags::empty(), fd <span class="hljs-keyword">as</span> <span class="hljs-built_in">u64</span>);
                    epoll_ctl(epfd, epoll_del, fd, <span class="hljs-literal">None</span>).unwrap();
                    fd2buf.remove(&amp;fd);
                    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"close: fd = {}"</span>, fd);
                    <span class="hljs-keyword">continue</span>;
                }
                <span class="hljs-built_in">println!</span>(<span class="hljs-string">"read: fd = {}, buf = {}"</span>, fd, buf.trim());
                <span class="hljs-comment">// 읽은 데이터를 다시 클라이언트로 전송</span>
                writer.write_all(buf.as_bytes()).unwrap();
                writer.flush().unwrap();
            }
        }
    }
}
</code></pre>
<p>이렇게 epoll 등을 사용해 여러 IO에 대해 동시에 처리를 수행하는 방법을 <strong>IO 다중화(IO multiplexing)</strong> 이라고 부른다. IO다중화의 방법론의 하나로 위 코드에서처럼 이벤트에 대해 처리를 기술하는 프로그래밍 모델, 디자인 패턴을 <strong>이벤트 주도(event-driven)</strong> 이라고 한다.</p>
]]></content:encoded></item><item><title><![CDATA[동기 처리 1-2]]></title><description><![CDATA[의사 각성
의사 각성의 정의는 다음과 같다.
의사 각성: 특정한 조건이 만족될 때까지 대기 중이어야 하는 프로세스가 해당 조건이 만족되지 않았음에도 실행 상태로 변경되는 것
의사 각성은 어떤 경우에 일어나는가?
다음 코드는 의사 각성을 일으키는 C 코드 예시이다.
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>...]]></description><link>https://maximizemaxwell.com/concurrent1-2</link><guid isPermaLink="true">https://maximizemaxwell.com/concurrent1-2</guid><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 15 Jun 2025 09:30:35 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-7j2y7iksioqwgeyesq">의사 각성</h1>
<p><strong>의사 각성</strong>의 정의는 다음과 같다.</p>
<p>의사 각성: 특정한 조건이 만족될 때까지 대기 중이어야 하는 프로세스가 해당 조건이 만족되지 않았음에도 실행 상태로 변경되는 것</p>
<p>의사 각성은 어떤 경우에 일어나는가?</p>
<p>다음 코드는 의사 각성을 일으키는 C 코드 예시이다.</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;signal.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;sys/types.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-keyword">pthread_mutex_t</span> mutex = PTHREAD_MUTEX_INITIALIZER;
<span class="hljs-keyword">pthread_cond_t</span> cond = PTHREAD_COND_INITIALIZER;

<span class="hljs-comment">// 시그널 핸들러: 시그널 번호 표시</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">handler</span><span class="hljs-params">(<span class="hljs-keyword">int</span> sig)</span> </span>{ <span class="hljs-built_in">printf</span>(<span class="hljs-string">"recieved signal %d\n"</span>, sig); }

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
  <span class="hljs-comment">// 프로세스 ID 취득, 표시.</span>
  <span class="hljs-keyword">pid_t</span> pid = getpid();
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"pid: %d\n"</span>, pid);

  <span class="hljs-comment">// SIGUSR1 시그널 핸들러 등록</span>
  signal(SIGUSR1, handler);

  <span class="hljs-comment">// wait처리, 그러나 notify하는 스레드가 없음.</span>
  <span class="hljs-comment">// 영원히 대기?</span>
  pthread_mutex_lock(&amp;mutex);
  <span class="hljs-keyword">if</span> (pthread_cond_wait(&amp;cond, &amp;mutex) != <span class="hljs-number">0</span>) {
    perror(<span class="hljs-string">"pthread_cond_wait"</span>);
    <span class="hljs-built_in">exit</span>(EXIT_FAILURE);
  }
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"spurious wake up\n"</span>);
  pthread_mutex_unlock(&amp;mutex);

  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>이 코드는 영원히 대기해야할 것 같지만 SIGUSR1 시그널이 송신되고 프로그램이 종료될 수 있다.</p>
<p>이유는</p>
<pre><code class="lang-c">pthread_mutex_lock(&amp;mutex);
  <span class="hljs-keyword">if</span> (pthread_cond_wait(&amp;cond, &amp;mutex) != <span class="hljs-number">0</span>) {
    perror(<span class="hljs-string">"pthread_cond_wait"</span>);
    <span class="hljs-built_in">exit</span>(EXIT_FAILURE);
  }
</code></pre>
<p>여기서 <code>pthread_cond_wait()</code>는 <code>pthread_cond_signal()</code>이나 <code>pthread_cond_broadcast()</code> 때문에만 돌아오는 함수가 아니기 때문이다.</p>
<p>POSIX 규격에서 <strong>조건 변수를 기다리던 스레드는 아무 이유 없이 깨어날 수 있다</strong>고 허용하고 있다.</p>
<pre><code class="lang-txt">Spurious wakeups from the _pthread_cond_timedwait_() or _pthread_cond_wait_() functions may occur.
</code></pre>
<p><a target="_blank" href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html">POSIX.1-2017</a></p>
<p>리눅스에서는 내부적으로 <code>futex</code>라는 시스템 콜을 이용하는데 커널 버전 2.6.22 이전에서는 <code>futex</code>에 의해 의사 각성이 발생했지만 이후 버전에는 발생하지 않는다.</p>
<p>정확히는 커널이 2.6.22 이후 안정화되어 내부 race로 인한 spurious wakeup은 줄어든 게 사실이지만 POSIX 규격상 spurious wakeup의 가능성을 명시하고 있는 것이 표준이기 때문에 <strong>완전히 배제된 개념은 아니라고 보는 게 옳다고 본다.</strong></p>
<h1 id="heading-7iuc6re464sq">시그널</h1>
<p>시그널이란 특정한 사건이 발생했다고 프로세스에게 알려주기 위해 UNIX 시스템에서 사용하는 알림 매커니즘이다. 일반적으로 시그널과 멀티스레드는 궁합이 맞지 않다고 알려져 있는데, 어떤 타이밍에서 시그널 핸들러가 호출되는지 알 수 없기 때문이다.</p>
<p>아래 코드는 시그널 핸들러를 사용할 때 데드락이 발생하는 전형적인 시그널 락 데드락 문제이다.</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">handler</span><span class="hljs-params">(<span class="hljs-keyword">int</span> sig)</span> </span>{
  pthread_mutex_lock(&amp;mutex); <span class="hljs-comment">// 데드락</span>
  <span class="hljs-comment">// 무언가 처리하기</span>
  pthread_mutex_unlock(&amp;mutex);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
  pthread_mutex_lock(&amp;mutex);
  <span class="hljs-comment">// 이 시점에서 시그널 발생</span>
  <span class="hljs-comment">// handler()진입</span>
  <span class="hljs-comment">// handler 안에서 또 다시 pthread_mutex_lock(&amp;mutex)</span>
  pthread_mutex_unlock(&amp;mutex);
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>위 코드는 어떤 스레드가 뮤텍스를 가진 상태에서 다시 락을 요청하게 되어 블록되는 상황이다.</p>
<p>위와 같은 상태에 빠지는 것을 방지하기 위해 <strong>시그널을 수신하는 전용 스레드</strong>를 이용할 수 있다.</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;signal.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-keyword">pthread_mutex_t</span> mutex = PTHREAD_MUTEX_INITIALIZER;
<span class="hljs-keyword">sigset_t</span> <span class="hljs-built_in">set</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">handler</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
  pthread_detach(pthread_self());

  <span class="hljs-keyword">int</span> sig;
  <span class="hljs-keyword">for</span> (;;) {
    <span class="hljs-keyword">if</span> (sigwait(&amp;<span class="hljs-built_in">set</span>, &amp;sig) != <span class="hljs-number">0</span>) {
      perror(<span class="hljs-string">"sigwait"</span>);
      <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);
    }
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Received signal %d\n"</span>, sig);
    pthread_mutex_lock(&amp;mutex);
    <span class="hljs-comment">// Simulate some work</span>
    pthread_mutex_unlock(&amp;mutex);
  }
  <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">worker</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) {
    pthread_mutex_lock(&amp;mutex);
    <span class="hljs-comment">// Simulate some work</span>
    sleep(<span class="hljs-number">1</span>);
    pthread_mutex_unlock(&amp;mutex);
    sleep(<span class="hljs-number">1</span>);
  }
  <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
  <span class="hljs-keyword">pid_t</span> pid = getpid();
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"pid: %d\n"</span>, pid);
  <span class="hljs-comment">// SIGSUR1 시그널을 블록으로 설정</span>
  sigemptyset(&amp;<span class="hljs-built_in">set</span>);
  sigaddset(&amp;<span class="hljs-built_in">set</span>, SIGUSR1);
  <span class="hljs-keyword">if</span> (pthread_sigmask(SIG_BLOCK, &amp;<span class="hljs-built_in">set</span>, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
    perror(<span class="hljs-string">"pthread_sigmask"</span>);
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);
  }

  <span class="hljs-keyword">pthread_t</span> th, wth;
  pthread_create(&amp;th, <span class="hljs-literal">NULL</span>, handler, <span class="hljs-literal">NULL</span>);
  pthread_create(&amp;wth, <span class="hljs-literal">NULL</span>, worker, <span class="hljs-literal">NULL</span>);
  pthread_join(wth, <span class="hljs-literal">NULL</span>);

  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>중요한 것은 <code>pthread_sigmask</code> 함수에서 시그널을 블록하는 것과 시그널 수신용 스레드를 준비해 sigwait함수에서 동기로 시그널을 수신하는 것이다. 이러면 어떤 타이밍에 시그널이 발생해도 데드락 상태가 되지 않는다.</p>
<p>어떤 시그널을 블록으로 설정하면 해당 시그널이 프로세스에 송신되어도 시그널 핸들러가 실행되지 않는다.</p>
<p>러스트에서는 <code>signal_hook</code>이라는 크레이트가 있으며 시그널을 다룰 때는 이 크레이트를 사용할 것을 권장한다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> libc::SIGUSR1;
<span class="hljs-keyword">use</span> signal_hook::iterator::Signals;
<span class="hljs-keyword">use</span> std::sync::{Arc, Mutex};
<span class="hljs-keyword">use</span> std::{error::Error, process, thread, time::Duration};

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() -&gt; <span class="hljs-built_in">Result</span>&lt;(), <span class="hljs-built_in">Box</span>&lt;<span class="hljs-keyword">dyn</span> Error&gt;&gt; {
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"pid: {}"</span>, process::id());

    <span class="hljs-comment">// 수신 대상 시그널인 SIGUSR1지정하여 SIGNALS 타입 생성</span>
    <span class="hljs-keyword">let</span> signals = Signals::new([SIGUSR1])?;
    <span class="hljs-keyword">let</span> signals = Arc::new(Mutex::new(signals));
    <span class="hljs-keyword">let</span> worker_signals = Arc::clone(&amp;signals);
    <span class="hljs-keyword">let</span> handle = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> signals = worker_signals.lock().unwrap();
        <span class="hljs-keyword">for</span> sig <span class="hljs-keyword">in</span> signals.forever() {
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"received signal: {:?}"</span>, sig);
        }
    });

    thread::sleep(Duration::from_secs(<span class="hljs-number">10</span>));
    <span class="hljs-comment">// 종료 직전 스레드 join</span>
    handle.join().ok();
    <span class="hljs-literal">Ok</span>(())
}
</code></pre>
<p>위와 같이 시그널 수신 스레드를 작성할 수 있다. 시그널을 수신한 것을 여러 스레드에 알리고 싶을 때는 crossbeam-channel 크레이트를 이용할 수 있다.</p>
<p><a target="_blank" href="https://docs.rs/crossbeam/latest/crossbeam/channel/index.html">crossbeac-channel</a></p>
<h1 id="heading-66mu66qo66asiouwsoumroywta">메모리 배리어</h1>
<p>현대적인 CPU에서는 반드시 기계어 명령 순에 따라서만 처리를 수행하지는 않는다. 이러한 실행을 <strong>아웃 오브 오더</strong> 실행이라고 한다. 아웃 오브 오더 실행을 하는 이유는 파이프라인 처리 시 <strong>단위 시간당 실행 명령 수(instructions-per-second, IPC)</strong> 를 높이기 위해서이다.</p>
<p>예를 들면 메모리상에만 존재하는 주소 A와 캐시 라인상에 존재하는 주소 B가 있고 read A, read B순으로 기계어가 있을 때, 반드시 A-&gt;B순으로 읽는 것이 효율적이지는 않다. 이러한 경우에 <strong>아웃 오브 오더</strong> 실행은 <strong>IPC</strong> 향상에 기여할 수 있다.</p>
<p>아웃 오브 오더 실행은 IPC 향상에 도움이 되기도 하지만 몇 가지 문제를 발생시킬 수 있다. 이 파트의 제목이기도 한 <strong>메모리 배리어(메모리 펜스)</strong> 는 아웃 오브 오더 실행에 관한 여러 문제에서 시스템을 보호하기 위한 처리이다.</p>
<p><img src="https://i.imgur.com/PZ1s0j6.png" alt /></p>
<p>위 그림에서, 프로세스 A와 B는 공유 변수에 접근하기 위해 락을 획득하고 락 획득 중에 공유 변수를 증가한다고 가정한다. 그러나 A의 write(v+1)작업이 끝나기 이전 B가 이를 읽어오기 때문에 최종 결괏값으로 2를 기대했으나 1이 도출되는 상황이 된다. 그러나 실제로는 메모리 배리어 명령에 의해 메모리 읽기 쓰기 순서가 보증되기 때문에 위와 같은 일은 벌어지지 않는다.</p>
<p>AArch64의 메모리 배리어 명령 중 일부를 소개하자면 아래 사진과 같다.</p>
<p><img src="https://i.imgur.com/IBiqelE.png" alt /></p>
<p>이 중 dmb는</p>
<ul>
<li><p><code>dmb</code>:</p>
<ul>
<li><p><code>dmb sy</code>: 이 명령어를 기준으로 앞의 <strong>모든 load/store</strong>가 완료되기 전에는 이후의 load/store가 시작되지 않음</p>
</li>
<li><p><code>dmb st</code>: 이 명령 이전의 store과 이후의 store간의 순서만 보장하고, load는 관여하지 않음.</p>
</li>
<li><p><code>dmb ld</code>: 이 명령 이전의 load와 이후의 load간의 순서만 보장.</p>
</li>
</ul>
</li>
</ul>
<p>그림으로는</p>
<p><img src="https://i.imgur.com/kX6P7vL.png" alt /></p>
<p>와 같다.</p>
<p><strong>메모리 오더링(memory ordering)</strong> 은 메모리 읽기 쓰기를 수행하는 순서를 말하고 기계어 순서와 다르게 실행되는 것을 <strong>리오더링</strong>이라 부른다. 리오더링이 발생하는 명령 순서는 읽기 쓰기 순서나 CPU 아키텍처에 따라 달라진다.</p>
<p><img src="https://i.imgur.com/MtrUI7u.png" alt /></p>
<h2 id="heading-65s7iqk7yq4ioyvho2goouvuq">러스트 아토믹</h2>
<p>러스트 언어에도 아토믹 변수를 다루는 라이브러리인 <code>std::sync::atomic</code>이 존재한다. 러스트에서는 아토믹 변수를 읽고 쓸 떄 메모리 배리어의 방법을 지정해야 하고 이 때 <strong>Ordering</strong>타입을 이용한다.</p>
<p>러스트에서의 Ordering은 아래 표와 같다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Ordering</td><td>Load 이후 연산 순서</td><td>Store 이전 연산 순서</td><td>설명</td><td>사용 예</td></tr>
</thead>
<tbody>
<tr>
<td><code>Relaxed</code></td><td>❌</td><td>❌</td><td>순서 보장 없음 (원자성만 보장됨)</td><td>단순 카운터, 통계 수집</td></tr>
<tr>
<td><code>Acquire</code></td><td>O</td><td>❌</td><td>Load 이후 연산 순서 보장</td><td>락 취득(load-acquire)</td></tr>
<tr>
<td><code>Release</code></td><td>❌</td><td>O</td><td>Store 이전 연산 순서 보장</td><td>락 해제(store-release)</td></tr>
<tr>
<td><code>AcqRel</code></td><td>O</td><td>O</td><td>Acquire + Release 결합(load, store)</td><td>CAS, 교환 연산</td></tr>
<tr>
<td><code>SeqCst</code></td><td>O (전역적으로)</td><td>O (전역적으로)</td><td>전역 순서까지 보장하는 가장 강한 모델</td><td>대부분의 락 없는 동기화</td></tr>
</tbody>
</table>
</div><p>러스트에서 뮤텍스는</p>
<ul>
<li><p>보호 대상 데이터에는 락 후에만 접근 가능</p>
</li>
<li><p>락 해제는 자동으로 되는 특성이 있었다.</p>
</li>
</ul>
<p>따라서 러스트의 아토믹 변수를 이용한 스핀락을 구현하면 아래와 같다.</p>
<pre><code class="lang-rust">
<span class="hljs-keyword">use</span> std::ops::{Deref, DerefMut};
<span class="hljs-keyword">use</span> std::sync::Arc;
<span class="hljs-keyword">use</span> std::sync::atomic::{AtomicBool, Ordering};

<span class="hljs-keyword">const</span> NUM_THREADS: <span class="hljs-built_in">usize</span> = <span class="hljs-number">4</span>;
<span class="hljs-keyword">const</span> NUM_LOOP: <span class="hljs-built_in">usize</span> = <span class="hljs-number">100_000</span>;

<span class="hljs-comment">// 스핀락 구조체 정의</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SpinLock</span></span>&lt;T&gt; {
    locked: AtomicBool,
    data: UnsafeCell&lt;T&gt;,
}

<span class="hljs-comment">// 락 가드 구조체</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SpinLockGuard</span></span>&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    spin_lock: &amp;<span class="hljs-symbol">'a</span> SpinLock&lt;T&gt;,
}

<span class="hljs-keyword">impl</span>&lt;T&gt; SpinLock&lt;T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(data: T) -&gt; <span class="hljs-keyword">Self</span> {
        SpinLock {
            locked: AtomicBool::new(<span class="hljs-literal">false</span>),
            data: UnsafeCell::new(data),
        }
    }

<span class="hljs-comment">// 락 함수</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">lock</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; SpinLockGuard&lt;T&gt; {
        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 빠른 경합 회피용 빠른 스핀</span>
            <span class="hljs-keyword">while</span> <span class="hljs-keyword">self</span>.locked.load(Ordering::Relaxed) {
                std::hint::spin_loop(); <span class="hljs-comment">// CPU friendly spin</span>
            }
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>
                .locked
                .compare_exchange_weak(<span class="hljs-literal">false</span>, <span class="hljs-literal">true</span>, Ordering::Acquire, Ordering::Relaxed)
                .is_ok()
            {
                <span class="hljs-keyword">break</span>;
            }
        }
        SpinLockGuard { spin_lock: <span class="hljs-keyword">self</span> }
    }

    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">unlock</span></span>(&amp;<span class="hljs-keyword">self</span>) {
        <span class="hljs-keyword">self</span>.locked.store(<span class="hljs-literal">false</span>, Ordering::Release);
    }
}

<span class="hljs-comment">// unsafe marker: SpinLock&lt;T&gt;는 T가 Send일 때만 Send/Sync 가능</span>
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T: <span class="hljs-built_in">Send</span>&gt; <span class="hljs-built_in">Send</span> <span class="hljs-keyword">for</span> SpinLock&lt;T&gt; {}
<span class="hljs-keyword">unsafe</span> <span class="hljs-keyword">impl</span>&lt;T: <span class="hljs-built_in">Send</span>&gt; <span class="hljs-built_in">Sync</span> <span class="hljs-keyword">for</span> SpinLock&lt;T&gt; {}

<span class="hljs-comment">// SpinLockGuard는 Deref/DerefMut 제공</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; Deref <span class="hljs-keyword">for</span> SpinLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">Target</span></span> = T;
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; &amp;Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;*<span class="hljs-keyword">self</span>.spin_lock.data.get() }
    }
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; DerefMut <span class="hljs-keyword">for</span> SpinLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">deref_mut</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) -&gt; &amp;<span class="hljs-keyword">mut</span> Self::Target {
        <span class="hljs-keyword">unsafe</span> { &amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.spin_lock.data.get() }
    }
}

<span class="hljs-comment">// Drop 구현으로 unlock</span>
<span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>, T&gt; <span class="hljs-built_in">Drop</span> <span class="hljs-keyword">for</span> SpinLockGuard&lt;<span class="hljs-symbol">'a</span>, T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">drop</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {
        <span class="hljs-keyword">self</span>.spin_lock.unlock();
    }
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> lock = Arc::new(SpinLock::new(<span class="hljs-number">0</span>));
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> handles = <span class="hljs-built_in">Vec</span>::new();

    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_THREADS {
        <span class="hljs-keyword">let</span> lock_clone = Arc::clone(&amp;lock);
        <span class="hljs-keyword">let</span> handle = std::thread::spawn(<span class="hljs-keyword">move</span> || {
            <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOOP {
                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> data = lock_clone.lock();
                *data += <span class="hljs-number">1</span>;
            }
        });
        handles.push(handle);
    }

    <span class="hljs-keyword">for</span> handle <span class="hljs-keyword">in</span> handles {
        handle.join().unwrap();
    }

    <span class="hljs-keyword">let</span> result = lock.lock();
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Final result: {}"</span>, *result);
}
</code></pre>
<p><code>UnsafeCell</code> 사용 없이 구현하면 위와 같다.</p>
]]></content:encoded></item><item><title><![CDATA[동기 처리+동시성에서의 문제점]]></title><description><![CDATA[여기서는 배리어 동기, readers-writers lock, 동시성 특유의 문제점과 버그 일부분에 대해 다룬다.

배리어 동기
배리어 동기란 공유 변수를 증가시키다 공유 변수가 어떤 일정한 수에 도달하면 배리어를 벗어나 처리를 수행하는 방식이다.
이러한 배리어 동기를 사용하면 진행 순서를 보장할 수 있는데 예컨대 스레드 A가 스레드 B보다 먼저 넘어가면 안 된다고 가정하자. 이러한 경우에 A가 포함된 모든 1단계 스레드가 배리어에 도달해야만 ...]]></description><link>https://maximizemaxwell.com/concurrent2nproblem</link><guid isPermaLink="true">https://maximizemaxwell.com/concurrent2nproblem</guid><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 08 Jun 2025 11:44:43 GMT</pubDate><content:encoded><![CDATA[<p>여기서는 배리어 동기, readers-writers lock, 동시성 특유의 문제점과 버그 일부분에 대해 다룬다.</p>
<hr />
<h1 id="heading-67cw66as7ja0ioupmeq4sa">배리어 동기</h1>
<p>배리어 동기란 공유 변수를 증가시키다 공유 변수가 어떤 <strong>일정한 수에 도달하면 배리어를 벗어나 처리를 수행</strong>하는 방식이다.</p>
<p>이러한 배리어 동기를 사용하면 <strong>진행 순서를 보장</strong>할 수 있는데 예컨대 스레드 A가 스레드 B보다 먼저 넘어가면 안 된다고 가정하자. 이러한 경우에 A가 포함된 모든 1단계 스레드가 배리어에 도달해야만 다음 단계(B 포함)를 실행할 수 있게 되므로 진행 순서를 보장할 수 있다.</p>
<h2 id="heading-7iqk7zwa6529ioq4souwmcdrsldrpqzslrqg64z6riw">스핀락 기반 배리어 동기</h2>
<p>다음 코드는 스핀락 기반의 배리어 동기를 보여주는데,</p>
<pre><code class="lang-c"><span class="hljs-comment">// 공유 변수에 대한 포인터 cnt와 최댓값 max</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">barrier</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *cnt, <span class="hljs-keyword">int</span> max)</span> </span>{
    __sync_fetch_and_add(cnt, <span class="hljs-number">1</span>); <span class="hljs-comment">// 공유 변수를 아토믹하게 증가</span>
    <span class="hljs-keyword">while</span> (*cnt &lt; max); <span class="hljs-comment">// cnt가 가리키는 값이 max가 될때까지 대기</span>
}
</code></pre>
<p>배리어 동기를 이용하는 코드의 예시는 다음과 같다.</p>
<pre><code class="lang-c"><span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> num = <span class="hljs-number">0</span>; <span class="hljs-comment">//공유변수</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">worker</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{ <span class="hljs-comment">// 스레드용 함수</span>
    barrier(&amp;num, <span class="hljs-number">10</span>); <span class="hljs-comment">// 배리어 동기 실행. 모든 스레드가 배리어에 도달하기 전까지 5행 처리 X</span>
    <span class="hljs-comment">// 무언가 처?리</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
    <span class="hljs-comment">// 스레드 생성</span>
    <span class="hljs-keyword">pthread_t</span> th[<span class="hljs-number">10</span>];
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) {
        <span class="hljs-keyword">if</span> (pthread_create(&amp;th[i], <span class="hljs-literal">NULL</span>, worker, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
            perror(<span class="hljs-string">"pthread_create"</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
        }
    }
    <span class="hljs-comment">// join</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<hr />
<h2 id="heading-pthreads">Pthreads를 이용한 배리어 동기</h2>
<p>스핀락을 이용한 배리어 동기에서는 대기 중에 루프 처리를 수행해야 하므로 CPU 리소스를 낭비할 여지가 있다. Pthreads의 조건 변수를 이용해서 이를 완화하는 방법에 대해 알아보자.</p>
<p>이것은 다음 코드로 구현될 수 있는데</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>

<span class="hljs-keyword">pthread_mutex_t</span> barrier_mut = PTHREAD_MUTEX_INITIALIZER;
<span class="hljs-keyword">pthread_cond_t</span> barrier_cond = PTHREAD_COND_INITIALIZER;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">barrier</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *cnt, <span class="hljs-keyword">int</span> max)</span> </span>{
    <span class="hljs-keyword">if</span> (pthread_mutex_lock(&amp;barrier_mut) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_mutex_lock"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
    }
    (*cnt)++; <span class="hljs-comment">// 락 획득 후 공유변수 증가</span>

    <span class="hljs-keyword">if</span> (*cnt == max) { 
        <span class="hljs-comment">// *cnt와 max가 같으면 스레드 모두 실행</span>
        <span class="hljs-keyword">if</span> (pthread_cond_broadcast(&amp;barrier_cond) != <span class="hljs-number">0</span>) {
            perror(<span class="hljs-string">"pthread_cond_broadcast"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);    
        }
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">do</span> { <span class="hljs-comment">// 값이 같지 않으면 대기</span>
            <span class="hljs-keyword">if</span> (pthread_cond_wait(&amp;barrier_cond, &amp;barrier_mut) != <span class="hljs-number">0</span>) {
                perror(<span class="hljs-string">"pthread_cond_wait"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
            } 
        } <span class="hljs-keyword">while</span> (*cnt &lt; max);
    }
    <span class="hljs-keyword">if</span> (pthread_mutex_unlock(&amp;barrier_mut) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_mutex_unlock"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
    }
}
</code></pre>
<hr />
<h2 id="heading-65s7iqk7yq4iouwsoumroywtcdrj5nqula">러스트 배리어 동기</h2>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Barrier}; <span class="hljs-comment">// 배리어 동기</span>
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// 스레드 핸들러 저장</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = <span class="hljs-built_in">Vec</span>::new();
    <span class="hljs-comment">// 10 스레드만큼의 배리어 동기를 Arc로 감싸기</span>
    <span class="hljs-keyword">let</span> barrier = Arc::new(Barrier::new(<span class="hljs-number">10</span>));
    <span class="hljs-comment">// 스레드 실행</span>
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">10</span> {
        <span class="hljs-keyword">let</span> b = barrier.clone();
        <span class="hljs-keyword">let</span> th = thread::spawn(<span class="hljs-keyword">move</span> || {
            b.wait(); <span class="hljs-comment">// 배리어 동기 대기</span>
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"finished barrier"</span>);
        });
        v.push(th);
    }
    <span class="hljs-keyword">for</span> th <span class="hljs-keyword">in</span> v {
        th.join().unwrap();
    }
}
</code></pre>
<p>실행 결과는 다음과 같다.</p>
<pre><code class="lang-bash">   Compiling barrier v0.1.0 (/home/max/coding/concurrent/barrier)
    Finished `dev` profile [unoptimized + debuginfo] target(s) <span class="hljs-keyword">in</span> 0.17s
     Running `target/debug/barrier`
finished barrier
finished barrier
finished barrier
finished barrier
finished barrier
finished barrier
finished barrier
finished barrier
finished barrier
</code></pre>
<hr />
<h1 id="heading-readers-writers">Readers-Writers 락</h1>
<p>레이스 컨디션이 발생하는 원인은 쓰기 처리 때문이므로 쓰기만 배타적으로 수행하면 문제가 발생하지 않는다.</p>
<p>뮤텍스와 세마포어에서는 프로세스에 특별한 역할을 설정하지 않았다. 사실 쓰기만 문제가 되는데 읽기까지 제한을 하는 것은 비효율적으로 보일 수 있다. 따라서 쓰기와 읽기를 구분하고, 읽기끼리는 병렬성을 허용하되 쓰기 때는 제한하는 방식을 제안할 수 있는데 그것이 <strong>Readers-Writers락</strong>이다.</p>
<p><strong>Readers-Writer락(RW 락)</strong> 에서는 읽기만 수행하는 프로세스(reader)와 쓰기만 수행하는 프로세스(wrtier)로 분류하고 다음 제약을 만족하도록 한다.</p>
<ul>
<li><p>락을 획득 중인 reader는 같은 시각에 다수 존재할 수 있다.</p>
</li>
<li><p>락을 획득 중인 writer는 같은 시각에 1개만 존재할 수 있다.</p>
</li>
<li><p>reader와 writer는 같은 시각에 락 획득 상태가 될 수 없다.</p>
</li>
</ul>
<h2 id="heading-rw">스핀락 기반 RW락</h2>
<p>스핀락 기반의 RW락 알고리즘은 다음과 같이 구현될 수 있다. Reader용 락 획득과 반환 함수, Writer용 락 획득과 반환 함수는 별도의 인터페이스로 구현되어 있으니 실제 이용할 때는 공유 리소스의 읽기만 수행할지 쓰기만 수행할지 적절하게 판단해서 이용해야 한다.</p>
<pre><code class="lang-C"><span class="hljs-comment">// reader용 락 획득 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">rwlock_read_acquire</span><span class="hljs-params">(<span class="hljs-keyword">int</span> *rcnt, <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *wcnt)</span> </span>{
    <span class="hljs-keyword">for</span> (;;) {
        <span class="hljs-keyword">while</span> (*wcnt); <span class="hljs-comment">//writer가 있으면 대기</span>
        __sync_fetch_and_add(rcnt, <span class="hljs-number">1</span>);
        <span class="hljs-keyword">if</span> (*wcnt == <span class="hljs-number">0</span>) <span class="hljs-comment">// writer가 없으면 락 획득</span>
            <span class="hljs-keyword">break</span>;
        __sync_fetch_and_sub(rcnt, <span class="hljs-number">1</span>);
    }
}

<span class="hljs-comment">// reader용 락 반환 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">rwlock_read_release</span><span class="hljs-params">(<span class="hljs-keyword">int</span> *rcnt)</span> </span>{
    __sync_fetch_and_sub(rcnt, <span class="hljs-number">1</span>);
}

<span class="hljs-comment">// writer용 락 획득 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">rwlock_write_acquire</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> *lock, <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *rcnt, <span class="hljs-keyword">int</span> *wcnt)</span> </span>{
    __sync_fetch_and_add(wcnt, <span class="hljs-number">1</span>);
    <span class="hljs-keyword">while</span> (*rcnt); <span class="hljs-comment">// reader가 있으면 대기</span>
    spinlock_acquire(lock);
}

<span class="hljs-comment">// writer용 락 반환 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">rwlock_write_release</span><span class="hljs-params">(bbol *lock, <span class="hljs-keyword">int</span> *wcnt)</span> </span>{
    spinlock_release(lock);
    __sync_fetch_and_sub(wcnt, <span class="hljs-number">1</span>);
}
</code></pre>
<p>활용은 C에서는</p>
<pre><code class="lang-c"><span class="hljs-keyword">int</span> rcnt = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> wcnt = <span class="hljs-number">0</span>;
<span class="hljs-keyword">bool</span> lock = <span class="hljs-literal">false</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">reader</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">for</span> (;;) {
        rwlock_read_acquire(&amp;rcnt, &amp;wcnt);
        <span class="hljs-comment">//크리티컬 섹션</span>
        rwlock_read_release(&amp;rcnt);
    }
}
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">writer</span> <span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">for</span> (;;) {
        rwlock_write_acquire(&amp;lock, &amp;rcnt, &amp;wcnt);
        <span class="hljs-comment">// 크리티컬 섹션</span>
        rwlock_write_release(&amp;lock, &amp;wcnt);
    }
}
</code></pre>
<p>러스트에서는</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::RwLock;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> lock = RwLock::new(<span class="hljs-number">10</span>);
    {
        <span class="hljs-keyword">let</span> v1 = lock.read().unwrap();
        <span class="hljs-keyword">let</span> v2 = lock.read().unwrap();
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"v1: {}, v2: {}"</span>, *v1, *v2);
    }
    {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> v = lock.write().unwrap();
        *v = <span class="hljs-number">7</span>;
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"v: {}"</span>, *v);
    }
}
</code></pre>
<h2 id="heading-7iuk7zajioygjeupha">실행 속도</h2>
<p>Read가 대부분인 경우 뮤텍스보다 RW락을 사용하는 것이 실행 속도를 향상시키는 데 도움이 될 것이다. Read에 대해 병렬성을 허용하기 위한 것이 RW락이므로, 당연한 결과이다.</p>
<hr />
<h1 id="heading-64z7iuc7isxio2uhouhnoq3uouemouwjsdtirnsnkdsnzgg67ke6re47jmaiousuoygnoygka">동시성 프로그래밍 특유의 버그와 문제점</h1>
<p>동시성 프로그래밍도 특유의 버그와 문제점이 있다. 대표적으로 데드락, 라이브락, 기아(starvation) 등 문제가 발생한다.</p>
<h2 id="heading-642w65oc6529">데드락</h2>
<p>동시성 프로그래밍 특유의 비유에 대해 <strong>식사하는 철학자 문제</strong>라는 비유가 있다. 원본은 포크지만 젓가락이 더 올바른 비유에 가까울 듯하니 젓가락으로 비유하겠다. 철학자가 원탁에 둘러앉아 있는데 철학자 사이에 젓가락이 하나씩 놓여있고, 젓가락 두 개가 있어야 식사를 할 수 있다.</p>
<p><img src="https://i.imgur.com/LOXvFwd.png" alt /></p>
<p>위의 그림과 같은 상황에서, 철학자들은 다음 알고리즘을 따르는데,</p>
<ol>
<li><p>왼쪽 젓가락이 사용 가능할 때까지 기다렸다가 사용 가능해지면 젓가락을 든다.</p>
</li>
<li><p>오른쪽에 대해서도 마찬가지로 사용 가능해지면 젓가락을 든다.</p>
</li>
<li><p>식사를 한다.</p>
</li>
<li><p>젓가락을 다시 내려놓는다.</p>
</li>
<li><p>1로 돌아간다.</p>
</li>
</ol>
<p>만약에 모든 철학자가 동시에 자신의 왼편에 놓인 젓가락을 집어든다고 치자. 모든 철학자가 젓가락 하나만을 가지고 있기 때문에 모두 식사할 수 없는데, 위 알고리즘에 따르면 모든 철학자들이 오른쪽이 사용 가능해질 때까지 대기하고 있지만, 모두가 식사를 하고 젓가락을 내려놓을 수 없으니 모두가 오른쪽을 대기하는 상태로 멈춰버리고, 모두가 식사를 하지 못한다.</p>
<p>이처럼 자원이 비는 것을 기다리며 더 이상 처리가 진행되지 않는 상태를 <strong>데드락</strong>이라고 한다.</p>
<p>위의 철학자 문제를 코드로 구현해보자면 아래와 같다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Mutex};
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> c0 = Arc::new(Mutex::new(()));
    <span class="hljs-keyword">let</span> c1 = Arc::new(Mutex::new(()));

    <span class="hljs-keyword">let</span> c0_p0 = c0.clone();
    <span class="hljs-keyword">let</span> c1_p0 = c1.clone();

    <span class="hljs-comment">//철학자0</span>
    <span class="hljs-keyword">let</span> p0 = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">1000</span> {
            <span class="hljs-keyword">let</span> _lock0 = c0_p0.lock().unwrap();
            <span class="hljs-keyword">let</span> _lock1 = c1_p0.lock().unwrap();
            <span class="hljs-comment">// 철학자가 밥을 먹는 코드</span>
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Philosopher 0 is eating"</span>);
        }
    });

    <span class="hljs-keyword">let</span> p1 = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">1000</span> {
            <span class="hljs-keyword">let</span> _lock1 = c1.lock().unwrap();
            <span class="hljs-keyword">let</span> _lock0 = c0.lock().unwrap();
            <span class="hljs-comment">// 철학자가 밥을 먹는 코드</span>
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Philosopher 1 is eating"</span>);
        }
    });
    p0.join().unwrap();
    p1.join().unwrap();
}
</code></pre>
<p>이 코드는 마지막까지 실행될 수도 있지만 데드락이 발생해 더 이상 실행되지 않기도 한다.</p>
<p>특히 RW락은 데드락을 주의해야 하는데, 아래 코드처럼 데드락을 발생시킬 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, RwLock};
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> val = Arc::new(RwLock::new(<span class="hljs-literal">true</span>));

    <span class="hljs-keyword">let</span> t = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">let</span> flag = val.read().unwrap(); <span class="hljs-comment">//read락 획득</span>
        <span class="hljs-keyword">if</span> *flag {
            *val.write().unwrap() = <span class="hljs-literal">false</span>; <span class="hljs-comment">// write락 획득</span>
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"flag is true"</span>);
        }
    });
    t.join().unwrap();
}
</code></pre>
<p>read락을 획득한 상태로 write락을 획득하게 되기 때문에 데드락 상태가 된다.</p>
<p>이를 해결하기 위해 다음과 같이 제시될 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, RwLock};
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> val = Arc::new(RwLock::new(<span class="hljs-literal">true</span>));

    <span class="hljs-keyword">let</span> t = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">let</span> flag = *val.read().unwrap(); <span class="hljs-comment">// 읽기 락 drop</span>
        <span class="hljs-keyword">if</span> flag {
            *val.write().unwrap() = <span class="hljs-literal">false</span>; <span class="hljs-comment">// 쓰기 락 가능</span>
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"flag is true"</span>);
        }
    });
    t.join().unwrap();
}
</code></pre>
<p>이 코드는 데드락이 발생하지 않는다.</p>
<p>첫 번째 코드의 작동 원리를 알아보자면 이렇다. <a target="_blank" href="http://val.read"><code>val.read</code></a><code>()</code>는 <code>RwLockReadGuard</code>를 생성하고 <code>flag</code>가 그것을 소유하게 된다. <code>if *flag</code>는 단순히 참조할 뿐, <code>flag</code>의 수명은 줄이지 않는다. 따라서 읽기 락이 유지된 상태로 쓰기 락을 시도하게 된다.</p>
<p>두 번째 코드에서 <a target="_blank" href="http://val.read"><code>val.read</code></a><code>()</code>는 <code>RwLockReadGuard</code>를 반환하지만 이 값을 <code>*</code>로 역참조해 복사한다. 이 복사는 소유권의 이동이 아니라 스칼라 타입의 복사(Copy)라서 <code>flag</code>는 <code>read_guard</code>없이도 존재할 수 있다. 그리고 읽기 락은 곧바로 drop되기 때문에 쓰기 락을 안전하게 획득할 수 있다.</p>
<hr />
<h2 id="heading-starvation">라이브락과 starvation</h2>
<p>식사하는 철학자 문제를 수정해서 다음과 같이 알고리즘을 변경한다고 치자.</p>
<ol>
<li><p>왼쪽 젓가락이 사용 가능할 때까지 기다렸다가 사용 가능해지면 젓가락을 든다.</p>
</li>
<li><p>오른쪽에 대해서도 마찬가지로 사용 가능해지면 젓가락을 들지만, 어느정도 기다려도 들 수 있는 상태가 되지 않으면 왼쪽 젓가락을 내려놓고 단계1로 되돌아간다.</p>
</li>
<li><p>식사를 한다.</p>
</li>
<li><p>젓가락을 다시 내려놓는다.</p>
</li>
<li><p>1로 돌아간다.</p>
</li>
</ol>
<p>이 고쳐진 알고리즘은 잘 작동할 것 같지만 마찬가지로 두 명의 철학자가 왼쪽 젓가락을 동시에 들고 있다가 내려놓고 동시에 드는 상황이 일어나면 이 상황이 반복되어 처리가 진행되지 않는다.</p>
<p><img src="https://i.imgur.com/55s2qBf.png" alt /></p>
<p>이렇게 리소스를 획득하는 처리는 수행하지만 다음 리소스를 획득하지 못해 이후의 처리를 하지 못하게 되는 상태를 <strong>라이브락</strong>이라고 한다.</p>
<p>라이브락이 되는 스테이트 머신은 다음과 같이 정의할 수 있다.</p>
<p><strong>스테이트 머신에서 라이브락이 발생할 가능성이 있다 &lt;=&gt; 특정한 리소스를 획득하는 상태에는 도달하지만 그 외의 상태에는 절대 도달하지 못하는 무한 전이 사례가 존재한다.</strong></p>
<p>그리고 <strong>굶주림(기아, starvation)</strong> 이란 특정 프로세스만 리소스 획득 상태로 전이하지 못하는 상태에 있는 것을 말한다. 이 또한 스테이트 머신을 정의하면</p>
<p><strong>스테이트 머신에서 굶주림이 발생할 가능성이 있다 &lt;=&gt; 어떤 프로세스가 존재하고, 항상 리소스를 요청 가능한 상태에 도달하지만 실제 리소스를 획득하고 진행하는 상태로는 결코 도달하지 못하는 무한 루프를 가진다..</strong></p>
<hr />
<h2 id="heading-7j2a7zaj7juqioyvjoqzooumroymma">은행원 알고리즘</h2>
<p>데드락을 회피하기 위한 알고리즘으로 다익스트라가 고안한 <strong>은행원 알고리즘</strong>이 있다. 예를 들자면 다음과 같다.</p>
<p><img src="https://i.imgur.com/DAkS7j0.png" alt /></p>
<p>은행원은 2000만원의 자본을 가지고 있고 기업 A와 기업 B는 각각 1500만원, 2000만원의 자금이 필요하다. 은행원은 먼저 기업 A에 1500만원을 대출해주고, A는 이 돈으로 사업을 하고 다시 1500만원을 상환한다. 그 후 은행원은 B에 2000만원을 대출해주고 B는 사업 후 2000만원을 상환한다.</p>
<p>단 여기에 다음과 같은 제약이 존재하는데</p>
<ul>
<li><p>기업은 자금을 대출하는 즉시 사용</p>
</li>
<li><p>기업은 필요한 금액을 대출받으면 반드시 전액 상환</p>
</li>
<li><p>기업은 전액 대출받기 전까지 사업할 수 없다.</p>
</li>
<li><p>이자는 존재하지 않는다.</p>
</li>
<li><p>은행은 보유 자금 이상 대출할 수 없다.</p>
</li>
</ul>
<p>이 상황에서 아래 그림과 같은 상황을 가정하자.</p>
<p><img src="https://i.imgur.com/aiCCnF7.png" alt /></p>
<p>이 경우 데드락이 발생한다. 둘 모두 사업할 수 없고 상환할 수도 없다.</p>
<p>위 두 사례에서 알 수 있는 것은 은행원의 자본과 각 기업이 필요로 하는 자본의 금액을 미리 알고 있다면 시뮬레이션을 통해 어떻게 대출을 해주면 데드락이 되는지 예측 가능하다. 따라서 은행원 알고리즘에서는 데드락이 발생하는 상태로 전이하는지 시뮬레이션을 통해 판정함으로써 데드락을 회피한다.</p>
<p>조금 더 부연설명하자면 은행은 한 기업이 요구하는 자금의 전액 정도는 가지고 있어야 한다. 즉 위 그림에서 은행원이 전체 1000만원을 가지고 있다면 성공적으로 대출해줄 수 없을 것이다. 그러나 위에서는 2000만원을 보유하고 있어 순서를 적절히 규정해주면 모두에게 돈을 빌려줄 수 있다.</p>
<p>이를 정리하면 은행원 알고리즘에서 상태는 두 가지가 있다.</p>
<p><strong>안전상태</strong>: 시스템 교착을 일으키지 않고 각 프로세스가 요구한 양만큼 자원을 할당해줄 수 있는 <strong>안전 순서열</strong>이 존재하는 상태 <strong>불안전상태</strong>: 프로세스가 요구한 양만큼 자원 할당이 불가해 안전순서열이 존재하지 않는 상태</p>
<p>은행원 알고리즘은 결과적으로 자원의 할당 허용 여부를 결정하기 전에 미리 결정된 모든 자원의 최대 가능한 할당량을 시뮬레이션하여 안전 여부를 검사하고, 대기중인 모든 활동의 교착 상태 가능성을 조사하여 <strong>안전 상태</strong> 여부를 검사한다.</p>
<p>코드로는 다음과 같다.</p>
<p><a target="_blank" href="http://main.rs"><code>main.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Mutex};

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Resource</span></span>&lt;<span class="hljs-keyword">const</span> NRES: <span class="hljs-built_in">usize</span>, <span class="hljs-keyword">const</span> NTH: <span class="hljs-built_in">usize</span>&gt; {
    available: [<span class="hljs-built_in">usize</span>; NRES],         <span class="hljs-comment">// 이용 가능한 리소스</span>
    allocation: [[<span class="hljs-built_in">usize</span>; NRES]; NTH], <span class="hljs-comment">// 스레드 i가 확보 중인 리소스</span>
    max: [[<span class="hljs-built_in">usize</span>; NRES]; NTH],        <span class="hljs-comment">// 스레드 i가 필요로 하는 리소스의 최댓값</span>
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-keyword">const</span> NRES: <span class="hljs-built_in">usize</span>, <span class="hljs-keyword">const</span> NTH: <span class="hljs-built_in">usize</span>&gt; Resource&lt;NRES, NTH&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(available: [<span class="hljs-built_in">usize</span>; NRES], max: [[<span class="hljs-built_in">usize</span>; NRES]; NTH]) -&gt; <span class="hljs-keyword">Self</span> {
        Resource {
            available,
            allocation: [[<span class="hljs-number">0</span>; NRES]; NTH],
            max,
        }
    }

    <span class="hljs-comment">// 현재 상태가 데드록을 발생시키지 않는가 확인</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">is_safe</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> finish = [<span class="hljs-literal">false</span>; NTH]; <span class="hljs-comment">// 스레드 i는 리소스 획득과 반환에 성공했는가?</span>
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> work = <span class="hljs-keyword">self</span>.available.clone(); <span class="hljs-comment">// 이용 가능한 리소스의 시뮬레이션값</span>

        <span class="hljs-keyword">loop</span> {
            <span class="hljs-comment">// 모든 스레드 i와 리소스 j에 대해,</span>
            <span class="hljs-comment">// finish[i] == false &amp;&amp; work[j] &gt;= (self.max[i][j] - self.allocation[i][j])</span>
            <span class="hljs-comment">// 을 만족하는 스레드를 찾는다.</span>
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> found = <span class="hljs-literal">false</span>;
            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> num_true = <span class="hljs-number">0</span>;
            <span class="hljs-keyword">for</span> (i, alc) <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.allocation.iter().enumerate() {
                <span class="hljs-keyword">if</span> finish[i] {
                    num_true += <span class="hljs-number">1</span>;
                    <span class="hljs-keyword">continue</span>;
                }

                <span class="hljs-comment">// need[j] = self.max[i][j] - self.allocation[i][j] 를 계산하고, </span>
                <span class="hljs-comment">// 모든 리소스 j에 대해, work[j] &gt;= need[j] 인가를 판정한다.</span>
                <span class="hljs-keyword">let</span> need = <span class="hljs-keyword">self</span>.max[i].iter().zip(alc).map(|(m, a)| m - a);
                <span class="hljs-keyword">let</span> is_avail = work.iter().zip(need).all(|(w, n)| *w &gt;= n);
                <span class="hljs-keyword">if</span> is_avail {
                    <span class="hljs-comment">// 스레드 i가 리소스 확보 가능</span>
                    found = <span class="hljs-literal">true</span>;
                    finish[i] = <span class="hljs-literal">true</span>;
                    <span class="hljs-keyword">for</span> (w, a) <span class="hljs-keyword">in</span> work.iter_mut().zip(alc) {
                        *w += *a <span class="hljs-comment">// 스레드 i가 현재 확보하고 있는 리소스를 반환</span>
                    }
                    <span class="hljs-keyword">break</span>;
                }
            }

            <span class="hljs-keyword">if</span> num_true == NTH {
                <span class="hljs-comment">// 모든 스레드가 리소스 확보 가능하면 안전함</span>
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            }

            <span class="hljs-keyword">if</span> !found {
                <span class="hljs-comment">// 스레드가 리소스를 확보할 수 없음</span>
                <span class="hljs-keyword">break</span>;
            }
        }

        <span class="hljs-literal">false</span>
    }

    <span class="hljs-comment">// id번 째의 스레드가 resource를 하나 얻음</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">take</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, id: <span class="hljs-built_in">usize</span>, resource: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-comment">// 스레드 번호, 리소스 번호 검사</span>
        <span class="hljs-keyword">if</span> id &gt;= NTH || resource &gt;= NRES || <span class="hljs-keyword">self</span>.available[resource] == <span class="hljs-number">0</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        <span class="hljs-comment">// 리소스 확보를 시험해 본다</span>
        <span class="hljs-keyword">self</span>.allocation[id][resource] += <span class="hljs-number">1</span>;
        <span class="hljs-keyword">self</span>.available[resource] -= <span class="hljs-number">1</span>;

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.is_safe() {
            <span class="hljs-literal">true</span> <span class="hljs-comment">// 리소스 확보 성공</span>
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// 리소스 확보에 실패했으므로 상태 원복</span>
            <span class="hljs-keyword">self</span>.allocation[id][resource] -= <span class="hljs-number">1</span>;
            <span class="hljs-keyword">self</span>.available[resource] += <span class="hljs-number">1</span>;
            <span class="hljs-literal">false</span>
        }
    }

    <span class="hljs-comment">// id번 째의 스레드가 resource를 하나 반환</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">release</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, id: <span class="hljs-built_in">usize</span>, resource: <span class="hljs-built_in">usize</span>) {
        <span class="hljs-comment">// 스레드 번호, 리소스 번호를 검사</span>
        <span class="hljs-keyword">if</span> id &gt;= NTH || resource &gt;= NRES || <span class="hljs-keyword">self</span>.allocation[id][resource] == <span class="hljs-number">0</span> {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">self</span>.allocation[id][resource] -= <span class="hljs-number">1</span>;
        <span class="hljs-keyword">self</span>.available[resource] += <span class="hljs-number">1</span>;
    }
}

<span class="hljs-meta">#[derive(Clone)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Banker</span></span>&lt;<span class="hljs-keyword">const</span> NRES: <span class="hljs-built_in">usize</span>, <span class="hljs-keyword">const</span> NTH: <span class="hljs-built_in">usize</span>&gt; {
    resource: Arc&lt;Mutex&lt;Resource&lt;NRES, NTH&gt;&gt;&gt;,
}

<span class="hljs-keyword">impl</span>&lt;<span class="hljs-keyword">const</span> NRES: <span class="hljs-built_in">usize</span>, <span class="hljs-keyword">const</span> NTH: <span class="hljs-built_in">usize</span>&gt; Banker&lt;NRES, NTH&gt; {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(available: [<span class="hljs-built_in">usize</span>; NRES], max: [[<span class="hljs-built_in">usize</span>; NRES]; NTH]) -&gt; <span class="hljs-keyword">Self</span> {
        Banker {
            resource: Arc::new(Mutex::new(Resource::new(available, max))),
        }
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">take</span></span>(&amp;<span class="hljs-keyword">self</span>, id: <span class="hljs-built_in">usize</span>, resource: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> r = <span class="hljs-keyword">self</span>.resource.lock().unwrap();
        r.take(id, resource)
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">release</span></span>(&amp;<span class="hljs-keyword">self</span>, id: <span class="hljs-built_in">usize</span>, resource: <span class="hljs-built_in">usize</span>) {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> r = <span class="hljs-keyword">self</span>.resource.lock().unwrap();
        r.release(id, resource)
    }
}
</code></pre>
<p><a target="_blank" href="http://main.rs"><code>main.rs</code></a></p>
<pre><code class="lang-rust"><span class="hljs-keyword">mod</span> banker;

<span class="hljs-keyword">use</span> banker::Banker;
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-keyword">const</span> NUM_LOOP: <span class="hljs-built_in">usize</span> = <span class="hljs-number">100000</span>;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// 이용 가능한 젓가락의 수, 철학자가 이용하는 포크 최대 수 설정</span>
    <span class="hljs-keyword">let</span> banker = Banker::&lt;<span class="hljs-number">2</span>, <span class="hljs-number">2</span>&gt;::new([<span class="hljs-number">1</span>, <span class="hljs-number">1</span>], [[<span class="hljs-number">1</span>, <span class="hljs-number">1</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">1</span>]]);
    <span class="hljs-keyword">let</span> banker0 = banker.clone();

    <span class="hljs-keyword">let</span> philosopher0 = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOOP {
            <span class="hljs-comment">// 젓가락 0과 1을 확보</span>
            <span class="hljs-keyword">while</span> !banker0.take(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>) {}
            <span class="hljs-keyword">while</span> !banker0.take(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>) {}

            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"0: eating"</span>);

            <span class="hljs-comment">// 젓가락 0과 1을 반환</span>
            banker0.release(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
            banker0.release(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>);
        }
    });

    <span class="hljs-keyword">let</span> philosopher1 = thread::spawn(<span class="hljs-keyword">move</span> || {
        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..NUM_LOOP {
            <span class="hljs-comment">// 젓가락 1과 0을 확보</span>
            <span class="hljs-keyword">while</span> !banker.take(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>) {}
            <span class="hljs-keyword">while</span> !banker.take(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>) {}

            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"1: eating"</span>);

            <span class="hljs-comment">// 젓가락 1과 0을 반환</span>
            banker.release(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
            banker.release(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>);
        }
    });

    philosopher0.join().unwrap();
    philosopher1.join().unwrap();
}
</code></pre>
<hr />
<h2 id="heading-7j6s6rea6529">재귀락</h2>
<p><strong>재귀락</strong>이란 락을 획득한 상태에서 프로세스가 그 락을 해제하기 전에 다시 그 락을 획득하는 것을 말한다.</p>
<p>재귀락이 발생하면 어떻게 되는가? 는 알고리즘의 구현에 따라 다르다. 단순한 뮤텍스 구현에 대해 재귀락을 수행하면 데드락 상태가 되겠지만 재귀락을 수행해도 처리를 계속할 수 있는 락이 존재하고 이를 <strong>재진입 가능한(reentrant) 락</strong>이라고 한다. 다시 정리하자만 <strong>재귀락을 수행해도 데드락 상태에 빠지지 않고 처리를 계속할 수 있는 락</strong>이다.</p>
<p>C언어에서 재진입 가능한 뮤텍스를 구현하면 다음과 같다.</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;assert.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdbool.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>

<span class="hljs-comment">// 재진입 가능한 뮤텍스용 타입 ❶</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">reent_lock</span> {</span>
    <span class="hljs-keyword">bool</span> lock; <span class="hljs-comment">// 록용 공유 변수</span>
    <span class="hljs-keyword">int</span> id;    <span class="hljs-comment">// 현재 록을 획득 중인 스레드 ID, 0이 아니면 록 획득중임</span>
    <span class="hljs-keyword">int</span> cnt;   <span class="hljs-comment">// 재귀 록 카운트</span>
};

<span class="hljs-comment">// 재귀 록 획득 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">reentlock_acquire</span><span class="hljs-params">(struct reent_lock *lock, <span class="hljs-keyword">int</span> id)</span> </span>{
    <span class="hljs-comment">// 록 획득 중이고 자신이 획득 중인지 판정 ❷</span>
    <span class="hljs-keyword">if</span> (lock-&gt;lock &amp;&amp; lock-&gt;id == id) {
        <span class="hljs-comment">// 자신이 획등 중이면 카운트를 증가</span>
        lock-&gt;cnt++;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// 어떤 스레드도 혹을 획득하지 않았거나,</span>
        <span class="hljs-comment">// 다른 스레드가 록 획득 중이면 록 획득</span>
        spinlock_acquire(&amp;lock-&gt;lock);
        <span class="hljs-comment">// 록윽 획득하면 자신의 스레드 ID를 설정하고</span>
        <span class="hljs-comment">// 마운트를 인크리먼트</span>
        lock-&gt;id = id;
        lock-&gt;cnt++;
    }
}

<span class="hljs-comment">// 재귀 록 해제 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">reentlock_release</span><span class="hljs-params">(struct reent_lock *lock)</span> </span>{
    <span class="hljs-comment">// 카운트를 디크리먼트하고,</span>
    <span class="hljs-comment">// 해당 카운트가 0이 되면 록 해제 ❸</span>
    lock-&gt;cnt--;
    <span class="hljs-keyword">if</span> (lock-&gt;cnt == <span class="hljs-number">0</span>) {
        lock-&gt;id = <span class="hljs-number">0</span>;
        spinlock_release(&amp;lock-&gt;lock);
    }
}

<span class="hljs-comment">// 활용 예시</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">reent_lock</span> <span class="hljs-title">lock_var</span>;</span> <span class="hljs-comment">// 록용 공유 변수</span>

<span class="hljs-comment">// n회 재귀적으로 호출해 록을 거는 테스트 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">reent_lock_test</span><span class="hljs-params">(<span class="hljs-keyword">int</span> id, <span class="hljs-keyword">int</span> n)</span> </span>{
    <span class="hljs-keyword">if</span> (n == <span class="hljs-number">0</span>)
        <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// 재귀 록</span>
    reentlock_acquire(&amp;lock_var, id);
    reent_lock_test(id, n - <span class="hljs-number">1</span>);
    reentlock_release(&amp;lock_var);
}

<span class="hljs-comment">// 스레드용 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">thread_func</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
    <span class="hljs-keyword">int</span> id = (<span class="hljs-keyword">int</span>)arg;
    assert(id != <span class="hljs-number">0</span>);
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10000</span>; i++) {
        reent_lock_test(id, <span class="hljs-number">10</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
    <span class="hljs-keyword">pthread_t</span> v[NUM_THREADS];
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; NUM_THREADS; i++) {
        pthread_create(&amp;v[i], <span class="hljs-literal">NULL</span>, thread_func, (<span class="hljs-keyword">void</span> *)(i + <span class="hljs-number">1</span>));
    }
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; NUM_THREADS; i++) {
        pthread_join(v[i], <span class="hljs-literal">NULL</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>C와는 달리 러스트에서는 재귀락을 수행하는 코드는 의도적으로 작성하지 않으면 거의 일어나지 않는 것으로 보인다. 락용 변수와 리소스가 강하게 결합되어 있기 때문이다.</p>
<p>아래와 같이 재귀락을 수행하는 코드를 짤 수 있기는 하나, 일반적이지 않다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Mutex};

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// 뮤텍스를 Arc로 작성하고 클론</span>
    <span class="hljs-keyword">let</span> lock0 = Arc::new(Mutex::new(<span class="hljs-number">0</span>)); <span class="hljs-comment">// ❶</span>
    <span class="hljs-comment">// Arc의 클론은 참조 카운터를 증가하기만 한다</span>
    <span class="hljs-keyword">let</span> lock1 = lock0.clone(); <span class="hljs-comment">// ❷</span>

    <span class="hljs-keyword">let</span> a = lock0.lock().unwrap();
    <span class="hljs-keyword">let</span> b = lock1.lock().unwrap(); <span class="hljs-comment">// 데드록 ❸</span>
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, a);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, b);
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[동기 처리 1]]></title><description><![CDATA[여기서는 동기 처리가 필요한 이유(레이스 컨디션)을 설명하고 뮤텍스에서 조건 변수까지를 설명한다.
레이스 컨디션(race condition)
레이스 컨디션은 한국어로는 경합 상태라고 불리며 여러 프로세스가 동시에 공유하는 자원에 접근함에 따라 일어나는 예상치 않은 이상이나 상태를 의미한다.
레이스 컨디션의 예로 아래 그림을 들 수 있다. 아래 그림은 공유 메모리상의 변수를 여러 프로세스가 증가시키는 상황을 나타낸다,

프로세스 B가 2를 쓰는 ...]]></description><link>https://maximizemaxwell.com/concurrentone</link><guid isPermaLink="true">https://maximizemaxwell.com/concurrentone</guid><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 01 Jun 2025 11:51:27 GMT</pubDate><content:encoded><![CDATA[<p>여기서는 동기 처리가 필요한 이유(레이스 컨디션)을 설명하고 뮤텍스에서 조건 변수까지를 설명한다.</p>
<h1 id="heading-race-condition">레이스 컨디션(race condition)</h1>
<p>레이스 컨디션은 한국어로는 경합 상태라고 불리며 여러 프로세스가 동시에 공유하는 자원에 접근함에 따라 일어나는 예상치 않은 이상이나 상태를 의미한다.</p>
<p>레이스 컨디션의 예로 아래 그림을 들 수 있다. 아래 그림은 공유 메모리상의 변수를 여러 프로세스가 증가시키는 상황을 나타낸다,</p>
<p><img src="https://i.imgur.com/bhPAqcp.png" alt /></p>
<p>프로세스 B가 2를 쓰는 시점까지는 문제가 없어 보이나, A 프로세스가 2를 읽어온 이후 A가 이를 다시 쓰기 전에 B가 공유 변수를 읽어와버린다. 결과적으로 B가 3이 아닌 2를 읽어오게 되고 기댓값인 4 대신 3을 내놓는다.</p>
<p>이처럼 동시성 프로그래밍에서 예기치 못한 결함에 빠지는 것을 <strong>레이스 컨디션</strong>이라고 한다. 그리고 레이스 컨디션을 일으키는 프로그램 코드 부분을 <strong>크리티컬 섹션</strong>이라고 하는데, 레이스 컨디션을 방지하기 위해서는 크리티컬 섹션을 보호하는 장치들이 필요할 것이다.</p>
<hr />
<h1 id="heading-7jwe7yag665ioyymoumra">아토믹 처리</h1>
<p><strong>아토믹 처리</strong>란 더 이상 나눌 수 없는 단일한 작업 단위를 의미한다. 즉, <strong>다른 쓰레드나 프로세스가 끼어들 수 없다는</strong> 것이다. 현대 컴퓨터상의 동기 처리 대부분은 이러한 아토믹 명령에 의존하고 있다.</p>
<h2 id="heading-compare-and-swap">Compare and Swap</h2>
<p><strong>Compare and Swap</strong>은 동기 처리 기능의 하나인 <strong>세마포어</strong>, <strong>락프리</strong>, <strong>웨이트프리</strong>한 데이터 구조를 구현하기 위해 이용하는 처리다. 줄여서 <strong>CAS</strong>라고 한다.</p>
<p>다음 코드를 통해 CAS의 의미를 파악해볼 수 있다.</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">compare_and_swap</span><span class="hljs-params">(<span class="hljs-keyword">uint64_t</span> *p, <span class="hljs-keyword">uint64_t</span> val, <span class="hljs-keyword">uint64_t</span> newval)</span>
</span>{
    <span class="hljs-keyword">if</span> (*p != val) {
    <span class="hljs-comment">// *p의 값이 val과 다르면 false를 반환</span>
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;    
    }
    <span class="hljs-comment">// *p의 값이 val과 같으면 *p에 newval을 대입하고 true를 반환</span>
    *p = newval;
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>어셈블리에서도 같은 작업이 가능한데 기본적으로는 레지스터의 값을 비교하고 비교 결과를 통해 특정 라벨로 점프하는 로직이다. C코드는 이러한 작업을 사람이 읽기 쉬운 변수들로 구현하였으나 레지스터 단위에서 이 작업들이 일어나는 것이 다를 뿐 근본적으로 큰 차이는 없다.</p>
<pre><code class="lang-plaintext">    cmpq %rsi, (%rdi)
    jne LBB0_1
    movq %rdx, (%rdi)
    movl $1, %eax
    retq
LBB0_1:
    xorl %eax, %eax
    retq
</code></pre>
<p>위에서 <code>rdi</code>, <code>rsi</code>, <code>rdx</code>가 함수의 첫번째, 두번째, 세번째 인수로 이용된다. 즉 각각이 <code>p</code>, <code>val</code>, <code>newval</code>에 해당한다.</p>
<p>그런데 위의 C함수는 <strong>아토믹하지 않다!</strong> 이유는 <code>*p != val</code>검사와 <code>*p = newval</code> 사이에 <strong>다른 쓰레드가 끼어들 수 있기 때문</strong>이다. 따라서 여전히 <strong>레이스 컨디션</strong>이 발생할 여지가 있다.</p>
<p>그래서 gcc와 같은 컴파일러에서 이와 같은 조작을 아토믹으로 처리하기 위해 내장 함수 <code>__sync_bool_compare_and_swap</code>을 제공한다.</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">compare_and_swap</span><span class="hljs-params">(<span class="hljs-keyword">uint64_t</span> *p, <span class="hljs-keyword">uint64_t</span> val, <span class="hljs-keyword">uint64_t</span> newval)</span> 
</span>{
    <span class="hljs-keyword">return</span> __sync_bool_compare_and_swap(p, val, newval);
}
</code></pre>
<p><code>__sync_bool_compare_and_swap</code>함수의 의미와 인수는 <code>compare_and_swap</code>함수와 완전히 동일하다. 이 함수는 다음 어셈블리 코드로 변환되는데</p>
<pre><code class="lang-plaintext">movq %rsi, %rax
xorl %ecx, %ecx
lock cmpxchgq %rdx, (%rdi)
sete %cl
movl %ecx, %eax
retq
</code></pre>
<p>두 번째 인수를 의미하는 <code>rsi</code> 레지스터의 값을 <code>rax</code>로 복사하고 <code>ecx</code> 레지스터 값을 0으로 초기화한다. 여기서 이제 차이가 발생하는데 <code>cmpxchgq</code>명령을 통해 <strong>아토믹하게 비교 및 교환</strong>된다.</p>
<p><code>cmpxchgq</code> 명령은 다음 코드로 의미를 표현할 수 있는데</p>
<pre><code class="lang-c"><span class="hljs-keyword">if</span> (%rax == (%rdi)) {
    (%rdi) = %rdx
    ZF = <span class="hljs-number">1</span>
} <span class="hljs-keyword">else</span> {
    %rax = (%rdi)
    ZF = <span class="hljs-number">0</span>
}
</code></pre>
<p>즉 <code>rax</code> 레지스터의 값과 <code>rdi</code>레지스터가 가리키는 메모리상의 값을 비교하고 같으면 <code>rdi</code>에 <code>rdx</code>의 값을 대입하고 ZF플래그를 1로 만든다. 만약 값이 다르면 <code>rax</code>에 <code>rdi</code>의 값을 대입하고 ZF플래그를 0으로 설정한다. 그러니까 의미 자체는 <code>compare_and_swap</code>함수와 같다는 건데 이것이 아토믹하게 일어날 뿐이다.</p>
<p>위의 로직이 하나의 시퀀스로 묶여 실행되고 중단되지 않는다. 그런데 멀티코어 환경에서 <code>cmpxchgq</code>만 쓰면 <strong>코어 간 레이스 컨디션</strong>은 보장되지 않을 수 있는데 <code>lock</code> 프리픽스를 붙여서 아토믹을 보장할 수 있다.</p>
<p>참고로 <code>cmpxchgq</code>에서 마지막 <code>q</code>의 의미는 <strong>명령어의 오퍼랜드 크기</strong>, 그러니까 <strong>64바이트</strong>를 의미하는 것이므로 위의 로직은 엄밀히는 <code>cmpxchg</code>를 설명한다고 보는 게 더 적절하겠다.</p>
<h2 id="heading-test-and-set">Test and Set</h2>
<p><strong>Test and Set</strong>은 TAS로 줄일 수 있다. 이는 코드로</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">test_and_set</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> *p)</span> </span>{
    <span class="hljs-keyword">if</span> (*p) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    } <span class="hljs-keyword">else</span> {
        *p = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<p>즉 포인터 p가 가리키는 값이 <code>true</code>면 그대로 <code>true</code>를 반환하고 <code>false</code>를 가르키면 p가 가리키는 메모리의 값을 true로 설정하고 false를 반환한다.</p>
<p>아까 CAS처럼 이 자체로는 아토믹하게 실행되지 않는다. 또 gcc같은 C컴파일러에서는 내장 함수 <code>__sync_lock_test_and_set</code>을 제공해서 아토믹하게 작동할 수 있게 하는데, 이 함수는 아까와는 다르게 위의 <code>test_and_set</code>함수와 작동 원리가 다르다.</p>
<pre><code class="lang-C">type __sync_lock_test_and_set(type *p, type val) {
    type tmp = *p;
    *p = val;
    <span class="hljs-keyword">return</span> tmp;
}
</code></pre>
<p>아까와는 다르게 포인터 p에 더해 val이 두 번째 인자로 들어오고, tmp에 *p를 저장, *p에는 두 번째 인자인 val의 값을 넣어주고, 최종적으로 초기 *p의 값이 저장되었던 tmp가 반환된다.</p>
<p>이때 이 함수는 두번째 인수 <code>val</code>에 1(true)를 지정함으로써 TAS함수처럼 작동하게 된다.</p>
<p>따라서 <code>__sync_lock_test_and_set</code>을 이용해 아토믹으로 구현이 가능하다.</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">test_and_set</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">bool</span> *p)</span> </span>{
    <span class="hljs-keyword">return</span> __sync_lock_test_and_set(p, <span class="hljs-number">1</span>);
}
</code></pre>
<h2 id="heading-load-linkstore-conditional">Load-Link/Store-Conditional</h2>
<p>x86-64와 x86에서는 lock명령 접두사를 사용해 메모리에 읽고 쓰기를 배타적으로 수행하도록 했다. 그 외 ARM, RISC-V 등에서는 Load-Link/Store-Conditional 명령을 이용해 아토믹 처리를 구현했다. 둘은 줄여서 LL과 SC라고 한다.</p>
<p>자세히 알아보자면, <code>LL</code>은 메모리를 <strong>배타적으로</strong> 읽도록 지정하는 명령어이고, <code>SC</code>명령어는 <strong>메모리 쓰기</strong>를 수행하는 명령어이다. <code>LL</code>명령어로 지정한 메모리로의 쓰기는 다른 CPU가 수행하지 않는 경우에만 쓰기가 성공한다.</p>
<hr />
<h1 id="heading-666k7ywn7iqk">뮤텍스</h1>
<p><strong>뮤텍스(Mutex)</strong> 는 MUTual EXecution의 약어로 배타 실행(Exclusive Execution)이라고도 하는 동기 처리 방법이다. 위에서 <strong>크리티컬 섹션</strong>은 레이스 컨디션이 발생할 수 있는 코드 영역이라고 했다. 뮤텍스는 이러한 크리티컬 섹션을 실행할 수 있는 프로세스 수를 <strong>최대 1개</strong>로 제한한다.</p>
<p>뮤텍스의 원리는 플래그를 이용해 크리티컬 섹션에서 실행될 프로세스를 제한하는 방식인데, 아래 코드와 같이 동작한다.</p>
<pre><code class="lang-C"><span class="hljs-keyword">bool</span> lock = <span class="hljs-literal">false</span>; <span class="hljs-comment">// 공유 변수 정의</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">some_func</span><span class="hljs-params">()</span> </span>{
retry: 
    <span class="hljs-keyword">if</span> (!lock) { <span class="hljs-comment">// 이미 다른 프로세스가 크리티컬 섹션 실행중인지?</span>
        lock = <span class="hljs-literal">true</span>; <span class="hljs-comment">// 사용중이다</span>
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">goto</span> retry;
    }
    lock = <span class="hljs-literal">false</span>; <span class="hljs-comment">// 이제 사용중이 아님. 종료</span>
}
</code></pre>
<p>언뜻 보기에 이 함수는 배타적 실행을 성공시킬 수 있을 것 같지만 잘 동작하지 않는 경우가 발생한다.</p>
<p><img src="https://i.imgur.com/a69xgXR.png" alt /></p>
<p>이러한 상황이다.</p>
<p>lock을 true로 바꾸기 전에 프로세스가 들어와 lock을 false로 인식하여 레이스 컨디션이 발생하게 되는 상황이다.</p>
<p>따라서 이를 방지하려면</p>
<pre><code class="lang-C"><span class="hljs-keyword">bool</span> lock = <span class="hljs-literal">false</span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">some_func</span><span class="hljs-params">()</span> </span>{
retry: 
    <span class="hljs-keyword">if</span> (!test_and_set(&amp;lock)) { <span class="hljs-comment">// 락 획득</span>
        <span class="hljs-comment">// 크리티컬 섹션</span>
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">goto</span> retry;
    }
    tas_release(&amp;lock); <span class="hljs-comment">// 락 해제</span>
}
</code></pre>
<p>그것을 위해 TAS를 이용해 <strong>아토믹하게</strong> 검사와 값 변경을 수행하는 것이다. 그렇다면 중간에 다른 프로세스가 끼어드는 것을 방지할 수 있다.</p>
<h2 id="heading-7iqk7zwa6529">스핀락</h2>
<p>위에서는 락을 얻을 때까지 루프를 반복했다. 이렇게 리소스가 비는 것을 기다리며 확인하는 락 획득 방법을 <strong>스핀락</strong>이라고 한다.</p>
<p>전형적으로 스핀락용 API는 락 획득용과 락 해제용 함수 두 가지가 제공되고</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">void</span> spinlock <span class="hljs-title">acquire</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> *lock)</span> </span>{
    <span class="hljs-keyword">while</span> (test_and_set(lock));
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">spinlock_release</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> *lock)</span> </span>{
    tas_release(lock);
}
</code></pre>
<p>일반적으로 아토믹 명령은 실행 속도상의 패널티가 있어 호출 전에 검사를 하고 나는 방식을 추가하면 조금 더 효율적이다.</p>
<pre><code class="lang-C"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">spinlock_acquire</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">bool</span> *lock)</span> </span>{
    <span class="hljs-keyword">for</span> (;;) {
        <span class="hljs-keyword">while</span> (*lock);
        <span class="hljs-keyword">if</span> (!test_and_set(lock))
            <span class="hljs-keyword">break</span>;
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">spinlock_release</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> *lock)</span> </span>{
    tas_release(lock);
}
</code></pre>
<h2 id="heading-pthreads">Pthreads의 뮤텍스</h2>
<p>C의 Pthreads에서도 뮤텍스를 이용할 수 있다. 일반적으로는 이렇게 직접 구현하는 것보다 라이브러리에서 제공하는 뮤텍스를 이용하는 것이 바람직하다.</p>
<pre><code class="lang-C"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>

<span class="hljs-keyword">pthread_mutex_t</span> mut = PTHREAD_MUTEX_INITIALIZER;

<span class="hljs-function"><span class="hljs-keyword">void</span>* <span class="hljs-title">some_func</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
    <span class="hljs-keyword">if</span> (pthread_mutex_lock(&amp;mut) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_mutex_lock"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
    }
    <span class="hljs-comment">// 크리티컬 섹션</span>

    <span class="hljs-keyword">if</span> (pthread_mutex_unlock(&amp;mut) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_mutex_unlock"</span>); <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
    <span class="hljs-comment">//스레드 생성</span>
    <span class="hljs-keyword">pthread_t</span> th1, th2;
    <span class="hljs-keyword">if</span> (pthread_create(&amp;th1, <span class="hljs-literal">NULL</span>, some_func, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_create"</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-comment">// 스레드 종료 대기</span>
    <span class="hljs-keyword">if</span> (pthread_join(th1, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_join"</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-keyword">if</span> (pthread_join(th2, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_join"</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-comment">// 뮤텍스 객체 릴리즈</span>
    <span class="hljs-keyword">if</span> (pthread_mutex_destroy(&amp;mut) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_mutex_destroy"</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h1 id="heading-7is466ei7ys7ja0">세마포어</h1>
<p>뮤텍스에서는 락을 최대 1개 프로세스까지 획득할 수 있었다. 그렇다면 1개보다 많은 프로세스가 동시에 락을 획득하려면 어떻게 해야하는가? 세마포어를 쓰면 된다.</p>
<p><strong>세마포어</strong>에서는 최대 N개 프로세스까지 동시에 락을 획득할 수 있는데, 여기서 N은 프로그램 실행 전에 임의로 결정할 수 있는 값이다.</p>
<p>그런고로 뮤텍스는 N이 1인 세마포어인 것으로, 세마포어는 일반화한 뮤텍스로도 볼 수 있겠다.</p>
<p>코드로 구현하면 아래와 같다.</p>
<pre><code class="lang-C"><span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> NUM 4 <span class="hljs-comment">// N</span></span>

<span class="hljs-comment">// 다수의 프로세스가 락을 획득했는지 알아야 하므로 int타입</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">semaphore_acquire</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *cnt)</span> </span>{
    <span class="hljs-keyword">for</span> (;;) {
    <span class="hljs-comment">// 공유 변숫값이 최댓값 NUM 이상이면 스핀하며 대기</span>
        <span class="hljs-keyword">while</span> (*cnt &gt;= NUM);
        __sync_fetch_and_add(cnt, <span class="hljs-number">1</span>);
        <span class="hljs-comment">// NULL 이하인지 검사하여 그렇다면 루프 벗어나기</span>
        <span class="hljs-keyword">if</span> (*cnt &lt;= NUM) 
            <span class="hljs-keyword">break</span>;
            __sync_fetch_and_sub(cnt, <span class="hljs-number">1</span>);
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">semapthore_release</span><span class="hljs-params">(<span class="hljs-keyword">int</span> *cnt)</span> </span>{
    __sync_fetch_and_sub(cnt, <span class="hljs-number">1</span>);
}
</code></pre>
<p>세마포어는 물리적인 계산 리소스 이용에 제한을 적용하고 싶은 경우 등에 이용할 수 있다.</p>
<h2 id="heading-posix">POSIX 세마포어</h2>
<p>POSIX 규격을 따르는 세마포어 인터페이스이다. 표준적인 구현으로 볼 수 있다. POSIX 세마포어에서는 이름 있는 세마포어와 이름 없는 세마포어가 있는데, 이름 있는 세마포어는 슬래시로 시작해 널 문자열로 끝나는 문자열로 식별된다. 또한 이름 있는 세마포어는 파일로 공유 리소스를 지정할 수 있고, sem_open으로 생성과 열기, sem_close와 sem_unlink로 닫기와 파기를 수행한다.</p>
<hr />
<h1 id="heading-7kgw6rg0iouzgoyima">조건 변수</h1>
<p><strong>조건 변수</strong>는 스레드 간의 동기화를 위한 도구로, <strong>특정 조건이 만족될 때까지 스레드를 잠들게 했다가, 조건이 충족되면 스레드를 깨워 다시 실행하게 만드는</strong> 구조이다. 교차로에서 신호등같은 역할을 하는 것이다.</p>
<p>뮤텍스만 사용하게 되면 플래그가 true가 되었는지 반복해서 검사해야하기 때문에 <strong>Busy-Waiting</strong> 상태가 되는데, CPU 사용 면에서 비효율적이라고 할 수 있다. 확인하기 위해 CPU를 계속해서 깨워야하기 때문이다.</p>
<p>이를 위해 조건변수를 사용하게 된다.</p>
<hr />
<h1 id="heading-rust">Rust에서의 구현</h1>
<p>러스트에서는 기본적인 동기 처리 라이브러리를 표준 라이브러리로 제공하는데, 아래와 같이 구현된다고 할 수 있다.</p>
<h2 id="heading-666k7ywn7iqk-1">뮤텍스</h2>
<p>아래 코드는 러스트에서 뮤텍스의 활용 예제를 나타낸다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Mutex}; <span class="hljs-comment">//여기서 Arc는 참조 카운트 기반 스마트 포인터.</span>
<span class="hljs-keyword">use</span> std::thread; <span class="hljs-comment">// 표준 스레드 생성 기능</span>

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">some_func</span></span>(lock: Arc&lt;Mutex&lt;<span class="hljs-built_in">u64</span>&gt;&gt;) {
    <span class="hljs-keyword">loop</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> val = lock.lock().unwrap(); <span class="hljs-comment">// 락 획득</span>
        *val += <span class="hljs-number">1</span>; <span class="hljs-comment">// 값 증가</span>
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, *val); <span class="hljs-comment">// 출력</span>
    }
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> lock0 = Arc::new(Mutex::new(<span class="hljs-number">0</span>)); 
    <span class="hljs-keyword">let</span> lock1 = lock0.clone(); <span class="hljs-comment">// Arc 복제(참조 카운트 증가)</span>
    <span class="hljs-keyword">let</span> th0 = thread::spawn(<span class="hljs-keyword">move</span> || {
        some_func(lock0); <span class="hljs-comment">// 첫 스레드 실행</span>
    });
    <span class="hljs-keyword">let</span> th1 = thread::spawn(<span class="hljs-keyword">move</span> || {
        some_func(lock1); <span class="hljs-comment">// 두 번째 스레드 실행</span>
    });
    th0.join().unwrap();
    th1.join().unwrap();
}
</code></pre>
<p>러스트에서는 뮤텍스용 변수는 보호 대상 데이터를 보존하도록 되어 있어 접근하려면 반드시 <code>lock</code>을 해야한다.</p>
<p>C언어에서 보호 대상 데이터는 락을 하지 않아도 접근할 수 있는데, 그런 코드는 레이스 컨디션이 될 가능성이 있다.</p>
<p>자세히 설명하자면 러스트에서는</p>
<ul>
<li><p><code>Mutex&lt;T&gt;</code> 자페가 <code>T</code>에 대한 소유권을 가지고 있다.</p>
</li>
<li><p>따라서 <code>T</code>에 직접 접근하려면 <code>lock()</code>을 통해 <code>MutexGuard&lt;T&gt;</code>를 획득해야 한다.</p>
</li>
<li><p>결론적으로 락 없이는 접근 불가가 된다.</p>
</li>
</ul>
<p>그러나 C에서는 뮤텍스와 보호 대상 데이터를 따로 관리하므로 락 없이 접근하는 케이스가 생길 수 있고, 이 경우 레이스 컨디션이 된다.</p>
<p>한마디로 러스트에서는 락과 데이터를 결합해서 <strong>락을 통한 접근만</strong> 가능하고 타입에도 이를 강제하고 있으므로 <strong>safe</strong>하다.</p>
<h2 id="heading-7kgw6rg0iouzgoyima-1">조건 변수</h2>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> std::sync::{Arc, Mutex, Condvar}; <span class="hljs-comment">//조건변수</span>
<span class="hljs-keyword">use</span> std::thread;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">child</span></span>(id: <span class="hljs-built_in">u64</span>, p: Arc&lt;(Mutex&lt;<span class="hljs-built_in">bool</span>&gt;, Condvar)&gt;) {
    <span class="hljs-comment">// 대기 스레드용 함수 정의</span>
    <span class="hljs-keyword">let</span> &amp;(<span class="hljs-keyword">ref</span> lock, <span class="hljs-keyword">ref</span> cvar) = &amp;*p;
    <span class="hljs-comment">// Arc 타입 내부에 포함된 뮤텍스와 조건변수를 꺼내기</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> started = lock.lock().unwrap();
    <span class="hljs-keyword">while</span> !*started {
        <span class="hljs-comment">// 시작 신호가 오기 전까지 대기</span>
        started = cvar.wait(started).unwrap();
    }
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Child thread {} started"</span>, id);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">parent</span></span>(p: Arc&lt;(Mutex&lt;<span class="hljs-built_in">bool</span>&gt;, Condvar)&gt;) {
    <span class="hljs-comment">// 시작 신호를 보내는 함수 정의</span>
    <span class="hljs-keyword">let</span> &amp;(<span class="hljs-keyword">ref</span> lock, <span class="hljs-keyword">ref</span> cvar) = &amp;*p;
    <span class="hljs-comment">// 락을 한 뒤 공유 변숫값을 true로 설정하고 알림</span>
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> started = lock.lock().unwrap();
    *started = <span class="hljs-literal">true</span>;
    cvar.notify_all();
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Parent thread notified all children"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> pair = Arc::new((Mutex::new(<span class="hljs-literal">false</span>), Condvar::new()));
    <span class="hljs-keyword">let</span> pair0 = Arc::clone(&amp;pair);
    <span class="hljs-keyword">let</span> pair1 = Arc::clone(&amp;pair);
    <span class="hljs-keyword">let</span> pair2 = Arc::clone(&amp;pair);

    <span class="hljs-keyword">let</span> c0 = thread::spawn(<span class="hljs-keyword">move</span> || {
        child(<span class="hljs-number">0</span>, pair0);
    });
    <span class="hljs-keyword">let</span> c1 = thread::spawn(<span class="hljs-keyword">move</span> || {
        child(<span class="hljs-number">1</span>, pair1);
    });
    <span class="hljs-keyword">let</span> p = thread::spawn(<span class="hljs-keyword">move</span> || {
        parent(pair2);
    });

    c0.join().unwrap();
    c1.join().unwrap();
    p.join().unwrap();
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[아주 간단한 깃헙 액션]]></title><description><![CDATA[자동 배포하기
깃허브 액션 워크플로우를 통해 자동으로 도커 이미지를 빌드하고 컨테이너를 실행하는 형태의 자동 배포도 가능하다.
name: Deploy to Remote Server

on:
  push:
    branches:
      - main

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Setup SS...]]></description><link>https://maximizemaxwell.com/simple-github-action</link><guid isPermaLink="true">https://maximizemaxwell.com/simple-github-action</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Thu, 29 May 2025 14:33:56 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-7j6q64ziouwso2pro2vmoq4sa">자동 배포하기</h1>
<p>깃허브 액션 워크플로우를 통해 자동으로 도커 이미지를 빌드하고 컨테이너를 실행하는 형태의 자동 배포도 가능하다.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Remote</span> <span class="hljs-string">Server</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">SSH</span> <span class="hljs-string">Key</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "${{ secrets.SSH_KEY }}" | base64 -d &gt; key.pem
          chmod 600 key.pem
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Pull</span> <span class="hljs-string">and</span> <span class="hljs-string">Restart</span> <span class="hljs-string">on</span> <span class="hljs-string">Server</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          ssh -i key.pem -p ${{ secrets.SSH_PORT }} -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} &lt;&lt; 'EOF'
            cd ${{ secrets.REMOTE_DIR }}
            echo "[INFO] Pulling latest changes..."
            git pull origin main
            echo "[INFO] Stopping old container..."
            docker stop discord-bot-toko || true
            docker rm discord-bot-toko || true
            echo "[INFO] Rebuilding image..."
            docker build -t discord-bot-toko .
            echo "[INFO] Starting container..."
            docker run -d --name discord-bot-toko --env-file .env --rm discord-bot-toko
          EOF</span>
</code></pre>
<p>위 코드의 경우 실제로는 배포상 문제가 있었으나, 위와 같은 식으로 push가 있는 경우 도커 이미지를 빌드하고 도커 컨테이너를 재실행하는 디자인으로 작성되었다.</p>
<h1 id="heading-7ywm7iqk7yq4iounjoutpoq4sa">테스트 만들기</h1>
<p>러스트에서도 테스트들을 만들 수 있다.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Audit</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">schedule:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">cron:</span> <span class="hljs-string">"0 0 * * 0"</span> <span class="hljs-comment"># Every Sunday at midnight</span>
  <span class="hljs-attr">workflow_dispatch:</span> <span class="hljs-comment"># Manual trigger</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">audit:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-comment"># Toolchain installation</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Rust</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions-rs/toolchain@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">toolchain:</span> <span class="hljs-string">stable</span>

      <span class="hljs-comment"># cargo-audit</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">cargo-audit</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">cargo</span> <span class="hljs-string">install</span> <span class="hljs-string">--locked</span> <span class="hljs-string">cargo-audit</span>

      <span class="hljs-comment"># check known vulnerabilities</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">cargo-audit</span>
        <span class="hljs-attr">working-directory:</span> <span class="hljs-string">vulncat</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">cargo</span> <span class="hljs-string">audit</span>
</code></pre>
<p>위와 같은 식으로 알려진 취약점에 대한 테스트도 작성할 수 있다. 위의 경우 일요일 자정 또는 수동 트리거에 의해 작동시킬 수 있다.</p>
<h1 id="heading-67cw7ys">배포</h1>
<p><code>release</code>를 이용해 자동으로 태그를 달고 배포하는 용도로도 사용 가능하다.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Release</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">tags:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"v*.*.*"</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">release:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">defaults:</span>
      <span class="hljs-attr">run:</span>
        <span class="hljs-attr">working-directory:</span> <span class="hljs-string">vulncat/model</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Rust</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions-rs/toolchain@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">toolchain:</span> <span class="hljs-string">stable</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">full</span> <span class="hljs-string">build</span> <span class="hljs-string">&amp;</span> <span class="hljs-string">tests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          cargo build --release
          cargo test
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Release</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/create-release@v1</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">create</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">tag_name:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.ref_name</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">release_name:</span> <span class="hljs-string">Release</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.ref_name</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">GITHUB_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">trained</span> <span class="hljs-string">model</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-release-asset@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">upload_url:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.create.outputs.upload_url</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">asset_path:</span> <span class="hljs-string">models/autoencoder.bin</span>
          <span class="hljs-attr">asset_name:</span> <span class="hljs-string">autoencoder.bin</span>
          <span class="hljs-attr">asset_content_type:</span> <span class="hljs-string">application/octet-stream</span>
</code></pre>
<h1 id="heading-66aw7yswioyggeyaqq">린터 적용</h1>
<p>파이썬 프로젝트의 경우 github action 웹에서 간단하게 <code>pylint</code>를 적용할 수 있다. 다만 기본 설정을 그대로 적용할 경우 파이린트 점수가 10점 이상이여야 테스트 성공이 나오나, 만약 머지를 막아놓은 경우 파이린트 10점이 꽤 강한 제한으로 느껴질 수 있다.</p>
<p>이러한 경우에</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Pylint</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">python-version:</span> [<span class="hljs-string">"3.13"</span>]
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v3</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.python-version</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        python -m pip install --upgrade pip
        pip install pylint
</span>    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Analysing</span> <span class="hljs-string">the</span> <span class="hljs-string">code</span> <span class="hljs-string">with</span> <span class="hljs-string">pylint</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
        <span class="hljs-string">pylint</span> <span class="hljs-string">$(git</span> <span class="hljs-string">ls-files</span> <span class="hljs-string">'*.py'</span><span class="hljs-string">)</span> <span class="hljs-string">--fail-under=7.5</span>
</code></pre>
<p>아래 이렇게 점수를 설정해줘서 테스트의 강도를 조절할 수 있는데</p>
<p><code>pylint $(git ls-files '*.py') --fail-under=7.5</code>인 경우 7.5 이상에서 테스트가 통과하게 된다.</p>
]]></content:encoded></item><item><title><![CDATA[Rust Basic[변수~라이프타임 입문하기]]]></title><description><![CDATA[일반 프로그래밍
변수
러스트에서 변수는 기본적으로 불변이다. 따라서 let x = 5;와 같이 선언하면 이는 기본적으로 불변이다. 따라서 이 값을 바꾸고자 한다면 컴파일이 불변성 에러를 뱉을 것이다.
따라서 변수를 가변으로 만들고자 한다면 let mut x = 5;처럼 반드시 mut 키워드를 붙여야 한다.
상수
사실 불변 변수라면 그냥 상수와 구분되지 않아도 되는 것이 아닌가 싶지만 상수는 let 대신 const 키워드로 선언하고 mut와 함께...]]></description><link>https://maximizemaxwell.com/rust-basic</link><guid isPermaLink="true">https://maximizemaxwell.com/rust-basic</guid><category><![CDATA[Rust]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 25 May 2025 10:20:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748174235121/77acc54d-c837-48b2-872b-6ef87f805ac8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-7j2867cyio2uhouhnoq3uouemouwjq">일반 프로그래밍</h1>
<h2 id="heading-67oa7iiy">변수</h2>
<p>러스트에서 변수는 기본적으로 불변이다. 따라서 <code>let x = 5;</code>와 같이 선언하면 이는 기본적으로 불변이다. 따라서 이 값을 바꾸고자 한다면 컴파일이 불변성 에러를 뱉을 것이다.</p>
<p>따라서 변수를 가변으로 만들고자 한다면 <code>let mut x = 5;</code>처럼 반드시 <code>mut</code> 키워드를 붙여야 한다.</p>
<h2 id="heading-7iob7iiy">상수</h2>
<p>사실 불변 변수라면 그냥 상수와 구분되지 않아도 되는 것이 아닌가 싶지만 상수는 <code>let</code> 대신 <code>const</code> 키워드로 선언하고 <code>mut</code>와 함께 사용할 수 없다.</p>
<p>그리고 기본적으로 불변 변수는 런타임에 바인딩되지만 상수는 컴파일 타임에 인라인된다. 불변 변수와 상수는 비슷한 것이라기보단 변수에 가변 옵션을 꺼놓은 것과 비슷한다. 유효한 범위도 불변 변수와 상수는 다르다.</p>
<p>그리고 변수는 타입 생략이 가능하지만 상수는 언제나 선언해주어야 한다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">const</span> THREE_HOURS_IN_SECONDS: <span class="hljs-built_in">u32</span> = <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">3</span>
</code></pre>
<p>+) <code>섀도잉</code> 러스트에서 같은 이름으로 변수를 다시 선언할 수 있으나 이건 <code>mut</code> 키워드로 가변 변수의 값을 바꿔주는 것과 다르다.</p>
<p>같은 이름으로 선언하는 것은 변수를 다시 만드는 것이고 값을 바꿔주는 것과는 다르다.</p>
<p>따라서 기본적으로 타입 간 변경이 불가능한 가변 변수에서</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> spaces = <span class="hljs-string">" "</span>;
spaces = spaces.len();
</code></pre>
<p>로 서로 다른 타입으로 값을 바꾸고자 하면 컴파일 에러가 일어나지만</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> spaces = <span class="hljs-string">" "</span>;
<span class="hljs-keyword">let</span> spaces = spaces.len();
</code></pre>
<p>는 에러를 일으키지 않는다는 것이다.</p>
<h2 id="heading-7zwo7iiy">함수</h2>
<p>러스트에서 함수는 <code>fn</code> 키워드로 생성하고 <code>-&gt;</code> 뒤에 반환값의 타입을 선언한다. 그리고 세미콜론 없이 값을 적어줘서 반환값을 지정할 수 있는데</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">five</span></span>() -&gt; <span class="hljs-built_in">i32</span> {
    <span class="hljs-number">5</span>
}
</code></pre>
<p>이러한 식이다. 반환값의 타입이 일치하지 않으면 이 또한 컴파일 에러를 일으킨다.</p>
<h2 id="heading-7kgw6rg066y4">조건문</h2>
<p>딱히 특별할 건 없고</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> number = <span class="hljs-number">6</span>;

    <span class="hljs-keyword">if</span> number % <span class="hljs-number">4</span> == <span class="hljs-number">0</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"number is divisible by 4"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> number % <span class="hljs-number">3</span> == <span class="hljs-number">0</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"number is divisible by 3"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> number % <span class="hljs-number">2</span> == <span class="hljs-number">0</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"number is divisible by 2"</span>);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"number is not divisible by 4, 3, or 2"</span>);
    }
}
</code></pre>
<p>이런 식으로 쓰면 되겠다.</p>
<h2 id="heading-67cy67o166y4">반복문</h2>
<p><code>loop</code> 키워드는 코드를 명시적으로 그만두라고 하기 전까지 반복문이 계속 돌아간다. 이걸 어디 쓰냐 하면 어떤 스레드가 실행 완료되었는지 검사하는 데에 쓸 수 있다. 대체로 실패할지도 모르는 연산을 재시도하는 경우로 보면 되겠다. <code>break</code> 이후에 값을 반환할 수도 있는데 그것은</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> counter = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">let</span> result = <span class="hljs-keyword">loop</span> {
        counter += <span class="hljs-number">1</span>;

        <span class="hljs-keyword">if</span> counter == <span class="hljs-number">10</span> {
            <span class="hljs-keyword">break</span> counter * <span class="hljs-number">2</span>;
        }
    };

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The result is {result}"</span>);
}
</code></pre>
<p>이런 식으로 써주면 되겠다.</p>
<p>그리고 라벨을 지정해서 특정한 루프에 적용되도록 할 수 있는데</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> count = <span class="hljs-number">0</span>;
    <span class="hljs-symbol">'counting_up</span>: <span class="hljs-keyword">loop</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"count = {count}"</span>);
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> remaining = <span class="hljs-number">10</span>;

        <span class="hljs-keyword">loop</span> {
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"remaining = {remaining}"</span>);
            <span class="hljs-keyword">if</span> remaining == <span class="hljs-number">9</span> {
                <span class="hljs-keyword">break</span>;
            }
            <span class="hljs-keyword">if</span> count == <span class="hljs-number">2</span> {
                <span class="hljs-keyword">break</span> <span class="hljs-symbol">'counting_up</span>;
            }
            remaining -= <span class="hljs-number">1</span>;
        }

        count += <span class="hljs-number">1</span>;
    }
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"End count = {count}"</span>);
}
</code></pre>
<p>이런 식으로.</p>
<p><code>while</code>이나 <code>for</code> 문도 다른 프로그래밍 언어랑 크게 다를 것은 없다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> a = [<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>, <span class="hljs-number">40</span>, <span class="hljs-number">50</span>];
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> index = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">while</span> index &lt; <span class="hljs-number">5</span> {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"the value is: {}"</span>, a[index]);

        index += <span class="hljs-number">1</span>;
    }
}
</code></pre>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> a = [<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>, <span class="hljs-number">40</span>, <span class="hljs-number">50</span>];

    <span class="hljs-keyword">for</span> element <span class="hljs-keyword">in</span> a {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"the value is: {element}"</span>);
    }
}
</code></pre>
<p>이런 식으로도 쓸 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">for</span> number <span class="hljs-keyword">in</span> (<span class="hljs-number">1</span>..<span class="hljs-number">4</span>).rev() {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{number}!"</span>);
    }
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"LIFTOFF!!!"</span>);
}
</code></pre>
<hr />
<h1 id="heading-amp">구조체 &amp; 열거형</h1>
<h2 id="heading-6rws7kgw7lk0">구조체</h2>
<h3 id="heading-6rws7kgw7lk0ioygleydmo2vmoq4sa">구조체 정의하기</h3>
<p>러스트에서 구조체도 C에서와 크게 다르지 않다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">User</span></span> {
    active: <span class="hljs-built_in">bool</span>,
    username: <span class="hljs-built_in">String</span>,
    email: <span class="hljs-built_in">String</span>,
    sign_in_count: <span class="hljs-built_in">u64</span>,
}
</code></pre>
<p>이렇게 써주면 되겠다.</p>
<p>사용도</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> user1 = User {
        active: <span class="hljs-literal">true</span>,
        username: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"username123"</span>),
        email: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"email@email.com"</span>),
        sign_int_count: <span class="hljs-number">1</span>,
    };
}
</code></pre>
<p>이런 식으로 써주면 되겠다.</p>
<p>똑같이 <code>mut</code> 키워드로 가변성을 지정해줄 수 있는데</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> user1 = User {
        active: <span class="hljs-literal">true</span>,
        username: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"username"</span>);
        email: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"email.com"</span>);
        sign_int_count: <span class="hljs-number">1</span>,
    };
    user1.email = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"other.com"</span>);
}
</code></pre>
<p>가변성은 인스턴스 전체가 지니게 되어서 모든 필드가 가변적이게 된다. 특정 필드만 가변 나머지는 불변 이렇게 만드는 것은 불가능하다.</p>
<p>다음 코드를 보자.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">build_user</span></span>(email: <span class="hljs-built_in">String</span>, username: <span class="hljs-built_in">String</span>) -&gt; User {
    User {
        active: <span class="hljs-literal">true</span>,
        username: username,
        email: email,
        sign_in_count: <span class="hljs-number">1</span>,
    }
}
</code></pre>
<p>여기서 변수명과 필드명이 같아서 <code>username: username</code>과 같이 써주었는데 이걸 생략할 수 있다. 즉</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">build_user</span></span>(email: <span class="hljs-built_in">String</span>, username: <span class="hljs-built_in">String</span>) -&gt; User {
    User {
        active: <span class="hljs-literal">true</span>,
        username,
        email,
        sign_in_count: <span class="hljs-number">1</span>,
    }
}
</code></pre>
<p>이런 식으로 쓸 수 있다.</p>
<h3 id="heading-6rws7kgw7lk0ioyxheunsoydto2kucdrrljrspu">구조체 업데이트 문법</h3>
<p>또 기존 인스턴스를 이용해 새 인스턴스를 만들 때 축약된 구조체 업데이트 문법을 사용할 수 있는데</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// --생략--</span>

    <span class="hljs-keyword">let</span> user2 = User {
        active: user1.active,
        username: user1.username,
        email: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"another@example.com"</span>),
        sign_in_count: user1.sign_in_count,
    };
}
</code></pre>
<p>가 있다고 할 때 이것을</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-comment">// --생략--</span>

    <span class="hljs-keyword">let</span> user2 = User {
        email: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"anotheremail@example.com"</span>),
        ..user1
    };
}
</code></pre>
<p>이런 식으로 쓸 수 있다.</p>
<h3 id="heading-7yqc7zsmioq1royhsoyyta">튜플 구조체</h3>
<p>러스트는 튜플같은 구조체도 지원하는데 이를 <code>튜플 구조체</code>라고 부른다. 튜플 구조체는 이름은 있지만 필드가 없고, 튜플처럼 인덱스로 접근하는 구조체이다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Color</span></span>(<span class="hljs-built_in">i32</span>, <span class="hljs-built_in">i32</span>, <span class="hljs-built_in">i32</span>);
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Point</span></span>(<span class="hljs-built_in">i32</span>, <span class="hljs-built_in">i32</span>, <span class="hljs-built_in">i32</span>);

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> black = Color(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">let</span> origin = Point(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Black: ({}, {}, {})"</span>, black.<span class="hljs-number">0</span>, black.<span class="hljs-number">1</span>, black.<span class="hljs-number">2</span>);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Origin: ({}, {}, {})"</span>, origin.<span class="hljs-number">0</span>, origin.<span class="hljs-number">1</span>, origin.<span class="hljs-number">2</span>);
}
</code></pre>
<p>이런 식으로 쓸 수 있다.</p>
<h3 id="heading-7jyg64ubioq1royhsoyyta">유닛 구조체</h3>
<p>필드가 없는 구조체도 있는데 이를 <code>유닛 구조체</code>라고 부른다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">AlwaysEqual</span></span>; <span class="hljs-comment">// 필드가 없다!</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> subject = AlwaysEqual; 
    <span class="hljs-keyword">let</span> subject2 = AlwaysEqual;

    <span class="hljs-keyword">if</span> subject == subject2 {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"They are equal!"</span>);
    }
}
</code></pre>
<h3 id="heading-6rws7kgw7lk07jeqiouulouyhoq5hsdstpzroku">구조체에 디버깅 출력</h3>
<p>러스트는 구조체에 디버깅 출력을 할 수 있는 <code>Debug</code> 트레이트를 제공한다. 그런데 구조체에 해당 기능을 적용하려면 명시적인 동의가 필요하다. <code>#[derive(Debug)]</code>를 구조체 정의 위에 붙여주면 된다.</p>
<p>그러면</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Debug)]</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Rectangle</span></span> {
    width: <span class="hljs-built_in">u32</span>,
    height: <span class="hljs-built_in">u32</span>,
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> rect = Rectangle {
        width: <span class="hljs-number">30</span>,
        height: <span class="hljs-number">50</span>,
    };

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"rect is {:#?}"</span>, rect); <span class="hljs-comment">// {:#?}는 pretty print</span>
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"rect is {:?}"</span>, rect); <span class="hljs-comment">// {:?}는 일반 print</span>
 }
</code></pre>
<p>이런 식으로 구조체를 출력할 수 있다.</p>
<h2 id="heading-66mu7isc65oc">메서드</h2>
<p>메서드는 구조체에 정의된 함수로, 구조체의 인스턴스에 대해 호출할 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Debug)]</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Rectangle</span></span> {
    width: <span class="hljs-built_in">u32</span>,
    height: <span class="hljs-built_in">u32</span>,
}

<span class="hljs-keyword">impl</span> Rectangle {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">area</span> </span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">u32</span> {
        <span class="hljs-keyword">self</span>.width * <span class="hljs-keyword">self</span>.height
    }
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> rect1 = Rectangle {
        width: <span class="hljs-number">30</span>,
        height: <span class="hljs-number">50</span>,
    };

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The area of the rectangle is {} square pixels."</span>, rect1.area());
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"rect1 is {:#?}"</span>, rect1);
}
    }
}
</code></pre>
<p>로 쓸 수 있는데</p>
<p><code>Rectangle</code> 의 컨택스트에 <code>impl</code> 블록을 사용하여 메서드를 정의한다.</p>
<h3 id="heading-7jew6rsaio2vqoyima">연관 함수</h3>
<p><code>impl</code> 블록 내에 구현된 모든 함수를 <code>연관 함수</code>라고 부른다. 이건 <code>impl</code> 뒤에 나오는 타입과 모두 연관되어 있기 때문에 이런 이름이 붙었다.</p>
<p>메서드가 아닌 연관 함수도 존재하는데 이건 구조체의 새 인스턴스를 반환하는 생성자로 자주 활용된다.</p>
<h3 id="heading-impl">여러개의 <code>impl</code> 블록</h3>
<p>각 구조체는 여러 개의 <code>impl</code> 블록을 가질 수 있다. 예컨대</p>
<pre><code class="lang-rust"><span class="hljs-keyword">impl</span> Rectangle {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(width: <span class="hljs-built_in">u32</span>, height: <span class="hljs-built_in">u32</span>) -&gt; Rectangle {
        Rectangle { width, height }
    }
}

<span class="hljs-keyword">impl</span> Rectangle {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">is_square</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">bool</span> {
        <span class="hljs-keyword">self</span>.width == <span class="hljs-keyword">self</span>.height
    }
}
</code></pre>
<p>이런 식으로 여러 개의 <code>impl</code> 블록을 정의할 수 있다. <s>다만 여기서 이렇게 쓰는 게 유리하지는 않다.</s></p>
<h2 id="heading-7je06rgw7ziv">열거형</h2>
<p>열거형은 어떤 값이 여러 개의 가능한 값의 집합 중 하나라는 것을 나타내는 방법을 제공한다. 열거형으로 IPv4와 IPv6 주소를 표현할 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">IpAddrKine</span></span> {
    v4,
    v6,
}

<span class="hljs-keyword">let</span> four = IpAddrKind::v4;
<span class="hljs-keyword">let</span> six = IpAddrKind::v6;
</code></pre>
<p>이런 식으로 열거형을 정의할 수 있다. 이러면 이제 <code>IpAddrKind</code>는 코드 어디에서나 쓸 수 있는 커스텀 데이터 타입이 된다.</p>
<p>또 다른 예시로 <code>qiskit</code>이라는 양자 컴퓨팅 프레임워크에서 양자 게이트 관련 상태들이 이런 식으로 열거형으로 정의되어 있다.</p>
<p>열거형을 사용하면 코드를 간결하게 만들 수 있는데</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">IpAddr</span></span> {
    V4(<span class="hljs-built_in">String</span>),
    V6(<span class="hljs-built_in">String</span>),
}

<span class="hljs-keyword">let</span> home = IpAddr::V4(<span class="hljs-built_in">String</span>::from(<span class="hljs-string">"127.0.0.1"</span>));
<span class="hljs-keyword">let</span> loopback = IpAddr::V6(<span class="hljs-built_in">String</span>::from(<span class="hljs-string">"::1"</span>));
</code></pre>
<p>이 코드를 구조체로 해결하려고 하면 꽤나 길어진다.</p>
<h3 id="heading-option">Option 열거형</h3>
<p><code>Option</code> 열거형은 값이 있거나 없을 수도 있는 상황을 나타낸다. 널로 써도 될 것 같지만, 널은 런타임에 오류를 발생시킬 수 있기 때문에 <code>Option</code> 열거형을 사용한다.</p>
<p>요약하자면 널 값을 널이 아닌 것처럼 사용하려고 하면 여러 종류 에러가 나타날 수 있다는 것인데, 이것은 개념적인 문제에서 나타난다기보다 구현적인 문제에 의해 나타난다.</p>
<p>러스트에는 널이 없다. 다만 값의 존재 또는 부재를 나타내는 <code>Option</code> 열거형이 있다. <code>Option&lt;T&gt;</code>로 나타낸다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Option</span></span>&lt;T&gt; {
    <span class="hljs-literal">Some</span>(T),
    <span class="hljs-literal">None</span>,
}
</code></pre>
<p>와 같이 <strong>정의되어있다</strong>. 다시 구현할 필요는 없다! 여기서 <code>T</code>는 제네릭 타입 매개변수로, 구체적인 <code>T</code>의 타입을 집어넣는 것이 <code>Option</code>이 어떤 타입의 값을 가질 수 있는지 나타낸다.<code>T</code> 자체에는 어떤 타입이든 들어갈 수 있다.</p>
<hr />
<h1 id="heading-7iam7jyg6ram">소유권</h1>
<p><a target="_blank" href="https://maximizemaxwell.com/rust-ownership">러스트 소유권 이해하기</a> 를 참고할 수 있다.</p>
<h2 id="heading-7iam7jyg6ram-1">소유권</h2>
<p>러스트의 소유권 시스템은 메모리 안전성을 보장하는 핵심 개념이다. 소유권 규칙이라는 것이 존재하는데, 이는 다음과 같다:</p>
<ol>
<li><p>러스트에서 각각의 값은 소유자가 정해져 있다.</p>
</li>
<li><p>한 값의 소유자는 동시에 여럿 존재할 수 없다.</p>
</li>
<li><p>소유자가 스코프 밖으로 벗어날 때, 값은 버려진다.</p>
</li>
</ol>
<p>여기서 3번에 스코프라는 것에 대해 나오는데, 스코프란 프로그램 내에서 아이템이 유효한 범위를 말한다. 러스트에서는 중괄호 <code>{}</code>로 묶인 블록이 스코프를 나타낸다. 중괄호가 끝나는 시점에서 러스트는 <code>drop</code>이라는 함수를 호출하여 해당 변수의 메모리를 해제한다. 즉, 스코프가 끝나면 해당 변수는 더 이상 유효하지 않게 된다.</p>
<p>예제로 보자면</p>
<pre><code class="lang-rust">{ <span class="hljs-comment">// s가 선언되기 전이다. 아직 유효하지 않다</span>
    <span class="hljs-keyword">let</span> s = <span class="hljs-string">"Hello"</span>; <span class="hljs-comment">// s가 선언되어 여기서부터 유효하다</span>
    <span class="hljs-comment">// s로 어떠한 작업을 한다.</span>
} <span class="hljs-comment">// 스코프가 종료되어 s가 유효하지 않다.</span>
</code></pre>
<p>이런 식으로 스코프가 종료되면 해당 변수는 더 이상 유효하지 않게 된다.</p>
<h3 id="heading-string">String</h3>
<p>바로 위에서 "Hello"라는 문자열을 사용했는데, 이는 문자열 리터럴로, 컴파일 타임에 결정되는 불변 문자열이다. 하지만 러스트에서는 동적으로 크기가 변할 수 있는 문자열을 다루기 위해 <code>String</code> 타입을 제공한다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> s = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Hello"</span>);
</code></pre>
<p>로 쓰면 가변적인 문자열을 만들 수 있다. 이 문자열은 힙에 저장되며, 런타임에 크기가 결정된다. 이런 이야기가 왜 나오나 싶지만 뒤의 소유권 개념에서 다시 나온다.</p>
<h3 id="heading-7iam7jyg6ramioydtoupmq">소유권 이동</h3>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> s1 = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Hello"</span>);
<span class="hljs-keyword">let</span> s2 = s1;
</code></pre>
<p>와 같은 코드를 작성했다고 하자. 그런데 <code>s2</code>에 <code>s1</code>을 할당하면 스택에 있는 데이터가 복사되지 포인터가 가리키는 힙 영역의 데이터까지 복사되지는 않는다.</p>
<p>그래서 <code>s1</code>이 여전히 살아있다면 <code>s1</code>과 <code>s2</code>가 같은 힙 메모리를 가리키게 된다. 이런 경우 중복 해제 문제가 발생할 수 있다.</p>
<p>그래서 러스트에서는 소유권 이동이라는 개념을 제공하는데, <code>s1</code>이 더 이상 유효하지 않고, <code>s2</code>만 유효하다고 판단하는 것이다.</p>
<p>위에서 소유권 규칙을 설명했는데, 한 값의 소유자는 동시에 여럿 존재할 수 없다는 규칙이 바로 이 소유권 이동을 통해 지켜진다. 즉, <code>s1</code>은 더 이상 유효하지 않게 되고, <code>s2</code>가 <code>s1</code>의 소유권을 가지게 된다.</p>
<p>이것은 복사와는 다른 개념이다. 스택 데이터만 복사되는 것을 얕은 복사, 힙 데이터까지 복사되는 것을 깊은 복사라고 하는데, 얕은 복사에서는 위와 같이 중복 해제 문제가 발생할 수 있다고 했다. 그러나 깊은 복사에서 힙 데이터까지 복사하는 것은 상당히 비효율적이 될 수 있다. 따라서 러스트에서는 <code>소유권 이동</code>을 통해 소유권을 이전하는 방식을 채택하고 있다.</p>
<p>힙 데이터까지 깊은 복사를 하려면 <code>clone</code> 메서드를 사용해야 한다.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> s1 = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Hello"</span>);
<span class="hljs-keyword">let</span> s2 = s1.clone(); <span class="hljs-comment">// 깊은 복사</span>
</code></pre>
<p>이런식으로.</p>
<p>그런데</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> x = <span class="hljs-number">1</span>;
<span class="hljs-keyword">let</span> y = x; <span class="hljs-comment">// x의 값을 y에 복사</span>
<span class="hljs-built_in">println!</span>(<span class="hljs-string">"x: {}, y: {}"</span>, x, y); <span class="hljs-comment">// x와 y 모두 1</span>
</code></pre>
<p>를 실행하면 여전히 x가 유효하다. 아까 우리가 살펴본 소유권 개념으로는 그래서는 안 될 것 같은데, 왜 그럴까?</p>
<p>그건 소유권 개념은 힙 메모리 관리에 사용되는 개념이므로 저렇게 컴파일 타임에 크기를 알 수 있는 고정 크기 값들은 스택에 저장되기 때문이다. 일반적인 스칼라 타입이나 튜플은 이런 식의 복사가 가능하다.</p>
<pre><code class="lang-rust">
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> s1 = gives_ownership(); <span class="hljs-comment">// gives_ownership이 반환 값을 s1으로</span>
    <span class="hljs-keyword">let</span> s2 = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Hello"</span>); <span class="hljs-comment">// s2가 스코프 안으로</span>

    <span class="hljs-keyword">let</span> s3 = takes_and_gives_back(s2); <span class="hljs-comment">// s2가 함수로 값을 이동</span>
    <span class="hljs-comment">// 함수도 값을 s3으로 이동</span>
} <span class="hljs-comment">// s3이 스코프 밖으로 벗어나면서 drop</span>
<span class="hljs-comment">// s1도 스코프 밖으로 벗어나고 버려진다</span>

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">gives_ownership</span></span>() -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-comment">// gives_ownership은 자신의 반환 값을 자신의 호출자 함수로</span>
    <span class="hljs-comment">// 이동시키게 된다</span>
    <span class="hljs-keyword">let</span> some_string = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"yours"</span>); <span class="hljs-comment">//some_string이 스코프 안으로</span>

    some_string <span class="hljs-comment">// some_string이 반환되고 호출자 함수로 이동</span>
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">takes_and_gives_back</span></span>(a_string: <span class="hljs-built_in">String</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-comment">// a_string이 스코프 안으로</span>
    a_string <span class="hljs-comment">// a_string이 반환되고 호출자 함수로 이동</span>
}
</code></pre>
<p>위 예제를 통해 소유권을 더 깊이 이해할 수 있다.</p>
<h2 id="heading-7lc47kgw7jmaioumgoyxra">참조와 대여</h2>
<p>만약 함수에 값을 넘겨주고 싶지만 소유권을 넘겨주고 싶지 않다면, 참조를 사용해야 한다. 참조는 <code>&amp;</code> 기호를 사용하여 생성한다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> s1 = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello"</span>);

    <span class="hljs-keyword">let</span> (s2, len) = calculate_length(s1);

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The length of '{}' is {}."</span>, s2, len);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">calculate_length</span></span>(s: <span class="hljs-built_in">String</span>) -&gt; (<span class="hljs-built_in">String</span>, <span class="hljs-built_in">usize</span>) {
    <span class="hljs-keyword">let</span> length = s.len(); <span class="hljs-comment">// len()은 String의 길이를 반환합니다</span>

    (s, length)
}
</code></pre>
<p>이걸 어떻게 바꿀 수 있을까?</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> s1 = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello"</span>);
    <span class="hljs-keyword">let</span> len = calculate_length(&amp;s1); <span class="hljs-comment">// s1의 참조를 넘겨줍니다</span>
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"The length of '{}' is {}."</span>, s1, len);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">calculate_length</span></span>(s: &amp;<span class="hljs-built_in">String</span>) -&gt; <span class="hljs-built_in">usize</span> {
    s.len() <span class="hljs-comment">// s의 길이를 반환합니다</span>
}
</code></pre>
<p>이런 식으로 <code>&amp;</code>를 사용하여 참조를 넘겨줄 수 있다. 이 경우 소유권은 이동하지 않고, 함수 내에서 <code>s</code>의 값을 읽을 수만 있다.</p>
<h3 id="heading-6rca67oaioywuoyhsoyeka">가변 참조자</h3>
<p>참조자도 <code>mut</code> 키워드를 사용하여 가변 참조자를 만들 수 있다. 가변 참조자는 값을 변경할 수 있는 참조자이다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> s = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello"</span>);

    change(&amp;<span class="hljs-keyword">mut</span> s); <span class="hljs-comment">// 가변 참조자를 넘겨줍니다</span>
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Changed string: {}"</span>, s);
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">change</span></span>(s: &amp;<span class="hljs-keyword">mut</span> <span class="hljs-built_in">String</span>) {
    s.push_str(<span class="hljs-string">", world!"</span>); <span class="hljs-comment">// s의 값을 변경합니다</span>
}
</code></pre>
<p>이런 식으로 가변 참조자를 사용하여 값을 변경할 수 있다. 그런데 가변 참조자는 큰 제약사항이 있는데, 어떤 값에 대해 가변 참조자가 있다면 <strong>그 값에 대한 참조자는 더 이상 만들 수 없다.</strong> 즉, 가변 참조자는 동시에 하나만 존재할 수 있다. 이는 데이터 경합을 방지하기 위한 것이다.</p>
<h3 id="heading-64yv6ria66ebioywuoyhsa">댕글링 참조</h3>
<p>댕글링 참조는 참조자가 유효하지 않은 메모리를 가리키는 경우를 말한다. 러스트에서는 댕글링 참조를 방지하기 위해 컴파일 타임에 참조의 유효성을 검사한다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> reference_to_nothing = dangle();
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">dangle</span></span>() -&gt; &amp;<span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> s = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello"</span>);

    &amp;s
}
</code></pre>
<p>이런 코드는 컴파일 에러를 발생시킨다. 왜냐하면 <code>s</code>는 함수가 끝나면 스코프 밖으로 벗어나면서 메모리가 해제되기 때문이다.</p>
<p>이걸 유효하게 하려면</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">no_dangle</span></span>() -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> s = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello"</span>);

    s <span class="hljs-comment">// 소유권을 반환합니다</span>
}
</code></pre>
<p>이런 식으로 소유권을 반환해야 한다. 즉, 참조를 반환하는 것이 아니라 소유권을 반환하는 것이다. 이렇게 하면 <code>s</code>가 스코프 밖으로 벗어나면서 메모리가 해제되지 않고, <code>no_dangle</code> 함수의 호출자가 <code>s</code>의 소유권을 가지게 된다.</p>
<h2 id="heading-7iqs65287j207iqk">슬라이스</h2>
<p>슬라이스는 배열이나 문자열의 전체를 참조하는 것이 아니라 그 일부를 참조하는 것을 말한다.</p>
<p>문자열 슬라이스의 예제를 살펴보자.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> s = <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"hello world"</span>);
<span class="hljs-keyword">let</span> hello = &amp;s[<span class="hljs-number">0</span>..<span class="hljs-number">5</span>]; <span class="hljs-comment">// "hello" 부분 문자열을 슬라이스로 참조</span>
<span class="hljs-keyword">let</span> world = &amp;s[<span class="hljs-number">6</span>..<span class="hljs-number">11</span>]; <span class="hljs-comment">// "world" 부분 문자열을 슬라이스로 참조</span>
</code></pre>
<p>이런 식으로 문자열의 일부를 슬라이스로 참조할 수 있다.</p>
<hr />
<h1 id="heading-amp-1">제네릭 &amp; 라이프타임</h1>
<h2 id="heading-7kcc64sk66at">제네릭</h2>
<p>제네릭을 사용하면 함수 시그니처나 구조체 아이템에 다양한 데이터 타입을 사용할 수 있다. 제네릭을 사용하여 아래 예제의 두 함수를 합칠 수 있다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">largest_i32</span></span>(list: &amp;[<span class="hljs-built_in">i32</span>]) -&gt; &amp;<span class="hljs-built_in">i32</span> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> largest = &amp;list[<span class="hljs-number">0</span>];

    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> list {
        <span class="hljs-keyword">if</span> item &gt; largest {
            largest = item;
        }
    }

    largest
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">largest_char</span></span>(list: &amp;[<span class="hljs-built_in">char</span>]) -&gt; &amp;<span class="hljs-built_in">char</span> {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> largest = &amp;list[<span class="hljs-number">0</span>];

    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> list {
        <span class="hljs-keyword">if</span> item &gt; largest {
            largest = item;
        }
    }

    largest
}
</code></pre>
<p>이 두 함수를</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">largest</span></span>&lt;T&gt; (list: &amp;[T]) -&gt; &amp;T {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> largest = &amp;list[<span class="hljs-number">0</span>];
    T:<span class="hljs-built_in">PartialOrd</span>; <span class="hljs-comment">// T가 PartialOrd 트레이트를 구현해야 한다.</span>
    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> list {
        <span class="hljs-keyword">if</span> item &gt; largest {
            largest = item;
        }
    }

    largest
}
</code></pre>
<p>이런 식으로 제네릭을 사용하여 하나의 함수로 합칠 수 있다. 여기서 <code>T</code>는 제네릭 타입 매개변수로, 함수가 호출될 때 구체적인 타입으로 대체된다. 다만 <code>T</code>가 <code>PartialOrd</code> 트레이트를 구현해야 한다는 제약 조건을 추가해야 한다. 이는 <code>&gt;</code> 연산자를 사용할 수 있도록 하기 위함이다. <code>i32</code>와 <code>char</code>는 모두 <code>PartialOrd</code>를 구현하고 이므로 이 함수는 두 타입 모두에 대해 작동한다.</p>
<h3 id="heading-7kcc64sk66atioq1royhsoyyta">제네릭 구조체</h3>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Point</span></span>&lt;T&gt; {
    x: T,
    y: T,
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> integer_point = Point { x: <span class="hljs-number">5</span>, y: <span class="hljs-number">10</span> };
    <span class="hljs-keyword">let</span> float_point = Point { x: <span class="hljs-number">1.0</span>, y: <span class="hljs-number">4.0</span> };

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Integer Point: ({}, {})"</span>, integer_point.x, integer_point.y);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Float Point: ({}, {})"</span>, float_point.x, float_point.y);
}
</code></pre>
<p>이런식으로 제네릭 구조체를 정의할 수 있는데 여기서 x, y는 모두 같은 타입이어야만 컴파일 에러가 발생하지 않는다. 만약 서로 다른 타입을 사용하고 싶다면</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Point</span></span>&lt;T, U&gt; {
    x: T,
    y: U,
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> integer_point = Point { x: <span class="hljs-number">5</span>, y: <span class="hljs-number">10.0</span> };
    <span class="hljs-keyword">let</span> float_point = Point { x: <span class="hljs-number">1.0</span>, y: <span class="hljs-number">4.0</span> };

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Integer Point: ({}, {})"</span>, integer_point.x, integer_point.y);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Float Point: ({}, {})"</span>, float_point.x, float_point.y);
}
</code></pre>
<p>이런 식으로 제네릭 타입 매개변수를 두 개 사용하여 서로 다른 타입을 사용할 수 있다.</p>
<h3 id="heading-7kcc64sk66atioyxtoqxso2ylq">제네릭 열거형</h3>
<p>제네릭 열거형도 마찬가지로 정의할 수 있다. 이전에 <code>Option</code> 열거형을 살펴봤는데, 이 또한 제네릭 열거형이다.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Option</span></span>&lt;T&gt;{
    <span class="hljs-literal">Some</span>(T),
    <span class="hljs-literal">None</span>,
}
</code></pre>
<p>다른 예제로</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Result</span></span>&lt;T, E&gt; {
    <span class="hljs-literal">Ok</span>(T),
    <span class="hljs-literal">Err</span>(E),
}
</code></pre>
<p>도 존재한다. 이 열거형은 성공적인 결과와 실패한 결과를 나타내는 데 사용된다. <code>T</code>는 성공적인 결과의 타입, <code>E</code>는 실패한 결과의 타입을 나타낸다.</p>
<h3 id="heading-7kcc64sk66atiouployenoutna">제네릭 메서드</h3>
<p>제네릭 메서드도 정의할 수 있다. 예를 들어</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Point</span></span>&lt;T&gt; {
    x: T,
    y: T,
}

<span class="hljs-keyword">impl</span>&lt;T&gt; Point&lt;T&gt; {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(x: T, y: T) -&gt; Point&lt;T&gt; {
        Point { x, y }
    }

    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">x</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; &amp;T {
        &amp;<span class="hljs-keyword">self</span>.x
    }

    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">y</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; &amp;T {
        &amp;<span class="hljs-keyword">self</span>.y
    }
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> p = Point::new(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>);
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Point: ({}, {})"</span>, p.x(), p.y());
}
</code></pre>
<h2 id="heading-7yq466ci7j207yq4">트레이트</h2>
<p>트레이트는 특정한 타입이 가지고 있으면서 다른 타입과 공유할 수 있는 기능을 정의한다. 이전에 <code>Debug</code>도 트레이트의 일종이다.</p>
<p>아래 코드는 <code>candle</code>의 <a target="_blank" href="http://qwen3.rs"><code>qwen3.rs</code></a>에서 가져온 예제이다. 이런 식으로 기본 트레이트인 <code>Debug</code>과 <code>Clone</code>같은 것은 꽤 자주 쓰이는 편이다.</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Debug, Clone)]</span>
<span class="hljs-keyword">pub</span>(<span class="hljs-keyword">crate</span>) <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Qwen3RotaryEmbedding</span></span> {
    sin: Tensor,
    cos: Tensor,
}

<span class="hljs-keyword">impl</span> Qwen3RotaryEmbedding {
    <span class="hljs-keyword">pub</span>(<span class="hljs-keyword">crate</span>) <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(dtype: DType, cfg: &amp;Config, dev: &amp;Device) -&gt; <span class="hljs-built_in">Result</span>&lt;<span class="hljs-keyword">Self</span>&gt; {
        <span class="hljs-keyword">let</span> dim = cfg.head_dim;
        <span class="hljs-keyword">let</span> max_seq_len = cfg.max_position_embeddings;
        <span class="hljs-keyword">let</span> inv_freq: <span class="hljs-built_in">Vec</span>&lt;_&gt; = (<span class="hljs-number">0</span>..dim)
            .step_by(<span class="hljs-number">2</span>)
            .map(|i| <span class="hljs-number">1f32</span> / cfg.rope_theta.powf(i <span class="hljs-keyword">as</span> <span class="hljs-built_in">f64</span> / dim <span class="hljs-keyword">as</span> <span class="hljs-built_in">f64</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">f32</span>)
            .collect();
        <span class="hljs-keyword">let</span> inv_freq_len = inv_freq.len();
        <span class="hljs-keyword">let</span> inv_freq = Tensor::from_vec(inv_freq, (<span class="hljs-number">1</span>, inv_freq_len), dev)?.to_dtype(dtype)?;
        <span class="hljs-keyword">let</span> t = Tensor::arange(<span class="hljs-number">0u32</span>, max_seq_len <span class="hljs-keyword">as</span> <span class="hljs-built_in">u32</span>, dev)?
            .to_dtype(dtype)?
            .reshape((max_seq_len, <span class="hljs-number">1</span>))?;
        <span class="hljs-keyword">let</span> freqs = t.matmul(&amp;inv_freq)?;
        <span class="hljs-literal">Ok</span>(<span class="hljs-keyword">Self</span> {
            sin: freqs.sin()?,
            cos: freqs.cos()?,
        })
    }

    <span class="hljs-comment">/// Apply RoPE (q, k shape: B x H x L x D)</span>
    <span class="hljs-keyword">pub</span>(<span class="hljs-keyword">crate</span>) <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">apply</span></span>(&amp;<span class="hljs-keyword">self</span>, q: &amp;Tensor, k: &amp;Tensor, offset: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-built_in">Result</span>&lt;(Tensor, Tensor)&gt; {
        <span class="hljs-keyword">let</span> (_, _, seq_len, _) = q.dims4()?;
        <span class="hljs-keyword">let</span> cos = <span class="hljs-keyword">self</span>.cos.narrow(<span class="hljs-number">0</span>, offset, seq_len)?;
        <span class="hljs-keyword">let</span> sin = <span class="hljs-keyword">self</span>.sin.narrow(<span class="hljs-number">0</span>, offset, seq_len)?;
        <span class="hljs-keyword">let</span> q_embed = candle_nn::rotary_emb::rope(&amp;q.contiguous()?, &amp;cos, &amp;sin)?;
        <span class="hljs-keyword">let</span> k_embed = candle_nn::rotary_emb::rope(&amp;k.contiguous()?, &amp;cos, &amp;sin)?;
        <span class="hljs-literal">Ok</span>((q_embed, k_embed))
    }
}
</code></pre>
<p>다시 돌아와서 트레이트를 정의하는 방법을 알아보자.</p>
<p>트레이트는 <code>trait</code> 키워드를 사용하여 정의한다. 예를 들어</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">trait</span> <span class="hljs-title">Summary</span></span> {
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">summarize</span></span>(&amp;<span class="hljs-keyword">self</span>) -&gt; <span class="hljs-built_in">String</span> {
        <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"(Read more...)"</span>)
    }
}
</code></pre>
<p>이런 식으로 정의하고</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> article = NewsArticle {
    headline: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Breaking News"</span>),
    location: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"New York"</span>),
    author: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"John Doe"</span>),
    content: <span class="hljs-built_in">String</span>::from(<span class="hljs-string">"Lorem ipsum dolor sit amet..."</span>),
};

<span class="hljs-built_in">println!</span>(<span class="hljs-string">"New article available! {}"</span>, article.summarize());
</code></pre>
<p>이런 식으로 사용할 수 있다.</p>
<h2 id="heading-65287j207zse7yoa7j6e7jy866gcioywuoyhsoyekoydmcdsnkdtmqjshleg6rka7kad7zwy6riw">라이프타임으로 참조자의 유효성 검증하기</h2>
<p>라이프타임은 어떤 참조자가 필요한 기간동안 유효함을 보장하도록 하는 일종의 제네릭이다.</p>
<p>라이프타임의 주 목적은 댕글링 참조 방지다. 예를 들어</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> r;
{
        <span class="hljs-keyword">let</span> x = <span class="hljs-number">5</span>;
        r = &amp;x; <span class="hljs-comment">// x는 이 블록 안에서만 유효하다</span>
    }
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"r: {}"</span>, r); <span class="hljs-comment">// r은 x를 참조하지만 x는 이미 스코프 밖으로 벗어났다</span>
}
</code></pre>
<p>이런 코드는 컴파일 에러를 발생시킨다. 그런데 이때 컴파일러의 에러 메시지를 살펴보면 <code>'x' does not live long enough</code>라는 메시지가 나타난다.</p>
<h2 id="heading-borrow-checker">대여 검사기(borrow checker)</h2>
<p>러스트 컴파일러는 대여 검사기라는 기능을 통해 참조자의 유효성을 검사한다. 대여 검사기는 참조자가 유효한지, 댕글링 참조가 발생하지 않는지 등을 검사한다.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> r;                <span class="hljs-comment">// ---------+-- 'a</span>
                          <span class="hljs-comment">//          |</span>
    {                     <span class="hljs-comment">//          |</span>
        <span class="hljs-keyword">let</span> x = <span class="hljs-number">5</span>;        <span class="hljs-comment">// -+-- 'b  |</span>
        r = &amp;x;           <span class="hljs-comment">//  |       |</span>
    }                     <span class="hljs-comment">// -+       |</span>
                          <span class="hljs-comment">//          |</span>
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"r: {}"</span>, r); <span class="hljs-comment">//          |</span>
}                         <span class="hljs-comment">// ---------+</span>
</code></pre>
<p>위 예제의 라이프타임을 시각적으로 나타내면 위와 같다. 여기서 <code>'a</code>는 <code>r</code>의 라이프타임, <code>'b</code>는 <code>x</code>의 라이프타임을 나타낸다. <code>r</code>은 <code>x</code>를 참조하지만, <code>x</code>는 스코프 밖으로 벗어나면서 더 이상 유효하지 않게 된다. 따라서 <code>r</code>은 댕글링 참조가 되어 컴파일 에러가 발생한다. 그러니까 참조 대상이 참조자보다 오래 살지 못해서 러스트 컴파일러가 이 프로그램을 허용하지 않는 것이다.</p>
]]></content:encoded></item><item><title><![CDATA[Intro to ETL Pipeline]]></title><description><![CDATA[Intro to ETL Pipeline
우선 ETL은 약자인데 무엇의 약자이냐면
ETL:

Extract

Transform

Load 의 약자이다.


그래서 ETL 파이프라인이 뭐냐고 하면, ETL 파이프라인은 데이터 파이프라인의 일부이다.
그럼 다시 데이터 파이프라인이란 무엇인가? 데이터 파이프라인이란 다양한 데이터 소스에서 원시 데이터를 수집하고 변환한 다음 분석을 위해 데이터 레이크나 데이터 웨어하우스같은 데이터 저장소로 이식하는 방법...]]></description><link>https://maximizemaxwell.com/intro-to-etl-pipeline</link><guid isPermaLink="true">https://maximizemaxwell.com/intro-to-etl-pipeline</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Thu, 22 May 2025 13:58:04 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-intro-to-etl-pipeline">Intro to ETL Pipeline</h1>
<p>우선 ETL은 약자인데 무엇의 약자이냐면</p>
<p>ETL:</p>
<ul>
<li><p>Extract</p>
</li>
<li><p>Transform</p>
</li>
<li><p>Load 의 약자이다.</p>
</li>
</ul>
<p>그래서 ETL 파이프라인이 뭐냐고 하면, ETL 파이프라인은 데이터 파이프라인의 일부이다.</p>
<p>그럼 다시 데이터 파이프라인이란 무엇인가? <code>데이터 파이프라인</code>이란 다양한 데이터 소스에서 원시 데이터를 수집하고 변환한 다음 분석을 위해 <code>데이터 레이크</code>나 <code>데이터 웨어하우스</code>같은 데이터 저장소로 이식하는 방법이다.</p>
<h1 id="heading-etl-pipeline">ETL Pipeline</h1>
<h2 id="heading-astro-cli">Astro cli</h2>
<p><code>Astro cli</code>는 Airflow 세팅을 위한 일종의 프레임워크라고 보면 될 듯하다. 이 툴을 이용해서 초기화하면 웹 프레임워크 (리액트나 뷰처럼) 테스트부터 세팅에 필요한 기초적인 디렉토리 구조와 일부 코드들이 제공된다.</p>
<p>리눅스 환경에서 설치했으므로</p>
<pre><code class="lang-bash">curl -sSL install.astronomer.io | sudo bash -s
</code></pre>
<p>와 같이 설치해주었다.</p>
<p>설치 후에 초기화를 위해</p>
<pre><code class="lang-bash">  ~/coding/MLOps_study/week9 on   main ?2 ──────  max-env at  21:19:21 ─╮
❯ astro dev init                                                             ─╯
Initialized empty Astro project <span class="hljs-keyword">in</span> /home/max/coding/MLOps_study/week9
</code></pre>
<p><code>astro dev init</code> 을 진행해준다.</p>
<p>이를 진행해주면</p>
<p><img src="https://i.imgur.com/wpfsoa1.png" alt /></p>
<p>위 사진과 같은 디렉토리 구조가 형성된다.</p>
<p>참고하는 강의(Complete MLOps Bootcamp With 10+ End to End ML Projects)에서 강사는</p>
<h3 id="heading-7jes64u0">여담</h3>
<p>API -&gt; Transformation(python code) -&gt; (JSON 포맷) -&gt; LOAD -&gt; DB 의 파이프라인을 소개하고 있고 본 강의에서는 DB를 컨테이너애서 사용하고자 하나, 한 가지 덧붙이자면 실제 DB를 운영하고자 한다면 도커 컨테이너는 그닥 좋은 선택은 아니다.</p>
<p>파이프라인을 테스트하는 용도로는 괜찮으나 도커 컨테이너는 기본적으로 데이터를 지속시키고 안정적으로 유지하기보단 분리된 환경의 프로그램을 틀을 이용해 빠르게 찍어내고 운영할 수 있는 것에 가깝다.</p>
<p>애초에 바인드 마운트를 쓰지 않는 이상 컨테이너가 삭제되면 데이터는 날아가고, <code>--rm</code> 옵션을 붙였다면 컨테이너는 중지되기만 해도 날아가는 가변적인 것이니...게다가 로컬에 의존하게 되는 바인드 마운트를 테스트 밖의 범위에서 쓰는 것도 바람직하지 않으니 실 운영에서는 DB서버를 컨테이너가 아닌 따로 두는 것을 권장하는 편이며, 현재도 그렇게 하고 있다. (성능 이슈도 있고)</p>
<p>그러나 학습이나 테스트용으로 여러가지 애플리케이션의 상호작용을 테스트하고자 한다면 컨테이너와 도커 컴포즈는 꽤 괜찮은 선택이다.</p>
<h3 id="heading-airflow-hooks">Airflow Hooks</h3>
<p>Airflow에서 Hook이란 커스텀하게 로직을 구현하거나 외부 시스템의 푸시 작업 흐름을 만들 때 사용하는 것이다. Webhook에서의 hook과 의미적으로 크게 다르지는 않아보인다.</p>
<p>강의에서는 Postgres 접근을 위한 훅을 사용하는데 Mysql에서도 가능하다.</p>
<p>따로 더 말할 것은 없고 그냥 쉽게 접근하게 해주는 무언가라서</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> airflow.providers.postgres.hooks.postgres <span class="hljs-keyword">import</span> PostgresHook
</code></pre>
<p>이 친구를 잘 써주면 되는것이다.</p>
<h1 id="heading-7yym7j207zse65287j24">파이프라인</h1>
<p>파이프라인을 요약하자면</p>
<ol>
<li><p>테이블이 없으면 만들고</p>
</li>
<li><p>API로 데이터를 추출해오고(추출 파이프라인, 여기서는 NASA API 사용)</p>
</li>
<li><p>필요한 정보로 적절히 변환해주고</p>
</li>
<li><p>DB로 적재해준다.(여기서는 적재 공간으로 컨테이너에서 구르는 postgres 사용)</p>
</li>
<li><p>그리고 데이터를 검증해주고</p>
</li>
<li><p>마지막으로 태스크 의존성을 검증하는 과정이다.</p>
</li>
</ol>
<p>코드로 본다면</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> airflow <span class="hljs-keyword">import</span> DAG
<span class="hljs-keyword">from</span> airflow.providers.http.operators.http <span class="hljs-keyword">import</span> SimpleHttpOperator
<span class="hljs-keyword">from</span> airflow.decorators <span class="hljs-keyword">import</span> task
<span class="hljs-keyword">from</span> airflow.providers.postgres.hooks.postgres <span class="hljs-keyword">import</span> PostgresHook
<span class="hljs-keyword">from</span> airflow.utils.dates <span class="hljs-keyword">import</span> days_ago
<span class="hljs-keyword">import</span> json


<span class="hljs-comment">## Define the DAG</span>
<span class="hljs-keyword">with</span> DAG(
    dag_id=<span class="hljs-string">'nasa_apod_postgres'</span>,
    start_date=days_ago(<span class="hljs-number">1</span>),
    schedule_interval=<span class="hljs-string">'@daily'</span>,
    catchup=<span class="hljs-literal">False</span>

) <span class="hljs-keyword">as</span> dag:

    <span class="hljs-comment">## step 1: Create the table if it doesnt exists</span>

<span class="hljs-meta">    @task</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_table</span>():</span>
        <span class="hljs-comment">## initialize the Postgreshook</span>
        postgres_hook=PostgresHook(postgres_conn_id=<span class="hljs-string">"my_postgres_connection"</span>)

        <span class="hljs-comment">## SQL query to create the table</span>
        create_table_query=<span class="hljs-string">"""
        CREATE TABLE IF NOT EXISTS apod_data (
            id SERIAL PRIMARY KEY,
            title VARCHAR(255),
            explanation TEXT,
            url TEXT,
            date DATE,
            media_type VARCHAR(50)
        );


        """</span>
        <span class="hljs-comment">## Execute the table creation query</span>
        postgres_hook.run(create_table_query)


    <span class="hljs-comment">## Step 2: Extract the NASA API Data(APOD)-Astronomy Picture of the Day[Extract pipeline]</span>
    <span class="hljs-comment">## https://api.nasa.gov/planetary/apod?api_key=7BbRvxo8uuzas9U3ho1RwHQQCkZIZtJojRIr293q</span>
    extract_apod=SimpleHttpOperator(
        task_id=<span class="hljs-string">'extract_apod'</span>,
        http_conn_id=<span class="hljs-string">'nasa_api'</span>,  <span class="hljs-comment">## Connection ID Defined In Airflow For NASA API</span>
        endpoint=<span class="hljs-string">'planetary/apod'</span>, <span class="hljs-comment">## NASA API enpoint for APOD</span>
        method=<span class="hljs-string">'GET'</span>,
        data={<span class="hljs-string">"api_key"</span>:<span class="hljs-string">"{{ conn.nasa_api.extra_dejson.api_key}}"</span>}, <span class="hljs-comment">## USe the API Key from the connection</span>
        response_filter=<span class="hljs-keyword">lambda</span> response:response.json(), <span class="hljs-comment">## Convert response to json</span>
    )



    <span class="hljs-comment">## Step 3: Transform the data(Pick the information that i need to save)</span>
<span class="hljs-meta">    @task</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform_apod_data</span>(<span class="hljs-params">response</span>):</span>
        apod_data={
            <span class="hljs-string">'title'</span>: response.get(<span class="hljs-string">'title'</span>, <span class="hljs-string">''</span>),
            <span class="hljs-string">'explanation'</span>: response.get(<span class="hljs-string">'explanation'</span>, <span class="hljs-string">''</span>),
            <span class="hljs-string">'url'</span>: response.get(<span class="hljs-string">'url'</span>, <span class="hljs-string">''</span>),
            <span class="hljs-string">'date'</span>: response.get(<span class="hljs-string">'date'</span>, <span class="hljs-string">''</span>),
            <span class="hljs-string">'media_type'</span>: response.get(<span class="hljs-string">'media_type'</span>, <span class="hljs-string">''</span>)

        }
        <span class="hljs-keyword">return</span> apod_data


    <span class="hljs-comment">## step 4:  Load the data into Postgres SQL</span>
<span class="hljs-meta">    @task</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load_data_to_postgres</span>(<span class="hljs-params">apod_data</span>):</span>
        <span class="hljs-comment">## Initialize the PostgresHook</span>
        postgres_hook=PostgresHook(postgres_conn_id=<span class="hljs-string">'my_postgres_connection'</span>)

        <span class="hljs-comment">## Define the SQL Insert Query</span>

        insert_query = <span class="hljs-string">"""
        INSERT INTO apod_data (title, explanation, url, date, media_type)
        VALUES (%s, %s, %s, %s, %s);
        """</span>

        <span class="hljs-comment">## Execute the SQL Query</span>

        postgres_hook.run(insert_query,parameters=(
            apod_data[<span class="hljs-string">'title'</span>],
            apod_data[<span class="hljs-string">'explanation'</span>],
            apod_data[<span class="hljs-string">'url'</span>],
            apod_data[<span class="hljs-string">'date'</span>],
            apod_data[<span class="hljs-string">'media_type'</span>]


        ))



    <span class="hljs-comment">## step 5: Verify the data DBViewer</span>


    <span class="hljs-comment">## step 6: Define the task dependencies</span>
    <span class="hljs-comment">## Extract</span>
    create_table() &gt;&gt; extract_apod  <span class="hljs-comment">## Ensure the table is create befor extraction</span>
    api_response=extract_apod.output
    <span class="hljs-comment">## Transform</span>
    transformed_data=transform_apod_data(api_response)
    <span class="hljs-comment">## Load</span>
    load_data_to_postgres(transformed_data)
</code></pre>
<p>이러한 상황이다.</p>
<p>이 코드는 astro cli를 이용해 구축해준 디렉토리에서 <code>dags/</code><a target="_blank" href="http://etl.py"><code>etl.py</code></a>에 위치하도록 두면 된다.</p>
<p>이 과정도 마찬가지로 AWS EC2에 배포할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[동시성과 병렬성]]></title><description><![CDATA[이 포스트는 레퍼런스로 동시성 프로그래밍(OREILLY, 한빛미디어) 를 사용하고 있습니다.
프로세스
동시성과 병렬성 개념 이전에 프로세스란 무엇인가? 이 책에서 프로세스란 특정 시간에 일부만 존재하는(시간적인 넓이를 갖는) 개념 으로 소개하고 있다.
프로세스는 4가지 상태를 가질 수 있는데:

실행 전 상태: 계산 실행 전의 상태. 실행 상태로 전이 가능

실행 상태: 계산 실행중의 상태. 대기 또는 계산 종료 상태로 전이 가능

대기 상태:...]]></description><link>https://maximizemaxwell.com/concurrency</link><guid isPermaLink="true">https://maximizemaxwell.com/concurrency</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Sun, 18 May 2025 10:46:45 GMT</pubDate><content:encoded><![CDATA[<p>이 포스트는 레퍼런스로 <strong>동시성 프로그래밍(OREILLY, 한빛미디어)</strong> 를 사용하고 있습니다.</p>
<h1 id="heading-7zse66gc7is47iqk">프로세스</h1>
<p>동시성과 병렬성 개념 이전에 프로세스란 무엇인가? 이 책에서 프로세스란 <strong>특정 시간에 일부만 존재하는(시간적인 넓이를 갖는) 개념</strong> 으로 소개하고 있다.</p>
<p>프로세스는 4가지 상태를 가질 수 있는데:</p>
<ul>
<li><p><strong>실행 전 상태</strong>: 계산 실행 전의 상태. 실행 상태로 전이 가능</p>
</li>
<li><p><strong>실행 상태</strong>: 계산 실행중의 상태. 대기 또는 계산 종료 상태로 전이 가능</p>
</li>
<li><p><strong>대기 상태</strong>: 계산을 일시적으로 정지한 상태. 실행 상태로 전이 가능</p>
</li>
<li><p><strong>종료 상태</strong>: 계산을 종료한 상태</p>
</li>
</ul>
<p>프로세스는 실행 이후에 <strong>실행 상태</strong>와 <strong>대기 상태</strong> 간을 전이하다가 종료될 수 있는데, 계속 실행되지 않고 대기 상태로 전이하게 되는 이유로 아래 세 가지가 있다.</p>
<ol>
<li><p>데이터 도착을 기다리는 상황. 연산을 수행할 데이터가 아직 도착하지 않았다면 계산을 수행할 수 없다.</p>
</li>
<li><p>계산 리소스를 기다리는 상황. 계산에 필요한 리소스가 현재 충분하지 않은 경우 리소스가 반환되어 계산이 가능해지는 상황을 기다려야 한다.</p>
</li>
<li><p>타이머 등에 의해 자발적으로 대기 상태로 진입하는 경우. 이 경우 데이터와 리소스가 충분함에도 계산 조건(예: 시간)을 만족하지 않으면 대기 상태를 유지한다.</p>
</li>
</ol>
<hr />
<h1 id="heading-64z7iuc7isx">동시성</h1>
<p><strong>동시성</strong>이란 2개 이상의 프로세스가 동시에 계산을 진행하는 상태를 의미한다.</p>
<p><img src="https://i.imgur.com/FJq1R5T.png" alt /></p>
<p>위 그림에서 $t_{0}$ 에서 $t_{1}$ 까지를 <strong>계산 중 상태</strong> 라고 정의한다.</p>
<p><strong>계산 중 상태</strong>는 실행 상태 + 대기 상태, 즉 프로세스가 실행 시작 이후 종료되기까지의 시간이다.</p>
<p>따라서 이 계산 중인 시간이 겹치는 상황에서 동시에 실행되고 있다고 말할 수 있다. (개인적으로 실행과 계산 중의 개념이 다르기 때문에 동시에 실행한다는 표현이 헷갈릴 수 있다고 생각한다.)</p>
<p>책의 정의에선 <strong>동시</strong>를 아래와 같이 설명하고 있다.</p>
<p><code>동시</code>: 시각 $t$에서 여러 프로세스 $p_{0} ... p_{n}$이 <strong>계산 중인 상태</strong>에 있을 때 프로세스 $p_{0} ... p_{n}$ 이 <strong>동시에 실행</strong>되고 있다고 한다.</p>
<p><img src="https://i.imgur.com/dyfB15o.png" alt /></p>
<p>따라서 위 그림에서 프로세스 A와 B는 $(t_{0}, t_{1})$에 동시에 실행되고 있는 것이다.</p>
<hr />
<h1 id="heading-67or66cs7isx">병렬성</h1>
<p>동시성 프로그래밍 책에서 <code>병렬성</code>은 <strong>같은 시각에서 여러 프로세스가 동시에 계산을 실행하는 상태</strong>를 의미한다고 정의한다.</p>
<p><img src="https://i.imgur.com/gbXbDHk.png" alt /></p>
<p>동시성과의 차이가 있다면 병렬성은 <strong>실행 상태</strong>가 겹치는 순간만을 포함한다. 대기 상태가 겹치거나, 하나가 실행 상태, 다른 하나가 대기 상태에 있는 것과 같은 상황은 포함되지 않는다.</p>
<p>병렬성에는 크게 세 가지 종류가 있는데</p>
<ul>
<li><p>태스크 병렬성</p>
</li>
<li><p>데이터 병렬성</p>
</li>
<li><p>인스트럭션 레벨 병렬성 이 있다.</p>
</li>
</ul>
<h2 id="heading-7yoc7iqk7ygsiouzkeugroyesq">태스크 병렬성</h2>
<p>앞에서 본 그림은 태스크 병렬성이다. 태스크 병렬성은 여러 태스크(프로세스)가 동시에 실행되는 것을 의미한다.</p>
<h2 id="heading-642w7j207yswiouzkeugroyesq">데이터 병렬성</h2>
<p>데이터를 여러 개로 나눠서 병렬로 처리하는 방법이다.</p>
<p><img src="https://i.imgur.com/Xt8l1IJ.png" alt /></p>
<p>이건 연산기가 한 개인 경우에서 수행되는 계산의 상황인데, 연산기가 4개라면 각각의 덧셈을 각각의 연산기에서 수행해서 더 빠르게 연산할 수 있다. 이를 <strong>벡터 연산</strong> 이라 한다.</p>
<p>이러한 벡터 연산은 CPU가 제공하는 벡터 연산 명령으로도 구현될 수 있지만 아까의 연산기 4개를 4개의 스레드로 실행해도 구현할 수 있다.</p>
<p>그런데 이것처럼 계산량이 적은 문제는 오히려 스레드를 생성하는 데에 들어가는 오버헤드로 인해 더 느려질 수 있다. (항상 좋은 것이 아니다!)</p>
<h2 id="heading-7j2r64u1ioygjeuphoyzgcdsspjrpqzrn4k">응답 속도와 처리량</h2>
<p>계산속도에는</p>
<ol>
<li><p>응답 속도</p>
</li>
<li><p>처리량 이라는 두 가지 척도로 계산할 수 있다. 정리하자면</p>
</li>
</ol>
<p><code>응답 속도</code>: 정의: 계산 시작부터 마칠 때까지의 시간 척도: CPU 클록 수, 소비 CPU 인스트럭션 수 -&gt; 시간으로 표현 가능</p>
<p><code>처리량</code>: 정의: 단위 시간당 실행 가능한 계산량</p>
<h3 id="heading-7jwu64us7j2yiouyley5mq">암달의 법칙</h3>
<p><strong>암달의 법칙</strong>이란 일부 처리의 병렬화가 전체적으로 고속화에 어느 정도 영향을 미치는지 예측하는 법칙이다.</p>
<p>수식으로는 1(1−P)+PN</p>
<p>로 나타낸다. (P: 전체 프로그램 중 병렬화 가능한 처리의 비율, N: 병렬화 수)</p>
<p>이 식은 병렬화를 위한 오버헤드가 없는 상태에서의 <strong>성능 향상률</strong>을 나타낸다.</p>
<p>이제 병렬화에 사용되는 오버헤드를 고려한다면</p>
<p>1H+(1−P)+PN 가 될 것이다.( H: 오버헤드의 응답 속도와 순차 실행했을 때 응답 속도의 비율)</p>
<p>이것을 산술적으로 계산하는 게 중요하다기보다는 병렬화 수를 바꿨을 때 얼마나 성능이 향상되는지를 확인할 수 있다는 것이다.</p>
<p>그래프로는</p>
<p><img src="https://i.imgur.com/39PBTNZ.png" alt /></p>
<p>이렇게 나온다.</p>
<p>눈에 띄는 것은 P = 1.0 즉 전체 병렬화 가능한 처리인 경우 병렬화 수 N일 때 N만큼 빨라진다.</p>
<p>그러나 병렬화 불가능한 처리가 있는 경우 고속화 자체는 될지언정 그 배율이 작아진다는 것이다. 한마디로 늘린만큼 좋아지진 않는다.</p>
<h2 id="heading-7j247iqk7yq465t7iwyiougiouyqcdrs5hrokzshle">인스트럭션 레벨 병렬성</h2>
<p><strong>인스트럭션 레벨 병렬성</strong>이란 CPU의 명령어 레벨에서의 병렬화이다.</p>
<p>주로 하드웨어나 컴파일러가 이것을 수행하게 되고 프로그래머가 여기까지 고려하는 것은 루프 전개나 데이터 프리패치 이외에는 흔치 않다.</p>
<p>그러나 대체로 컴파일러가 최적화를 수행하는 경우가 대부분이므로 컴파일러 설계가 아닌 이상 자주 볼 일은 많지 않다.</p>
<p>그런 이유로 <strong>파이프라인 처리</strong> 개념을 위주로 다루고 넘어가자.</p>
<h3 id="heading-7yym7j207zse65287j24ioyymoumra">파이프라인 처리</h3>
<p>CPU의 명령 실행은 다음 5가지로 나눌 수 있다.</p>
<ul>
<li><p>명령 읽기(IF)</p>
</li>
<li><p>명령 해석(ID)</p>
</li>
<li><p>실행(EX)</p>
</li>
<li><p>메모리 엑세스(MEM)</p>
</li>
<li><p>쓰기(WB)</p>
</li>
</ul>
<p><img src="https://i.imgur.com/FoMGn2x.png" alt /></p>
<p>11011(2+3 계산 후 addr[1]과 a 레지스터에 결과를 저장) 수행의 과정을 각각의 과정으로 나눠보면 위와 같이 시각화할 수 있다.</p>
<ol>
<li><p>IF로 메모리상의 명령을 읽어 버킷에 명령을 저장한다.</p>
</li>
<li><p>ID: 버킷 내 명령을 해석한다.</p>
</li>
<li><p>2+3이 계산되고 5로 치환된다.</p>
</li>
<li><p>메모리 엑세스: addr[1]에 결과가 쓰인다.</p>
</li>
<li><p>쓰기: a 레지스터에 결과가 쓰인다.</p>
</li>
</ol>
<p>의 과정인 것이다.</p>
<p>위는 간소화한 설명이지만 실제 파이프라인 처리에서도 각 클록에서 처리를 실행하는 단계 이외에는 아무것도 수행하지 않는다. 자기가 일하는 시간 외에는 놀고 있는 것이다. 이들을 놀지 않게 두려면 여러 인스트럭션을 병렬로 주면 되는 것이다.</p>
<p><img src="https://i.imgur.com/bFu3Hv6.png" alt /></p>
<p>이상적인 상황에서 단순 계산을 하자면 처리량은 파이프라인 수의 배율만큼(위에서는 5개니 5배만큼.) 향상되겠지만 현실에서는 데이터 의존 관계 등의 조건으로 인해 5배까지는 향상되지 않는 경우가 대부분이다.</p>
<p>이렇게 데이터 의존 관계 등으로 인스트럭션 레벨에서 병렬 실행할 수 없는 상태를 <strong>파이프라인 해저드</strong>라고 한다.</p>
<p>따라서 컴파일러를 만들어야한다면 이러한 파이프라인 해저드까지 처리를 해야하는 것이다. 그러나 컴파일러를 여기서 만들지는 않으므로...</p>
<h4 id="heading-7yym7j207zse65287j24io2vtoyggoutnoydmcdsooxrpzg">파이프라인 해저드의 종류</h4>
<p>3가지 종류가 있는데:</p>
<p><code>구조 해저드</code> 하드웨어적으로 병렬 실행할 수 없는 명령을 실행했을 때 발생한다. 예를 들어 하드웨어적으로 동시에 메모리 접근을 할 수 없다면 구조 해저드가 된다.</p>
<p><code>데이터 해저드</code> 위에서 말한 데이터 의존 관계에 의해 병렬 실행이 불가능할 때의 이야기이다. 명령 1의 연산 결과를 명령 2가 사용하는 경우 데이터 해저드이다.</p>
<p><code>제어 해저드</code> 조건 분기가 있을 때 발생한다. 명령 1이 조건 분기이고 이 조건에 따라 명령2의 실행 여부가 결정되는 상황이라면 제어 해저드이다.</p>
<hr />
<h1 id="heading-64z7iucioyymoumroyzgcdrs5hrokwg7lky66as7j2yio2vhoyaloyesq">동시 처리와 병렬 처리의 필요성</h1>
<p>동시 처리와 병렬 처리가 왜 필요하냐는 내용에 대해서이다.</p>
<p>먼저</p>
<h2 id="heading-67or66csioyymoumroyzgcdshlhriqug7zal7iob">병렬 처리와 성능 향상</h2>
<p>병렬 처리는 성능 향상을 위해 필요하다. 그런데 아까 알아본 3가지 병렬성 중 데이터 병렬성과 인스트럭션 병렬성은 컴파일러나 하드웨어 차원에서 수행된다. 그래서 여기서 주로 다루는 것은 태스크 병렬성 처리이다.</p>
<p>결과적으로는 소프트웨어 차원에서도 병렬성(적어도 태스크 병렬성)은 의식해야된다는 내용인데, 하드웨어 기술의 한계로 소프트웨어에서도 이것을 의식해야한다는 것이다.</p>
<h2 id="heading-64z7iucioyymoumroydmcdtlytsmptshlhqs7wg6roe7ikwioqyveuhncdsijgg6rij7kad">동시 처리의 필요성과 계산 경로 수 급증</h2>
<p>동시 처리는 효율적인 계산 리소스 활용, 공평성, 편리성이 필요성이라고 할 수 있다.</p>
<p>효율적인 계산 리소스 활용: IO 대기 상태 중 다른 일 가능 공평성: 여러 작업을 한 쪽에 치우치지 않고 동시 작업 가능(예: 노래 들으면서 웹서핑하기)</p>
<p>그런데 동시 처리가 무조건 좋은 것은 아니다. 동시 처리는 복잡하다.</p>
<p>이유는 동시 처리의 계산 경로가 순차적으로 수행할 때보다 크게 늘어나기 때문이다. 동시 처리 시 계산 조합 수는 $n!$ 이 된다.</p>
<h1 id="heading-c">C</h1>
<p>책에서는 어셈블리를 다루지만 내용이 매우 간단하고 약소한 관계로 C 위주로 설명한다.</p>
<h2 id="heading-pthreads">Pthreads</h2>
<p>C언어에서는 기본 라이브러리에 스레드를 다루는 기능이 제공되지 않아서 외부 라이브러리가 필요하다. Pthreads란 POSIX 표준 인터페이스를 갖춘 스레드 라이브러리를 총칭한다고 한다.</p>
<pre><code class="lang-c"><span class="hljs-comment">//Pthreads를 사용하기 위해 pthreads.h를 읽어야한다.</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt; </span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> NUM_THREADS 10 <span class="hljs-comment">// 생성할 스레드 수</span></span>

<span class="hljs-comment">// 스레드용 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">thread_func</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{ 
    <span class="hljs-keyword">int</span> id = (<span class="hljs-keyword">int</span>)arg; 
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) { 
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"id = %d, i = %d\n"</span>, id, i);
        sleep(<span class="hljs-number">1</span>);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-string">"finished!"</span>; <span class="hljs-comment">// 반환값</span>
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
    <span class="hljs-comment">// 스레드용 핸들러 저장 배열.</span>
    <span class="hljs-keyword">pthread_t</span> v[NUM_THREADS]; 
    <span class="hljs-comment">// 스레드 생성 </span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; NUM_THREADS; i++) {
        <span class="hljs-keyword">if</span> (pthread_create(&amp;v[i], <span class="hljs-literal">NULL</span>, thread_func, (<span class="hljs-keyword">void</span> *)i) != <span class="hljs-number">0</span>) {
            perror(<span class="hljs-string">"pthread_create"</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
        }
    }

    <span class="hljs-comment">// 스레드 종료 대기 </span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; NUM_THREADS; i++) {
        <span class="hljs-keyword">char</span> *ptr;
        <span class="hljs-keyword">if</span> (pthread_join(v[i], (<span class="hljs-keyword">void</span> **)&amp;ptr) == <span class="hljs-number">0</span>) {
            <span class="hljs-built_in">printf</span>(<span class="hljs-string">"msg = %s\n"</span>, ptr);
        } <span class="hljs-keyword">else</span> {
            perror(<span class="hljs-string">"pthread_join"</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h3 id="heading-65su7yoc7lmyioykpougioutna">디태치 스레드</h3>
<p>앞에서는 스레드 종료를 <code>pthread_join</code> 함수에서 기다렸는데 이것으로 종료하지 않으면 메모리 누출이 된다.</p>
<p>그런데 스레드 종료 시 자동으로 스레드용 리소스를 해제하는 방법도 있는데 이것을 <strong>디태치 스레드</strong>라고 한다.</p>
<p>두 가지 방법으로 스레드를 디태치 스레드로 만들 수 있는데</p>
<ol>
<li><p>pthread_create 함수 호출 시 어트리뷰트로 지정</p>
</li>
<li><p>pthread_detach함수 호출</p>
</li>
</ol>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pthread.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;unistd.h&gt;</span></span>

<span class="hljs-comment">// 스레드용 함수</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">thread_func</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *arg)</span> </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++) {
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"i = %d\n"</span>, i);
        sleep(<span class="hljs-number">1</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[])</span> </span>{
    <span class="hljs-comment">// 어트리뷰트 초기화 </span>
    <span class="hljs-keyword">pthread_attr_t</span> attr;
    <span class="hljs-keyword">if</span> (pthread_attr_init(&amp;attr) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_attr_init"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }

    <span class="hljs-comment">// 디태치 스레드로 설정 </span>
    <span class="hljs-keyword">if</span> (pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_attr_setdetachstate"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }

    <span class="hljs-comment">// 어트리뷰터를 지정해 스레드 생성</span>
    <span class="hljs-keyword">pthread_t</span> th;
    <span class="hljs-keyword">if</span> (pthread_create(&amp;th, &amp;attr, thread_func, <span class="hljs-literal">NULL</span>) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_join"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }

    <span class="hljs-comment">// 어트리뷰트 파기</span>
    <span class="hljs-keyword">if</span> (pthread_attr_destroy(&amp;attr) != <span class="hljs-number">0</span>) {
        perror(<span class="hljs-string">"pthread_attr_destroy"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }

    sleep(<span class="hljs-number">7</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h2 id="heading-volatile">volatile 수식자</h2>
<p><code>volatile</code> 수식자를 이용하면 컴파일러의 최적화를 억제한 메모리 접근을 구현할 수 있다.</p>
<p>최적화를 왜 억제하냐 하겠지만 컴파일러가 느린 메모리 접근을 억제하기 위해 레지스터에 복사해서 값을 이용하는 작업이 메모리 값 감시 시에는 장애가 될 수도 있다.</p>
<p>사용법은 간단하다.</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">wait_while_0</span><span class="hljs-params">(<span class="hljs-keyword">int</span> *p)</span> </span>{
    <span class="hljs-keyword">while</span> (*p == <span class="hljs-number">0</span>) {}
}
</code></pre>
<p>에서 <code>int *p</code> 앞에 <code>volatile</code>만 붙여주면 된다.</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">wait_while_0</span><span class="hljs-params">(<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> *p)</span> </span>{
    <span class="hljs-keyword">while</span> (*p == <span class="hljs-number">0</span>) {}
}
</code></pre>
<p>이런 식이다.</p>
<h2 id="heading-7iqk7yodiouplouqqoumroyzgcdtnpkg66mu66qo66as">스택 메모리와 힙 메모리</h2>
<p>이 부분은 <a target="_blank" href="https://maximizemaxwell.com/rust-ownership">러스츠 소유권 이해하기</a>에서 가져왔다.</p>
<h2 id="heading-7iqk7yod">스택</h2>
<p>스택은 후입선출(last in first out)방식으로 값을 처리한다. 입출구가 하나인 통처럼 맨 꼭대기에서 무언가를 집어넣고 꺼내는 것은 가능하지만 중간과 아래에 있는 것은 바로 건드릴 수 없다. 스택에 저장될 데이터는 모두 명확하고 크기가 정해져 있어야 한다. 컴파일 타임에 크기를 알 수 없거나 크기가 변할 수 있는 데이터는 힙에 저장되어야 한다.</p>
<p>함수를 호출하면 호출한 함수에 넘겨준 값과 해당 함수의 로컬 변수들이 스택에 들어가고, 이 데이터들은 함수가 종료될 때 팝된다.</p>
<h2 id="heading-7z6z">힙</h2>
<p>데이터에 힙을 넣을 때 절차는 다음과 같다.</p>
<ol>
<li><p>저장할 공간이 있는지 운영체제에 물어보고</p>
</li>
<li><p>메모리 할당자가 힙 영역 내에서 빈 지점을 찾는다.</p>
</li>
<li><p>아까 발견한 빈 지점을 사용 중이라고 표시하고</p>
</li>
<li><p>그 지점을 가리키는 포인터를 반환한다.</p>
</li>
</ol>
<p>이 전체 과정을 <code>힙 공간 할당</code>이라고 한다.</p>
<p>레스토랑에 비유하자면</p>
<ol>
<li><p>레스토랑에 방문하는 인원수를 수용할 좌석이 있는지 묻는다.</p>
</li>
<li><p>직원은 빈 테이블을 찾아서</p>
</li>
<li><p>테이블이 사용 중임으로 상태를 바꾸고(비유가 이상하긴 한데 다른 손님이 들어왔을 때 여전히 빈 테이블이라고 하지는 않을테니까...)</p>
</li>
<li><p>손님에게 테이블 위치를 안내할 것이다.</p>
</li>
</ol>
<p>라고 할 수 있다.</p>
<p>힙 영역은 포인터가 가리키는 곳을 찾아가는 과정 때문에 느려진다. 힙에 있는 데이터들은 붙어 있는 것이 아니라 이곳 저곳에 떨어져 있는데 이렇게 되면 프로세서가 돌아다니면서 데이터들을 처리해야하기 때문에 느리다.</p>
]]></content:encoded></item><item><title><![CDATA[Docker for MLOps]]></title><description><![CDATA[이번에는 MLOps 입문을 위해 빠르고 간단한 도커 관련 개념 글 하나로 정리하기...같은 개념으로 작성해봤다. 어차피 이미 도커 관련 글이 블로그에 있으니...
Why Docker container?
ML 프로젝트들을 하다보면 특정 라이브러리에 의존하게 되는 경우가 많은데, 배포하려는 경우 환경도 라이브러리 버전, 운영체제 및 기타 환경들을 똑같이 맞춰주지 않으면 프로그램이 정상적으로 동작하지 않는다. 이러한 문제점을 해결하기 위해 가상머신들...]]></description><link>https://maximizemaxwell.com/docker-for-mlops</link><guid isPermaLink="true">https://maximizemaxwell.com/docker-for-mlops</guid><category><![CDATA[mlops]]></category><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Wed, 07 May 2025 11:25:09 GMT</pubDate><content:encoded><![CDATA[<p>이번에는 MLOps 입문을 위해 빠르고 간단한 도커 관련 개념 글 하나로 정리하기...같은 개념으로 작성해봤다. 어차피 이미 도커 관련 글이 블로그에 있으니...</p>
<h1 id="heading-why-docker-container">Why Docker container?</h1>
<p>ML 프로젝트들을 하다보면 특정 라이브러리에 의존하게 되는 경우가 많은데, 배포하려는 경우 환경도 라이브러리 버전, 운영체제 및 기타 환경들을 똑같이 맞춰주지 않으면 프로그램이 정상적으로 동작하지 않는다. 이러한 문제점을 해결하기 위해 가상머신들을 쓰곤 했고 아직도 가상머신이 많이 쓰이고 있다.</p>
<p>그러나 가상머신도 문제점들이 존재하는데...그러나 공통적으로는 가상머신이 너무 무겁기 때문에 일어나는 일들이다. 가상머신은 무겁기 때문에</p>
<ol>
<li><p>옮기기 어렵고</p>
</li>
<li><p>그 자체로 리소스의 낭비가 있다고 볼 수 있다.</p>
</li>
</ol>
<p>환경 분리를 하면서도 이것을 더 가볍게 쓰기 위해 나온 기술이 도커 컨테이너라고 할 수 있겠다.</p>
<p>도커에서 사용하는 컨테이너라는 개념은 실제 컨테이너와 거의 유사하다. 다만 물건이 아니라 애플리케이션을 담고 있을 뿐이다. 그런데 이 컨테이너 안에는 실제 컴퓨터처럼 호스트명, IP 주소, 디스크 드라이브도 들어있고 호스트 머신의 것들과 다르다.</p>
<p>이건 도커가 호스트명과 IP 주소, 파일 시스템까지 가상 리소스를 만들어 컨테이너에 넣어두었기 때문이다.</p>
<h1 id="heading-container-vs-vm">Container vs VM</h1>
<p>컨테이너와 가상 머신의 차이점은 간단하다. 가상 머신은 호스트 머신의 운영체제를 공유하지 않고 별도의 운영체제를 필요로 한다. 그러나 컨테이너는 호스트 머신의 운영체제를 사용한다.</p>
<p>예를 들어 가상머신 A와 B가 있다고 할 때 A와 B는 별도의 운영체제를 필요로 하기 때문에 호스트 머신까지 합해 운영체제가 총 3개 필요하다.</p>
<p>그러나 컨테이너는 호스트 머신의 운영체제만으로 충분하다.(적어도 리눅스 머신에서는)</p>
<p>운영체제는 그 자체로 무거운 프로그램이다. 운영체제가 중복 설치된다는 점이 리소스 측면에서는 전혀 바람직한 상황은 아니다. 컨테이너는 서로 다른 호스트명 IP주소 디스크 드라이브를 가지는 환경들을 생성하면서도 운영체제는 공유하는 방식을 사용한다. 따라서 필요한 리소스가 확연하게 줄어들게 된다.</p>
<p>그래서 컨테이너는 실행도 빠르고 같은 호스트 컴퓨터에도 가상머신보다 더 많은 수를 올릴 수 있다. 일반적으로 5배 더 많이 올릴 수 있다고 한다. 리소스에서 운영체제가 차지하는 비율이 높을수록 수가 더 차이날 것으로 볼 수 있다.</p>
<hr />
<h1 id="heading-docker-image-vs-docker-container">Docker Image vs Docker container</h1>
<p><img src="https://velog.velcdn.com/images/moonblue/post/79ab8669-8d8b-4e2b-b3e7-dfa65a7a3adf/image.jpeg" alt="도커 (Docker)" /></p>
<p>실제로 애플리케이션이 구동되는 공간은 컨테이너이고, 이미지는 컨테이너를 찍어내기 위한 틀이다.</p>
<p>이 둘의 관계는 클래스-인스턴스와도 꽤나 비슷하다. 클래스는 인스턴스를 찍어낼 수 있지만 인스턴스를 생성하지 않고서는 그 자체로 기능을 수행하는 것은 아니다.</p>
<p>마찬가지로 이미지로 컨테이너들을 찍어낼 수 있지만 이미지만으로는 애플리케이션을 돌릴 수 없다.</p>
<p>이미지는 그야말로 컨테이너를 찍어내기 위한 틀이기 때문에 공유가 가능하다. 이런 공유는 도커 허브에서 이루어진다.</p>
<p>아래에서 조금 더 설명하겠지만 실제로 구조가 틀로 무언가를 찍어내는 그런 느낌은 사실 아니기는 하다....</p>
<h2 id="heading-64e7lukioydtouvuoyngcdsooag642uioyvjoyvhouztoq4sa">도커 이미지 좀 더 알아보기</h2>
<h3 id="heading-7j206647kea7j2yioq1royhsa">이미지의 구조</h3>
<p>도커 이미지는 레이어로 구성된다. 도커는 매번 명령을 캐싱해두었다가 이미지를 다시 빌드할 때 캐싱된 결과를 가져와서 사용한다.</p>
<p>이미지를 빌드할 때 도커는 명령문들을 실행하면서 데이터를 독립적으로 저장하게 되는데, 이 저장된 데이터들이 레이어이다. 이런 환경 정보에 대한 레이어들은 읽기 전용이라 임의로 변경할 수 없다.</p>
<p>그리고 도커 컨테이너가 실행되면 읽기 전용 레이어들을 순서대로 쌓은 다음 마지막에 쓰기 가능한 레이어를 추가하는데, 컨테이너에서 발생한 일들은 이 쓰기 가능한 마지막 레이어에 기록되게 된다.</p>
<p><img src="https://i.imgur.com/wTF3jgJ.png" alt /></p>
<p>그림으로는 이러한 식인 것이다.</p>
<h3 id="heading-64e7luk7yym7j28iounlcdtmqjsnkjsoihsnlzrozwg7j6r7isx7zwy6riw">도커파일 더 효율적으로 작성하기</h3>
<p>도커 이미지가 레이어로 저장된다는 것을 알아야 하는 이유가 있는데</p>
<p>한줄요약하자면 도커가 <s>레이어인지 뭔지 그런 걸 가지고 있어서</s> 데이터가 캐싱되니까 매번 명령을 처음부터 실행하는 것이 아니라는 건데, 이걸 알면 도커파일을 더 효율적으로 작성할 수 있다는 거다.</p>
<p>예를 들자면 js앱을 만들고 있는데 <code>app/</code> 디렉토리의 코드에 뭔갈 변경했기 때문에 이미지를 다시 빌드하고자 하는 상황이다. 그런데 도커파일이</p>
<pre><code class="lang-Dockerfile"><span class="hljs-keyword">FROM</span> node

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . /app</span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"server.js"</span>]</span>
</code></pre>
<p>이렇게 되어있다고 에러가 나거나 큰 일이 벌어지는 건 아니지만 의존성에 변화가 없다면 <code>COPY . /app</code>부터 코드의 변화로 변경사항이 생기게 되는데 이러면 의존성을 다시 설치하느라 느리다.</p>
<pre><code class="lang-Dockerfile"><span class="hljs-keyword">FROM</span> node

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> package.json /app</span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . /app</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"server.js"</span>]</span>
</code></pre>
<p>그러니까 이렇게 짜는 것이 더 낫다는 것이다. 의존성은 상대적으로 변화 가능성이 적고 설치 자체도 매우 오래걸리므로 코드 카피를 뒤에 두고, 의존성은 캐싱된 결과 쓰자는 것이다. 그러면 대체로 더 효율적으로 만들 수 있다.</p>
<hr />
<h1 id="heading-64e7lukiouqheugueywta">도커 명령어</h1>
<p>명령어는....일단 이 글에서는 자세하게 다룰 마음이 없다. 일단 help치면 다 나오기도 하고...어차피 구글링의 영역이니. 도커를 공부하면서도 매번 잊어먹기 때문에 항상 찾아보고 있다. 이것은 잘 정리된 블로그를 참고하길 바란다.</p>
<p>내 블로그로 말하자면 <a target="_blank" href="https://maximizemaxwell.com/docker-images-container#heading-14">https://maximizemaxwell.com/docker-images-container#heading-14</a> 를 참고하길 바란다...</p>
<hr />
<h1 id="heading-64e7lukioy7to2proymia">도커 컴포즈</h1>
<p>도커 컴포즈는 여러 개의 컨테이너로 구성된 애플리케이션을 단일 도커 엔진 호스트에서 실행하기 위한 도구이다.</p>
<p>여러 개의 컨테이너를 굴리다보면 이걸 명령어를 일일히 쳐서 하기가 상당히 번거로운데, 명령어에 붙는 그런 부가적인 옵션을 한 파일에 몰아넣은 녀석을 <code>docker-compose.yaml</code>라고 하는 것이다.</p>
<h2 id="heading-64e7lukioy7to2proymicdtjizsnbwg7isk6roe7zwy6riw">도커 컴포즈 파일 설계하기</h2>
<p>오버라이드 파일을 이용한 도커 컴포즈 설계에 대해 소개하는 내용을 조금 갖고 왔다.</p>
<p>여러 가지 설정으로 애플리케이션을 실행하고 싶을 수 있는데 대부분의 경우 컴포즈 파일을 여러 개 둔다고 한다. 그런데 컴포즈 파일을 각각 작성하게 된다면 대부분의 내용이 사실상 중복인데, 수정 시 이 부분이 누락된다거나 하면 문제가 생기므로 유지 보수 측면에서 좋지 않다고 하는 것이다.</p>
<p>도커 컴포즈는 여러 파일을 합쳐 컴포즈 파일을 구성할 수 있는데, 나중에 지정된 파일의 내용이 이전 파일의 내용을 덮어쓰기(오버라이드)한다.</p>
<p>그렇다면</p>
<ul>
<li><p><code>docker-compose.yaml</code>: services(기본 컴포즈 파일. 공통적 정의)</p>
</li>
<li><p><code>docker-compose-dev.yaml</code>: services, networks(개발 환경용)</p>
</li>
<li><p><code>docker-compose-test.yaml</code>: services, networks, volumes(테스트용)</p>
</li>
<li><p><code>docker-compose-uat.yaml</code>: 사용자 인수 테스트 대상</p>
</li>
</ul>
<p>해당 파일에 각각 기능에 필요한 것만 추가하고</p>
<pre><code class="lang-sh">docker container rm -f $(docker container ls -aq)

docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-dev.yml -p numbers-dev up -d
//개발 환경용

docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-test.yml -p numbers-test up -d
//테스트 환경용

docker-compose -f ./numbers/docker-compose.yml -f ./numbers/docker-compose-uat.yml -p numbers-uat up -d
//인수 테스트 환경용
</code></pre>
<p>이런 식으로 각각의 환경을 만들 수 있겠다는 내용이다.</p>
]]></content:encoded></item><item><title><![CDATA[MLFLOW Pipeline and Intro to aws]]></title><description><![CDATA[MLFLOW Pipeline with DVC, MLFLOW, Dagshub
여기서는 MLFLOW, Dagshub, DVC를 이용해 모델을 트랙킹하는 것을 실제로 진행하는 과정을 보여준다. 우선 결론부터

├── data
│   ├── processed
│   │   └── data.csv
│   └── raw
│       ├── data.csv
│       └── data.csv.dvc
├── dvc.lock
├── dvc.yaml
├── ...]]></description><link>https://maximizemaxwell.com/mlflow-pipeline-and-intro-to-aws</link><guid isPermaLink="true">https://maximizemaxwell.com/mlflow-pipeline-and-intro-to-aws</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Thu, 01 May 2025 14:00:56 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-mlflow-pipeline-with-dvc-mlflow-dagshub">MLFLOW Pipeline with DVC, MLFLOW, Dagshub</h1>
<p>여기서는 MLFLOW, Dagshub, DVC를 이용해 모델을 트랙킹하는 것을 실제로 진행하는 과정을 보여준다. 우선 결론부터</p>
<pre><code class="lang-css">
├── <span class="hljs-selector-tag">data</span>
│   ├── <span class="hljs-selector-tag">processed</span>
│   │   └── <span class="hljs-selector-tag">data</span><span class="hljs-selector-class">.csv</span>
│   └── <span class="hljs-selector-tag">raw</span>
│       ├── <span class="hljs-selector-tag">data</span><span class="hljs-selector-class">.csv</span>
│       └── <span class="hljs-selector-tag">data</span><span class="hljs-selector-class">.csv</span><span class="hljs-selector-class">.dvc</span>
├── <span class="hljs-selector-tag">dvc</span><span class="hljs-selector-class">.lock</span>
├── <span class="hljs-selector-tag">dvc</span><span class="hljs-selector-class">.yaml</span>
├── <span class="hljs-selector-tag">models</span>
│   └── <span class="hljs-selector-tag">model</span><span class="hljs-selector-class">.pkl</span>
├── <span class="hljs-selector-tag">params</span><span class="hljs-selector-class">.yaml</span>
├── <span class="hljs-selector-tag">README</span><span class="hljs-selector-class">.md</span>
├── <span class="hljs-selector-tag">requirements</span><span class="hljs-selector-class">.txt</span>
└── <span class="hljs-selector-tag">src</span>
    ├── <span class="hljs-selector-tag">evaluate</span><span class="hljs-selector-class">.py</span>
    ├── __<span class="hljs-selector-tag">init__</span><span class="hljs-selector-class">.py</span>
    ├── <span class="hljs-selector-tag">preprocess</span><span class="hljs-selector-class">.py</span>
    └── <span class="hljs-selector-tag">train</span><span class="hljs-selector-class">.py</span>

6 <span class="hljs-selector-tag">directories</span>, 13 <span class="hljs-selector-tag">files</span>
</code></pre>
<p>이러한 디렉토리 구조의 프로그램을 작성하고 이를 관리한다.</p>
<p>우선 <code>params.yaml</code>에 <code>preprocess</code> 관련 데이터셋과 <code>train</code>의 데이터셋과 파라미터를 집어넣는다.</p>
<p>결과적으로</p>
<pre><code class="lang-yaml"><span class="hljs-attr">preprocess:</span>
  <span class="hljs-attr">input:</span> <span class="hljs-string">data/raw/data.csv</span>
  <span class="hljs-attr">output:</span> <span class="hljs-string">data/processed/data.csv</span>

<span class="hljs-attr">train:</span>
  <span class="hljs-attr">data:</span> <span class="hljs-string">data/raw/data.csv</span>
  <span class="hljs-attr">model:</span> <span class="hljs-string">models/model.pkl</span>
  <span class="hljs-attr">random_state:</span> <span class="hljs-number">42</span>
  <span class="hljs-attr">n_estimators:</span> <span class="hljs-number">100</span>
  <span class="hljs-attr">max_depth:</span> <span class="hljs-number">5</span>
</code></pre>
<p>이러한 형태를 바탕으로 하고, 이 <code>yaml</code> 파일을 읽어와서 이후의 전처리와 학습에 사용하게 된다.</p>
<p>따라서 <a target="_blank" href="http://preprocess.py"><code>preprocess.py</code></a> 파일에서</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> yaml
<span class="hljs-keyword">import</span> os

<span class="hljs-comment">## Load parameters from param.yaml</span>

params=yaml.safe_load(open(<span class="hljs-string">"params.yaml"</span>))[<span class="hljs-string">'preprocess'</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">preprocess</span>(<span class="hljs-params">input_path,output_path</span>):</span>
    data=pd.read_csv(input_path)

    os.makedirs(os.path.dirname(output_path),exist_ok=<span class="hljs-literal">True</span>)
    data.to_csv(output_path,header=<span class="hljs-literal">None</span>,index=<span class="hljs-literal">False</span>)
    print(<span class="hljs-string">f"Preprocesses data saved to <span class="hljs-subst">{output_path}</span>"</span>)

<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    preprocess(params[<span class="hljs-string">"input"</span>],params[<span class="hljs-string">"output"</span>])
</code></pre>
<p>이러한 식으로 <code>yaml</code>을 읽어와서 전처리하게 된다. 이 파일을 실행하면 처음의 전체 디렉토리 구조에서 봤던 <code>preprocessed/data.csv</code>가 생성된다.</p>
<p>데이터의 전처리가 끝났다면 다음은 모델 차례다.</p>
<p>모델은 <code>train</code>과 <code>evaluate</code> 두 과정으로 진행되는데</p>
<p><a target="_blank" href="http://train.py"><code>train.py</code></a>는</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> pickle
<span class="hljs-keyword">from</span> urllib.parse <span class="hljs-keyword">import</span> urlparse

<span class="hljs-keyword">import</span> dagshub
<span class="hljs-keyword">import</span> mlflow
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> yaml
<span class="hljs-keyword">from</span> mlflow.models <span class="hljs-keyword">import</span> infer_signature
<span class="hljs-keyword">from</span> sklearn.ensemble <span class="hljs-keyword">import</span> RandomForestClassifier
<span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> accuracy_score, classification_report, confusion_matrix
<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> GridSearchCV, train_test_split

<span class="hljs-comment"># Initialize dagshub integration</span>
dagshub.init(repo_owner=<span class="hljs-string">"maximizemaxwell"</span>, repo_name=<span class="hljs-string">"MLOps-pipeline"</span>, mlflow=<span class="hljs-literal">True</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hyperparameter_tuning</span>(<span class="hljs-params">X_train, y_train, param_grid</span>):</span>
    rf = RandomForestClassifier()
    grid_search = GridSearchCV(
        estimator=rf, param_grid=param_grid, cv=<span class="hljs-number">3</span>, n_jobs=<span class="hljs-number">-1</span>, verbose=<span class="hljs-number">2</span>
    )
    grid_search.fit(X_train, y_train)
    <span class="hljs-keyword">return</span> grid_search


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">train</span>(<span class="hljs-params">data_path: str, model_path: str</span>):</span>
    <span class="hljs-comment"># Load data</span>
    data = pd.read_csv(data_path, header=<span class="hljs-literal">None</span>)
    X = data.iloc[:, :<span class="hljs-number">-1</span>]
    y = data.iloc[:, <span class="hljs-number">-1</span>]

    <span class="hljs-comment"># Set MLflow URI (redundant with dagshub.init, can skip if you trust dagshub setup)</span>
    mlflow.set_tracking_uri(<span class="hljs-string">"https://dagshub.com/maximizemaxwell/MLOps-pipeline.mlflow"</span>)

    <span class="hljs-keyword">with</span> mlflow.start_run():
        <span class="hljs-comment"># Train-test split</span>
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=<span class="hljs-number">0.2</span>, random_state=<span class="hljs-number">42</span>
        )
        signature = infer_signature(X_train, y_train)

        <span class="hljs-comment"># Define hyperparameter grid</span>
        param_grid = {
            <span class="hljs-string">"n_estimators"</span>: [<span class="hljs-number">100</span>, <span class="hljs-number">200</span>],
            <span class="hljs-string">"max_depth"</span>: [<span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-literal">None</span>],
            <span class="hljs-string">"min_samples_split"</span>: [<span class="hljs-number">2</span>, <span class="hljs-number">5</span>],
            <span class="hljs-string">"min_samples_leaf"</span>: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>],
        }

        <span class="hljs-comment"># Hyperparameter tuning</span>
        grid_search = hyperparameter_tuning(X_train, y_train, param_grid)
        best_model = grid_search.best_estimator_

        <span class="hljs-comment"># Evaluation</span>
        y_pred = best_model.predict(X_test)
        accuracy = accuracy_score(y_test, y_pred)
        print(<span class="hljs-string">f"Accuracy: <span class="hljs-subst">{accuracy}</span>"</span>)

        <span class="hljs-comment"># Logging metrics and parameters</span>
        mlflow.log_metric(<span class="hljs-string">"accuracy"</span>, accuracy)
        <span class="hljs-keyword">for</span> key, val <span class="hljs-keyword">in</span> grid_search.best_params_.items():
            mlflow.log_param(key, val)

        <span class="hljs-comment"># Log confusion matrix and classification report as artifacts</span>
        os.makedirs(<span class="hljs-string">"artifacts"</span>, exist_ok=<span class="hljs-literal">True</span>)
        <span class="hljs-keyword">with</span> open(<span class="hljs-string">"artifacts/confusion_matrix.txt"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> f:
            f.write(str(confusion_matrix(y_test, y_pred)))
        <span class="hljs-keyword">with</span> open(<span class="hljs-string">"artifacts/classification_report.txt"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> f:
            f.write(classification_report(y_test, y_pred))

        mlflow.log_artifact(<span class="hljs-string">"artifacts/confusion_matrix.txt"</span>)
        mlflow.log_artifact(<span class="hljs-string">"artifacts/classification_report.txt"</span>)

        <span class="hljs-comment"># Model logging</span>
        <span class="hljs-keyword">if</span> urlparse(mlflow.get_tracking_uri()).scheme != <span class="hljs-string">"file"</span>:
            mlflow.sklearn.log_model(
                best_model, <span class="hljs-string">"model"</span>, registered_model_name=<span class="hljs-string">"Best_Model"</span>
            )
        <span class="hljs-keyword">else</span>:
            mlflow.sklearn.log_model(best_model, <span class="hljs-string">"model"</span>, signature=signature)

        <span class="hljs-comment"># Save locally</span>
        os.makedirs(os.path.dirname(model_path), exist_ok=<span class="hljs-literal">True</span>)
        <span class="hljs-keyword">with</span> open(model_path, <span class="hljs-string">"wb"</span>) <span class="hljs-keyword">as</span> f:
            pickle.dump(best_model, f)
        print(<span class="hljs-string">f"Model saved to <span class="hljs-subst">{model_path}</span>"</span>)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    params = yaml.safe_load(open(<span class="hljs-string">"../params.yaml"</span>))[<span class="hljs-string">"train"</span>]
    train(params[<span class="hljs-string">"data"</span>], params[<span class="hljs-string">"model"</span>])
</code></pre>
<p>과 같은 식으로 구성하였다. 참고한 유데미 강의의 코드와 dagshub연동 부분이 다르고 경로의 차이가 조금 있다.</p>
<p>이 코드를 실행하게 되면 <code>models/</code>디렉토리에 <code>pkl</code> 파일이 생성됨을 볼 수 있다.</p>
<p>코드를 실행하면</p>
<pre><code class="lang-txt">...
[CV] END max_depth=None, min_samples_leaf=1, min_samples_split=2, n_estimators=200; total time=   1.2s
[CV] END max_depth=None, min_samples_leaf=2, min_samples_split=2, n_estimators=200; total time=   0.7s
[CV] END max_depth=None, min_samples_leaf=2, min_samples_split=5, n_estimators=200; total time=   0.5s
[CV] END max_depth=None, min_samples_leaf=2, min_samples_split=5, n_estimators=200; total time=   0.6s
[CV] END max_depth=None, min_samples_leaf=2, min_samples_split=5, n_estimators=200; total time=   0.5s
Accuracy: 0.7532467532467533
</code></pre>
<p>이런 식으로 Accuracy가 출력되는데, 돌아가서 dagshub의 experiment에 가보면</p>
<p><img src="https://i.imgur.com/kvNIk1Q.png" alt /></p>
<p>이런 식으로 똑같이 기록된다.</p>
<p>그러면 다음으로 <a target="_blank" href="http://evaluate.py"><code>evaluate.py</code></a>는</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> pickle
<span class="hljs-keyword">from</span> urllib.parse <span class="hljs-keyword">import</span> urlparse

<span class="hljs-keyword">import</span> dagshub
<span class="hljs-keyword">import</span> mlflow
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> yaml
<span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> accuracy_score

<span class="hljs-comment"># Initialize dagshub integration</span>
dagshub.init(repo_owner=<span class="hljs-string">"maximizemaxwell"</span>, repo_name=<span class="hljs-string">"MLOps-pipeline"</span>, mlflow=<span class="hljs-literal">True</span>)
<span class="hljs-comment"># Get absolute path to params.yaml (in project root)</span>
params_path = os.path.join(os.path.dirname(__file__), <span class="hljs-string">".."</span>, <span class="hljs-string">"params.yaml"</span>)
params = yaml.safe_load(open(params_path))[<span class="hljs-string">"train"</span>]

<span class="hljs-comment"># Full data/model path</span>
data_path = os.path.join(os.path.dirname(__file__), <span class="hljs-string">".."</span>, params[<span class="hljs-string">"data"</span>])
model_path = os.path.join(os.path.dirname(__file__), <span class="hljs-string">".."</span>, params[<span class="hljs-string">"model"</span>])


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">evaluate</span>(<span class="hljs-params">data_path, model_path</span>):</span>
    <span class="hljs-comment"># Column names (based on PIMA dataset)</span>
    cols = [
        <span class="hljs-string">"Pregnancies"</span>,
        <span class="hljs-string">"Glucose"</span>,
        <span class="hljs-string">"BloodPressure"</span>,
        <span class="hljs-string">"SkinThickness"</span>,
        <span class="hljs-string">"Insulin"</span>,
        <span class="hljs-string">"BMI"</span>,
        <span class="hljs-string">"DiabetesPedigreeFunction"</span>,
        <span class="hljs-string">"Age"</span>,
        <span class="hljs-string">"Outcome"</span>,
    ]
    data = pd.read_csv(data_path, names=cols, header=<span class="hljs-literal">None</span>)
    X = data.drop(columns=[<span class="hljs-string">"Outcome"</span>])
    y = data[<span class="hljs-string">"Outcome"</span>]

    <span class="hljs-comment"># Use your own DagsHub tracking URI</span>
    mlflow.set_tracking_uri(<span class="hljs-string">"https://dagshub.com/maximizemaxwell/MLOps-pipeline.mlflow"</span>)

    model = pickle.load(open(model_path, <span class="hljs-string">"rb"</span>))

    predictions = model.predict(X)
    accuracy = accuracy_score(y, predictions)

    <span class="hljs-comment"># Log to MLflow</span>
    mlflow.log_metric(<span class="hljs-string">"eval_accuracy"</span>, accuracy)
    print(<span class="hljs-string">f"Model accuracy: <span class="hljs-subst">{accuracy:<span class="hljs-number">.4</span>f}</span>"</span>)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    evaluate(data_path, model_path)
</code></pre>
<p>이런 식으로 쓸 수 있겠고,</p>
<pre><code class="lang-txt">  warnings.warn(
Model accuracy: 0.9505
🏃 View run clumsy-goose-362 at: https://dagshub.com/maximizemaxwell/MLOps-pipeline.mlflow/#/experiments/0/runs/ddc3b5707ec448ec8c465d41a0956487
🧪 View experiment at: https://dagshub.com/maximizemaxwell/MLOps-pipeline.mlflow/#/experiments/0
</code></pre>
<p>실행결과를 확인할 수 있다.</p>
<p>다시 dagshub으로 돌아가서</p>
<p><img src="https://i.imgur.com/jJa9PnB.png" alt /></p>
<p>evaluate 실험도 여기서 accuracy를 확인할 수 있다.</p>
<h1 id="heading-mlflow-with-aws">MLFLOW with AWS</h1>
<p>지금까지는 실험 추적과 버전 관리를 dagshub에 올렸으나, aws 클라우드에 데이터사이언스 프로젝트를 호스팅하여 똑같이 실험과정을 진행하고 싶을 수 있다.</p>
<p>실험 추적은 <code>S3 버킷</code>에 기록되고, 이것은 다시 <code>aws EC2</code> 인스턴스로 들어간다. 따라서 아래와 같은 <a target="_blank" href="http://app.py"><code>app.py</code></a>를 작성할 수 있다.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> mean_squared_error, mean_absolute_error, r2_score
<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split
<span class="hljs-keyword">from</span> sklearn.linear_model <span class="hljs-keyword">import</span> ElasticNet
<span class="hljs-keyword">from</span> urllib.parse <span class="hljs-keyword">import</span> urlparse
<span class="hljs-keyword">import</span> mlflow
<span class="hljs-keyword">from</span> mlflow.models.signature <span class="hljs-keyword">import</span> infer_signature
<span class="hljs-keyword">import</span> mlflow.sklearn 

<span class="hljs-keyword">import</span> logging

<span class="hljs-keyword">import</span> os

os.environ[<span class="hljs-string">"MLFLOW_TRACKING_URI"</span>]=<span class="hljs-string">"http://ec2-54-158-152-207.compute-1.amazonaws.com:5000/"</span>

logging.basicConfig(level=logging.WARN)
logger = logging.getLogger(__name__)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">eval_metrics</span>(<span class="hljs-params">actual,pred</span>):</span>
    rmse=np.sqrt(mean_squared_error(actual,pred))
    mae=mean_absolute_error(actual,pred)
    r2=r2_score(actual,pred)
    <span class="hljs-keyword">return</span> rmse,mae,r2


<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:

    <span class="hljs-comment">## Data Ingestion-Reading the dataset-- wine quality dataset</span>

    csv_url=(
        <span class="hljs-string">"https://raw.githubusercontent.com/mlflow/mlflow/master/tests/datasets/winequality-red.csv"</span>
    )


    <span class="hljs-keyword">try</span>:
        data=pd.read_csv(csv_url,sep=<span class="hljs-string">";"</span>)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logger.exception(<span class="hljs-string">"Unable to download the data"</span>)

    <span class="hljs-comment"># Split the data in train and test</span>

    train,test=train_test_split(data)

    train_x = train.drop([<span class="hljs-string">"quality"</span>], axis=<span class="hljs-number">1</span>)
    test_x = test.drop([<span class="hljs-string">"quality"</span>], axis=<span class="hljs-number">1</span>)
    train_y = train[[<span class="hljs-string">"quality"</span>]]
    test_y = test[[<span class="hljs-string">"quality"</span>]]

    alpha = float(sys.argv[<span class="hljs-number">1</span>]) <span class="hljs-keyword">if</span> len(sys.argv) &gt; <span class="hljs-number">1</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0.5</span>
    l1_ratio = float(sys.argv[<span class="hljs-number">2</span>]) <span class="hljs-keyword">if</span> len(sys.argv) &gt; <span class="hljs-number">2</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0.5</span>

    <span class="hljs-keyword">with</span> mlflow.start_run():
        lr=ElasticNet(alpha=alpha,l1_ratio=l1_ratio,random_state=<span class="hljs-number">42</span>)
        lr.fit(train_x,train_y)

        predicted_qualities = lr.predict(test_x)
        (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)

        print(<span class="hljs-string">"Elasticnet model (alpha={:f}, l1_ratio={:f}):"</span>.format(alpha, l1_ratio))
        print(<span class="hljs-string">"  RMSE: %s"</span> % rmse)
        print(<span class="hljs-string">"  MAE: %s"</span> % mae)
        print(<span class="hljs-string">"  R2: %s"</span> % r2)

        mlflow.log_param(<span class="hljs-string">"alpha"</span>, alpha)
        mlflow.log_param(<span class="hljs-string">"l1_ratio"</span>, l1_ratio)
        mlflow.log_metric(<span class="hljs-string">"rmse"</span>, rmse)
        mlflow.log_metric(<span class="hljs-string">"r2"</span>, r2)
        mlflow.log_metric(<span class="hljs-string">"mae"</span>, mae)


        <span class="hljs-comment">## For the remote server AWS we need to do the setup</span>

        remote_server_uri=<span class="hljs-string">"http://ec2-54-158-152-207.compute-1.amazonaws.com:5000/"</span>
        mlflow.set_tracking_uri(remote_server_uri)

        tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme


        <span class="hljs-keyword">if</span> tracking_url_type_store!=<span class="hljs-string">"file"</span>:
            mlflow.sklearn.log_model(
                lr,<span class="hljs-string">"model"</span>,registered_model_name=<span class="hljs-string">"ElasticnetWineModel"</span>
            )
        <span class="hljs-keyword">else</span>:
            mlflow.sklearn.log_model(lr,<span class="hljs-string">"model"</span>)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Intro to Kubernetes]]></title><description><![CDATA[수동 배포의 문제점들
컨테이너가 충돌하는 이슈로 프로그램이 crashed될 수 있다. 이런 경우에 새 컨테이너를 다시 실행해야 하는데, 큰 애플리케이션에서 이런 작업은 상당히 불편하다. 그리고 트래픽이 많은 경우 하나의 기능을 수행하는 컨테이너를 하나만 쓰는 것이 아니라 컨테이너의 수를 조정하고 컨테이너들에 트래픽을 분산시킬 필요가 있다.
What is Kubernetes?
위에서 언급한 문제들은 aws의 auto-scaling, load-ba...]]></description><link>https://maximizemaxwell.com/intro-to-kubernetes</link><guid isPermaLink="true">https://maximizemaxwell.com/intro-to-kubernetes</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Mon, 21 Apr 2025 12:22:53 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-7iiy64ziouwso2proydmcdrrljsojzsojdrk6q">수동 배포의 문제점들</h1>
<p>컨테이너가 충돌하는 이슈로 프로그램이 crashed될 수 있다. 이런 경우에 새 컨테이너를 다시 실행해야 하는데, 큰 애플리케이션에서 이런 작업은 상당히 불편하다. 그리고 트래픽이 많은 경우 하나의 기능을 수행하는 컨테이너를 하나만 쓰는 것이 아니라 컨테이너의 수를 조정하고 컨테이너들에 트래픽을 분산시킬 필요가 있다.</p>
<h1 id="heading-what-is-kubernetes">What is Kubernetes?</h1>
<p>위에서 언급한 문제들은 aws의 auto-scaling, load-balancer등을 이용해 자동으로 처리할 수 있었다. 이러한 설정들을 적용하면 aws내에서는 문제를 해결할 수 있으나, 다른 클라우드 서비스에서는 작동하지 않을 수 있다. 예컨대 Google Cloud같은 곳에서는 또 다른 설정을 적용해야 할 수 있다.</p>
<p>물론 특정 클라우드 서비스만 사용하겠다면 큰 문제가 없을 수 있지만, 클라우드 플랫폼에 종속되지 않고 자동 배포를 도와줄 오픈소스 시스템이 있다. 이게 <code>쿠버네티스</code>이다.</p>
<p><code>쿠버네티스</code>도 자동 배포, 스케일링, 로드 밸런싱, 관리에 필요한 기능들이 존재한다.</p>
<p>아까 클라우드 플랫폼과 쿠버네티스를 비교했지만 쿠버네티스는 클라우드 플랫폼들의 대체재가 아니다. 자동 배포와 관리에 필요한 기능들과 개념들이 모여있는 오픈 소스 프로그램일 뿐이지, 클라우드 프로바이더는 따로 제공되어야 한다. 다만 aws든 다른 클라우드 플랫폼이든, 온프레미스 환경이든 쿠버네티스라는 공통적 시스템을 이용하여 관리할 수 있다는 데 의의가 있다.</p>
<p>쿠버네티스는 도커의 대체재도 아니다. 쿠버네티스를 사용해 도커를 관리하고 자동 배포할 수 있는 것이다. 그래서 요약하자면 쿠버네티스는 여러 머신에서 사용할 수 있는 <code>docker-compose</code>같은 개념이라고 볼 수 있는 것이다.</p>
<h1 id="heading-kubernetes">Kubernetes 아키텍처</h1>
<h2 id="heading-pod">포드(Pod)</h2>
<p>쿠버네티스는 여러 가지 개념과 구성 요소로 되어있는데, 우선 <code>포드</code>라는 게 있다. <code>포드</code>는 하나 또는 그 이상의 애플리케이션 컨테이너들의 그룹을 나타내는 쿠버네티스의 추상적 개념이다. 쿠버네티스 세계에서는 이 <code>포드</code>가 최소 단위가 된다. <code>포드</code>는 컨테이너의 자원들을 공유할 수 있는데,</p>
<ul>
<li><p>공유 스토리지(ex: 볼륨)</p>
</li>
<li><p>네트워킹(ex: 클러스터 IP 주소)</p>
</li>
<li><p>컨테이너가 동작하는 방식에 대한 정보(ex: 컨테이너 이미지 버전, 사용할 특정 포트) 와 같은 자원들을 공유할 수 있다고 한다.</p>
</li>
</ul>
<h2 id="heading-workder-node">워커 노드(Workder Node)</h2>
<p>포드에서 컨테이너 그룹을 나타냈다. 그럼 이 컨테이너들은 어디에서 실제로 실행되는가? 이에 대한 대답이 <code>워커 노드</code>라고 할 수 있다. 이렇게 보면 <code>워커 노드</code>는 일종의 인스턴스와 같은 개념이라고 할 수 있다.</p>
<h2 id="heading-proxy">프록시(Proxy)</h2>
<p>또 다른 구성 요소로 <code>프록시</code>가 있다. 이는 워커 노드의 포드 네트워크 트래픽의 제어를 설정하는 도구이다. 그러니까, <code>프록시</code>는 포드가 인터넷에 연결할 수 있는지의 여부와 외부에서 컨테이너를 어떻게 접근할 수 있는지와 같은 세팅을 제어할 수 있게 해준다고 보면 된다.</p>
<h2 id="heading-master-node">마스터 노드(Master Node)</h2>
<p>쿠버네티스는 트래픽에 따라 컨테이너의 수를 조정한다. 트래픽에 따라 포드와 컨테이너를 만들고 실행 가능한 워커 노드에서 실행시키는 작업을 해야한다. 반대로 필요하지 않은 경우에는 줄이고 교체하는 작업도 필요하다.</p>
<p>이런 작업은 <code>마스터 노드</code>에 의해 이루어진다. 정확히는 <code>마스터 노드</code>가 호출하는 <code>컨트롤 플레인(Control Plane)</code>에 일어난다.</p>
<p>이런 작업을 <code>마스터 노드</code>와 <code>컨트롤 플레인</code>이 수행한다면 개발자는 뭘 하는가?</p>
<p>개발자는 원하는 종료 상태를 설정하면 쿠버네티스가 이를 고려하여 관리한다.</p>
<p>여기서 <code>컨트롤 플레인</code>이 실제로 무엇이냐 하면 <code>마스터 노드</code>에서 실행하는 다양한 서비스와 도구 모음이라고 할 수 있다.</p>
<h2 id="heading-cluster">클러스터(Cluster)</h2>
<p>이렇게 마스터 노드와 컨트롤 플레인, 워커 노드들과 포드들이 구성된 단위를 <code>클러스터</code>라고 한다. AWS에서 설명하기론 컨테이너화된 애플리케이션을 실행하는 컴퓨팅 노드 또는 작업자 머신이라고 표현하고 있다.</p>
<p><img src="https://i.imgur.com/8XnyJix.png" alt /></p>
<p>위는 AWS에서 클러스터 연결에 대해 표현하고 있는 그림이다.</p>
<h1 id="heading-node">Node 좀 더 살펴보기</h1>
<p><img src="https://i.imgur.com/oqQpwyA.png" alt /></p>
]]></content:encoded></item><item><title><![CDATA[[MLOps] DVC&Dagshub]]></title><description><![CDATA[DVC
DVC는 Data Version Control의 약자로 모델이나 데이터 버전 관리에 사용되는 툴이다.
설치는
pip install dvc

로 진행하고
git과 마찬가지로
dvc init

을 통해 dvc가 버전을 관리할 수 있도록 한다.
dvc init을 진행해주면 .dvc 디렉토리가 생기고 tmp, .gitignore, config 등의 파일과 디렉토리가 생기는 것을 확인할 수 있다.
dvc의 사용법은 git과 거의 같은데
dvc a...]]></description><link>https://maximizemaxwell.com/dvc-dagshub</link><guid isPermaLink="true">https://maximizemaxwell.com/dvc-dagshub</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Thu, 10 Apr 2025 14:36:48 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-dvc">DVC</h1>
<p>DVC는 Data Version Control의 약자로 모델이나 데이터 버전 관리에 사용되는 툴이다.</p>
<p>설치는</p>
<pre><code class="lang-python">pip install dvc
</code></pre>
<p>로 진행하고</p>
<p>git과 마찬가지로</p>
<pre><code class="lang-sh">dvc init
</code></pre>
<p>을 통해 dvc가 버전을 관리할 수 있도록 한다.</p>
<p><code>dvc init</code>을 진행해주면 <code>.dvc</code> 디렉토리가 생기고 <code>tmp</code>, <code>.gitignore</code>, <code>config</code> 등의 파일과 디렉토리가 생기는 것을 확인할 수 있다.</p>
<p>dvc의 사용법은 git과 거의 같은데</p>
<pre><code class="lang-sh">dvc add <span class="hljs-string">'data/data.txt'</span>
</code></pre>
<p>을 통해 추적할 파일을 추가할 수 있다. 그런데 <code>dvc add</code>를 수행해주게 되면 <code>.dvc</code>파일이 생성되게 되는데, 위의 경우 <code>data/data.txt.dvc</code>가 생성된다.</p>
<p>내부를 열어보면</p>
<pre><code class="lang-plaintext">outs:
- md5: 0b1ebceb88b6324cafbda936c7500e37
  size: 6
  hash: md5
  path: data.txt
</code></pre>
<p>와 같이 생겼다. md5는 해시 키인데, 이 해시 값은 특정 데이터에 매핑된다. 그러니까, 데이터를 변경하고 다시 dvc를 통해 <code>.dvc</code> 파일을 생성해주면 이 해시 값은 변경된다.</p>
<p>이 해시 값들은 루트 디렉토리에 있는 <code>.dvc/cache</code> 안에서도 찾아볼 수 있다.</p>
<p><code>data/data.txt</code> 파일의 내용을 변경한 후 다시 <code>dvc add</code> 커맨드를 입력해보았다. 그 결과</p>
<pre><code class="lang-plaintext">outs:
- md5: cf1044dbd9d6a2055b7a1f0356a31399
  size: 31
  hash: md5
  path: data.txt
</code></pre>
<p>파일이 변경되었기 때문에 해시 값이 달라진 것을 볼 수 있다. 이렇게 달라지는 해시 값들을 기반으로 파일을 트랙킹하고 있다고 볼 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[[Docker] Laravel&php 도커화 프로젝트]]></title><description><![CDATA[타켓 설정
이번에는 PHP, MYSQL, Nginx 컨테이너로 구성된 웹앱을 도커로 구축하면서 복잡한 설정에서 도커를 이용해 이들을 어떻게 간단히 할 수 있는지 설명한다,.
Nginx 컨테이너
우선 docker-compose.yaml을 만들어 두고 Nginx 부분은
version: "3.8"

services:
  server:
    image: "nginx:stable-alpine"
    ports:
      - "8000:80"
   ...]]></description><link>https://maximizemaxwell.com/docker-laravelandphp</link><guid isPermaLink="true">https://maximizemaxwell.com/docker-laravelandphp</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Mon, 31 Mar 2025 14:54:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743432841743/ad2918a4-2b67-4523-a00d-43ef32335507.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-7yoa7lytioyepoyglq">타켓 설정</h1>
<p>이번에는 PHP, MYSQL, Nginx 컨테이너로 구성된 웹앱을 도커로 구축하면서 복잡한 설정에서 도커를 이용해 이들을 어떻게 간단히 할 수 있는지 설명한다,.</p>
<h2 id="heading-nginx">Nginx 컨테이너</h2>
<p>우선 <code>docker-compose.yaml</code>을 만들어 두고 Nginx 부분은</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">server:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">"nginx:stable-alpine"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8000:80"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/nginx.conf:/etc/nginx/nginx.conf:ro</span>
</code></pre>
<p>와 같이 작성한다. 도커허브에 있는 이미지를 사용하고, conf 파일은 읽기 전용으로 해놨다.</p>
<p>Nginx 설정이 여기서 크게 중요치는 않으나</p>
<pre><code class="lang-plaintext">server {
  listen 80;
  index index.php index.html;
  server_name localhost;
  root /var/www/html/public;
  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }
  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass php:3000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
  }
}
</code></pre>
<p>와 같이 적어주었다.</p>
<h2 id="heading-php">PHP 컨테이너</h2>
<p>php에 대한 도커파일을 <code>dockerfiles</code> 디렉토리에 넣고, 이름을 <code>php.dockerfile</code>이라 하자. (즉, <code>dockerfiles/php.dockerfile</code>)</p>
<p><code>php.dockerfile</code>는</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> php:<span class="hljs-number">7.4</span>-fpm-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /var/www/html</span>

<span class="hljs-keyword">RUN</span><span class="bash"> docker-php-ext-install pdo pdo_mysql</span>
</code></pre>
<p>아래와 같이 해주었고,</p>
<p>그래서 이제 <code>docker-compose.yaml</code>에서 php부분을 작성해볼 차례인데</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">php:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./dockerfiles</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">php.dockerfile</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./src:/var/www/html:delegated</span>
</code></pre>
<p><code>dockerfiles</code> 디렉토리 내부에 있는 <code>php.dockerfile</code>이므로 빌드 부분은 위와 같이 적어줄 수 있겠고, <code>src</code>디렉토리를 <code>/var/www/html</code>로 마운트하도록 지정해준다.</p>
<p>여기서 <code>delegated</code>라는 것이 나오는데, 이건 성능 향상을 위해 쓰일 수 있는 것이다.</p>
<p>이건 컨테이너가 일부 데이터를 기록해야 하는 경우에 그 결과를 호스트 머신에 즉시 반영하는 게 아니라 배치로 기본 처리한다. 일종의 최적화 옵션으로 볼 수 있다.</p>
<p>그런데 위의 <code>nginx.conf</code> 파일에서 php가 3000번 포트를 쓰기를 기대하고 있는데 실제 php 이미지를 검색해보면 9000번 포트를 노출하고 있음을 찾을 수 있다. 그래서 9000번 포트를 3000번으로 매핑해주어야한다.</p>
<p>그래서</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">php:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./dockerfiles</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">php.dockerfile</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./src:/var/www/html:delegated</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:9000"</span>
</code></pre>
<p>이렇게 써주면 될 것 같지만, 사실 로컬호스트를 통해 통신하는 게 아니라 컨테이너끼리의 통신을 이루고자 하는 것이므로 <code>docker-compose.yaml</code>에 포트를 저렇게 기입해 둘 것이 아니라</p>
<p><code>nginx.conf</code>에서</p>
<pre><code class="lang-plaintext">server {
  listen 80;
  index index.php index.html;
  server_name localhost;
  root /var/www/html/public;
  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }
  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass php:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
  }
}
</code></pre>
<p>포트를 9000번으로 수정해주는 것이 옳다.</p>
<h2 id="heading-mysql">MySQL 컨테이너</h2>
<p>MySQL의 경우 간단하다.</p>
<p><code>/env/mysql.env</code>를 만들어주고 필요한 정보를 넣은 후</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">mysql:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:5.7</span>
    <span class="hljs-attr">env_file:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./env/mysql.env</span>
</code></pre>
<p>로 해주면 그만이다.</p>
<h2 id="heading-composer">Composer 유틸리티 컨테이너</h2>
<p><code>Laravel</code>환경 구축을 위해 Composer 유틸리티 컨테이너도 만들어 볼 것이다.</p>
<p>이것도 간단하게 <code>/dockerfiles/composer.dockerfile</code>에</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> composer:latest

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /var/www/html</span>

<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [ <span class="hljs-string">"composer"</span>, <span class="hljs-string">"--ignore-platform-reqs"</span> ]</span>
</code></pre>
<p>이것들을 적어준 후</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">composer:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./dockerfiles</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">composer.dockerfile</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./src:/var/www/html</span>
</code></pre>
<p>로 유틸리티 컨테이너를 만들어줄 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[Docker-Utility Containers]]></title><description><![CDATA[유틸리티 컨테이너
유틸리티 컨테이너란?
유틸리티 컨테이너는 애플리케이션이 담긴 컨테이너가 아니라 특정 환경만 포함하는 컨테이너를 의미한다. 예를 들면 NodeJS환경이나 PHP환경같은 것을 말하는 것이다.
왜 사용하는가?
기본적으로 호스트 머신에 환경을 구축하지 않고 이를 도커를 통해 사용하기 위해서 이용한다.

컨테이너에서 명령을 실행하는 다양한 방법
docker run node를 터미널에서 입력하면 컨테이너가 그대로 죽는다. interact...]]></description><link>https://maximizemaxwell.com/docker-utility-containers</link><guid isPermaLink="true">https://maximizemaxwell.com/docker-utility-containers</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Mon, 24 Mar 2025 14:58:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742828254515/c9e4af06-f375-45e7-a45b-340383d5e3f8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-7jyg7yu466as7yuwioy7qo2fjoydtoueia">유틸리티 컨테이너</h1>
<h2 id="heading-7jyg7yu466as7yuwioy7qo2fjoydtoueiouegd8">유틸리티 컨테이너란?</h2>
<p>유틸리티 컨테이너는 애플리케이션이 담긴 컨테이너가 아니라 특정 환경만 포함하는 컨테이너를 의미한다. 예를 들면 NodeJS환경이나 PHP환경같은 것을 말하는 것이다.</p>
<h2 id="heading-7jmcioycroyaqe2vmoukloqwgd8">왜 사용하는가?</h2>
<p>기본적으로 호스트 머신에 환경을 구축하지 않고 이를 도커를 통해 사용하기 위해서 이용한다.</p>
<hr />
<h1 id="heading-7luo7ywm7j2064si7jeq7isciouqheugueydhcdsi6ttlontlzjripqg64uk7jar7zwciouwqeuylq">컨테이너에서 명령을 실행하는 다양한 방법</h1>
<p><code>docker run node</code>를 터미널에서 입력하면 컨테이너가 그대로 죽는다. interactive모드로 실행해야 정상 작동하기 때문이다.</p>
<p><code>docker run -it node</code> 로 실행하면 터미널에서 interactive모드로 노드 컨테이너를 돌려서 실행할 수 있지만 우리가 원하는 건 detached모드에서(백그라운드에서 실행하면서) 컨테이너가 죽지 않게 하는 것이다. 이것은 간단하게</p>
<pre><code class="lang-bash">docker run -it -d node
</code></pre>
<p><code>-d</code>옵션 하나만 더 붙여줘도 가능하다.</p>
<p>이를 실행하고 <code>docker ps</code>를 통해 실행중인 컨테이너를 확인하면</p>
<pre><code class="lang-bash">docker ps                                                                  ─╯
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
4551887165c2   node      <span class="hljs-string">"docker-entrypoint.s…"</span>   4 seconds ago   Up 3 seconds             eager_dubinsky
</code></pre>
<p>이런식으로 컨테이너가 생성된 것을 볼 수 있다.</p>
<p>그런데 이제 위에 만들어둔 컨테이너 내에 <code>npm init</code>이든, 뭐든 컨테이너 내부에 명령을 실행하고 싶을 수 있다. 그런데 이제 interactive모드에서 직접 터미널에서 바로 명령을 주는 것이 아니라, 저렇게 백그라운드에서 둔 상태로.</p>
<p>이를 위해 <code>docker exec</code> 명령어를 쓸 수 있다.</p>
<p><code>docker exec</code>는 아까 말한 바와 같이 컨테이너에 특정 명령어를 실행할 수 있게 한다.</p>
<p>아까 <code>docker run node</code>는 <code>-it</code> 옵션을 붙여서 실행해야했으므로 마찬가지로</p>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it node_container_name npm init
</code></pre>
<p>을 실행하면 패키지에 대해 입력을 받는 부분들이 나오고 컨테이너에 패키지가 설치된다. 이러면 호스트 머신에 이들을 설치하지 않아도 환경을 만들 수 있다.</p>
<hr />
<h2 id="heading-7jyg7yu466as7yuwioy7qo2fjoydtoueicdqtazstpxtlzjqula">유틸리티 컨테이너 구축하기</h2>
<p>만약 아래와 같은 도커파일을 작성했을 때</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
</code></pre>
<p>아래에 <code>CMB npm init</code>을 한 줄 더 적어줄 수도 있지만 <code>init</code>명령만 실행하는 것보다 유연하게 만들고자 한다.</p>
<p>그래서 도커파일은 위 상태 그대로 놔두고 이미지를 빌드해준 후 아까 했던대로 <code>npm init</code>명령어를 컨테이너에서 실행하고자 한다.</p>
<p>그런데 여기서 명령어는 컨테이너 내부에서만 돌기 때문에 이를 호스트 머신에서 관측하기 위해 미러링을 하려고 하는데, 이건 바인드 마운트를 이용해줄 수 있다.</p>
<pre><code class="lang-bash">docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm init
</code></pre>
<p>와 같이 실행해주면 호스트 머신에도 package.json(컨테이너의 npm init 옵션에서 사용한 것들이) 생성된다.</p>
<hr />
<h2 id="heading-entrypoint">ENTRYPOINT</h2>
<p><code>ENTRYPOINT</code>는 <code>CMD</code>와 외관상 유사해보이는 명령인데, 작동 방식은 조금 다르다.</p>
<p><code>CMD</code>명령의 경우 <code>CMD</code>를 도커파일에 지정해놓아도 위에서 실행한 <code>npm install</code>명령을 실행해주면 도커파일에 있는 것을 덮어씌우고 진행되지만,</p>
<p><code>ENTRYPOINT</code>의 경우 덮어씌우는 것이 아니라 그 뒤에 들어가게 된다. 그러니까</p>
<pre><code class="lang-Dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"npm"</span>]</span>
</code></pre>
<p>의 도커파일로 이미지를 빌드했다면</p>
<pre><code class="lang-bash">docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm init
</code></pre>
<p>이렇게 <code>init</code>만 붙여도 되는 것이다.</p>
<p>마찬가지로</p>
<pre><code class="lang-bash">docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm install express --save
</code></pre>
<p>도 같은 방식으로 실행해줄 수 있다.</p>
<hr />
<h2 id="heading-docker-compose">Docker Compose 사용하기</h2>
<p>도커파일이 하나이지만 도커 컴포즈를 여기서도 유용하게 사용할 수 있다.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">npm:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">./</span>
    <span class="hljs-attr">stdin_open:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">tty:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./:/app</span>
</code></pre>
<p>아까 엔트리포인트를 사용한 도커파일을 둔 디렉토리에 이런 식으로 도커 컴포즈 파일을 작성했을 때</p>
<p>단순히 <code>docker compose up</code>을 하게 되면 <code>ENTRYPOINT ["npm"]</code>밖에 없기 때문에 <code>npm init</code>이라든가 그 이후의 명령은 바로 실행되지 않는다.</p>
<p>그래서 사용할 수 있는 것이 <code>docker compose run</code>인데,</p>
<pre><code class="lang-bash">docker compose run npm init
</code></pre>
<p>을 실행해주면 우리가 원하는대로 <code>npm init</code>을 돌려줄 수 있다. 여기서 <code>npm</code>은 컴포즈 파일의 서비스 이름에서 온 것이고, <code>init</code>은 엔트리 포인트 이후의 명령이다.</p>
<p>그런데 <code>docker compose run</code>은 <code>docker compose up</code>과 다르게 <code>docker compose down</code>에 대응하는 것이 없다. 기본적으로 컨테이너가 시작되어 명령이 완료되면 종료되지만, 자동으로 제거되지는 않는다.</p>
<p><code>docker compose run --rm</code>으로 <code>--rm</code>을 달아주면 종료될 시 자동으로 제거되게 할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[FLASK for MLOps]]></title><description><![CDATA[대단한 뜻은 없고 MLOps 강의에 있는 플라스크 부분을 가져온 것 뿐이다
Flask
Flask 시작하기
flask를 돌리기 위해 우선 flask를 설치해주어야한다.
from flask import Flask

app = Flask(__name__)


@app.route("/")
def welcome():
    return "Welcome to this Flask course"


@app.route("/index")
def welcome_...]]></description><link>https://maximizemaxwell.com/flask-for-mlops</link><guid isPermaLink="true">https://maximizemaxwell.com/flask-for-mlops</guid><dc:creator><![CDATA[Eunsoo Max Eun]]></dc:creator><pubDate>Wed, 19 Mar 2025 14:51:22 GMT</pubDate><content:encoded><![CDATA[<p><s>대단한 뜻은 없고 MLOps 강의에 있는 플라스크 부분을 가져온 것 뿐이다</s></p>
<h1 id="heading-flask">Flask</h1>
<h2 id="heading-flask-1">Flask 시작하기</h2>
<p>flask를 돌리기 위해 우선 flask를 설치해주어야한다.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)


<span class="hljs-meta">@app.route("/")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">welcome</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Welcome to this Flask course"</span>


<span class="hljs-meta">@app.route("/index")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">welcome_index</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Welcome to this index page"</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run()
</code></pre>
<p>위 형식의 코드를 돌리면 웹브라우저에서 코드에 적어준 메시지들을 확인할 수 있다.</p>
<p><a target="_blank" href="http://app.run"><code>app.run</code></a><code>()</code> 부분에 <a target="_blank" href="http://app.run"><code>app.run</code></a><code>(debug=True)</code> 옵션으로 설정해주면 서버 재시작 없이도 변경사항을 바로바로 반영할 수 있다.</p>
<h2 id="heading-get-vs-post">GET vs POST</h2>
<p>Flask에서도 당연하겠지만 GET &amp; POST를 이용해 클라이언트로부터 데이터를 받는데 둘의 차이점을 써보자면 아래와 같다.</p>
<p><code>GET</code>: 주로 데이터 조회를 위해 사용 <code>POST</code>: 주로 데이터 생성, 변경, 삭제 등의 요청에 사용</p>
<h2 id="heading-jinja2">Jinja2 템플릿 엔진</h2>
<p><code>Jinja2</code>는 <code>Flask</code> 패키지에 내장된 템플릿 엔진이다. 동적 웹 페이지를 구성할 때 서버에서 곧바로 웹페이지 로딩과 동시에 변수 값을 할당해준다. 아래 문법에 따라 HTML 문서 상에서 반복문, 조건문을 사용 가능하다.</p>
<p><strong>작성법</strong>:</p>
<pre><code class="lang-txt">{{ }} expressions to print output in html
{%...%} conditions, for loops
{#..#} this is for comments
</code></pre>
<h2 id="heading-dynamic-url">Dynamic URL</h2>
<p>웹페이지를 이동할 때 다음 페이지의 URL을 url_for() 함수에 넣어서 전달하거나 route 함수명을 넣어서 해당 route가 가지는 url 주소를 가져올 수 있다.</p>
<p>따라서 URL을 하드코딩하는 대신 url_for에 함수 이름을 전달해서 정확한 URL을 얻을 수도 있는 것이다.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route('/submit',methods=['POST','GET'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">submit</span>():</span>
    total_score=<span class="hljs-number">0</span>
    <span class="hljs-keyword">if</span> request.method==<span class="hljs-string">'POST'</span>:
        science=float(request.form[<span class="hljs-string">'science'</span>])
        maths=float(request.form[<span class="hljs-string">'maths'</span>])
        c=float(request.form[<span class="hljs-string">'c'</span>])
        data_science=float(request.form[<span class="hljs-string">'datascience'</span>])

        total_score=(science+maths+c+data_science)/<span class="hljs-number">4</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">'getresult.html'</span>)
    <span class="hljs-keyword">return</span> redirect(url_for(<span class="hljs-string">'successres'</span>,score=total_score))
</code></pre>
]]></content:encoded></item></channel></rss>