본문 바로가기
IT/Rust 기초 완전 정복

Rust 메시지 전달과 공유 메모리 (19)

by 지식 발전소 2024. 4. 25.
728x90
반응형

1

 

메시지 전달(Message Passing)

이번에는 Rust에서 작업 간 통신과 동기화를 위해 사용되는 메시지 전달 패턴에 대해 알아보겠습니다. 메시지 전달 패턴은 병렬 프로그래밍과 동시성 프로그래밍에서 매우 중요한 역할을 합니다. 메시지 전달을 잘 활용하면 안전하고 효율적인 동시성 프로그램을 작성할 수 있습니다.

 

1) 메시지 전달이란 무엇인가요?

메시지 전달(Message Passing)은 프로세스나 스레드 간 통신을 위한 패턴입니다. 메시지 전달에서는 데이터를 메시지 형태로 전송하여 작업 간 통신을 수행합니다. 이 패턴은 공유 메모리 방식과 달리 데이터 경합이나 동기화 문제를 피할 수 있습니다.

 

2) 왜 메시지 전달이 필요한가요?

병렬 프로그래밍이나 동시성 프로그래밍에서는 여러 작업이 동시에 실행되므로 작업 간 통신이 필수적입니다. 그러나 공유 메모리 방식을 사용하면 데이터 경합이나 교착 상태, 라이브락 등의 문제가 발생할 수 있습니다.

메시지 전달 패턴을 사용하면 이러한 문제를 피할 수 있습니다. 메시지 전달은 작업 간 통신을 안전하고 효율적으로 처리할 수 있도록 해줍니다.

 

3) Rust의 메시지 전달 구현

Rust에서는 표준 라이브러리의 std::sync::mpsc(Multi-Producer, Single-Consumer) 모듈을 통해 메시지 전달 패턴을 구현할 수 있습니다.

 

a. 채널(Channel) 생성

use std::sync::mpsc;

let (tx, rx) = mpsc::channel();

mpsc::channel()을 호출하면 송신자(Sender)와 수신자(Receiver) 객체가 반환됩니다. 이 객체들을 이용하여 메시지를 전송하고 수신할 수 있습니다.

b. 메시지 전송

tx.send(42).unwrap();

송신자 객체의 send 메서드를 사용하여 메시지를 전송할 수 있습니다.

c. 메시지 수신

let received = rx.recv().unwrap();
println!("Received: {}", received); // 출력: Received: 42

수신자 객체의 recv 메서드를 사용하여 메시지를 수신할 수 있습니다.

d. 멀티 스레딩 예제

use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    let tx1 = tx.clone();
    thread::spawn(move || {
        tx1.send(10).unwrap();
    });

    thread::spawn(move || {
        tx.send(20).unwrap();
    });

    let received1 = rx.recv().unwrap();
    let received2 = rx.recv().unwrap();

    println!("Received: {} and {}", received1, received2);
}

위 예제에서는 두 개의 스레드가 메시지를 전송하고, 메인 스레드에서 이 메시지들을 수신합니다.

 

4) 메시지 전달의 장점

메시지 전달 패턴에는 다음과 같은 장점이 있습니다.

  • 안전성: 공유 메모리 방식과 달리 데이터 경합이나 동기화 문제가 발생하지 않습니다.
  • 격리성: 작업 간 통신이 메시지로만 이루어지므로 서로 격리된 상태를 유지할 수 있습니다.
  • 유연성: 다양한 통신 패턴(1:1, 1:N, N:1, N:M)을 구현할 수 있습니다.
  • 확장성: 새로운 작업을 쉽게 추가할 수 있으며, 작업 간 결합도가 낮습니다.

5) 메시지 전달의 단점

그러나 메시지 전달 패턴에는 다음과 같은 단점도 있습니다.

  • 오버헤드: 메시지 전송과 수신에 따른 오버헤드가 발생할 수 있습니다.
  • 복잡성: 작업 간 통신 로직이 복잡해질 수 있습니다.
  • 지연 시간: 메시지 전송과 수신 사이에 지연이 발생할 수 있습니다.

메시지 전달 패턴은 장단점이 있지만, 동시성 프로그래밍에서 매우 유용한 패턴입니다. 작업 간 통신을 안전하고 효율적으로 처리할 수 있으므로 Rust에서 활발히 사용되고 있습니다.

 

정리하자면, Rust의 메시지 전달 패턴은 std::sync::mpsc 모듈을 통해 구현됩니다. 송신자와 수신자 객체를 생성하여 메시지를 전송하고 수신할 수 있습니다. 메시지 전달은 안전성과 격리성, 유연성, 확장성 등의 장점이 있지만, 오버헤드와 복잡성, 지연 시간 등의 단점도 있습니다. 하지만 전반적으로 병렬 프로그래밍과 동시성 프로그래밍에서 매우 유용한 패턴이므로 Rust 프로그래밍에서 필수적으로 익혀야 합니다.

공유 메모리(Shared Memory)

Rust에서 공유 메모리는 여러 작업이 동일한 메모리 영역에 접근할 수 있도록 해주는 기능입니다. 공유 메모리는 데이터를 효율적으로 공유할 수 있지만, 동시에 데이터 경합과 같은 문제가 발생할 수 있습니다. 이번에는 Rust에서 공유 메모리를 안전하게 사용하는 방법에 대해 알아보겠습니다.

 

1) 공유 메모리란 무엇인가요?

공유 메모리(Shared Memory)는 여러 작업이 동일한 메모리 영역에 접근할 수 있도록 해주는 메모리 관리 기법입니다. 공유 메모리를 사용하면 작업 간 데이터를 효율적으로 공유할 수 있습니다.

 

2) 왜 공유 메모리가 필요한가요?

일반적으로 프로세스나 스레드는 각자의 독립된 메모리 공간을 가지고 있습니다. 그러나 작업 간 데이터를 공유해야 하는 경우가 있습니다. 이때 공유 메모리를 사용하면 작업 간 데이터 공유가 가능해집니다.

공유 메모리는 데이터 복사 비용을 절감하고, 메모리 사용량을 줄일 수 있습니다. 또한, 작업 간 통신 비용도 절감할 수 있습니다.

3) Rust의 공유 메모리 구현

Rust에서는 std::sync 모듈을 통해 공유 메모리를 안전하게 사용할 수 있습니다. 대표적인 공유 메모리 구현체는 다음과 같습니다.

a. Mutex<T>와 Arc<T>

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

위 예제에서는 Mutex<T>와 Arc<T>를 사용하여 스레드 안전한 공유 메모리를 구현했습니다. Mutex<T>는 상호 배제 락을 제공하고, Arc<T>는 참조 카운팅 포인터를 제공합니다.

 

b. RwLock<T>와 Arc<T>

use std::sync::{RwLock, Arc};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(vec![]));
    let mut handles = vec![];

    for i in 0..10 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let mut lock = data.write().unwrap();
            lock.push(i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let lock = data.read().unwrap();
    println!("Result: {:?}", *lock);
}

위 예제에서는 RwLock<T>와 Arc<T>를 사용하여 읽기-쓰기 락을 구현했습니다. 여러 스레드가 동시에 읽기 작업을 수행할 수 있지만, 쓰기 작업은 한 번에 하나의 스레드만 수행할 수 있습니다.

 

4) 공유 메모리의 장단점

공유 메모리에는 다음과 같은 장점이 있습니다.

  • 효율성: 데이터 복사 비용과 메모리 사용량을 줄일 수 있습니다.
  • 통신 비용 절감: 작업 간 통신 비용이 줄어듭니다.

그러나 공유 메모리에는 다음과 같은 단점도 있습니다.

  • 데이터 경합 문제: 여러 작업이 동시에 데이터에 접근할 경우 데이터 경합 문제가 발생할 수 있습니다.
  • 동기화 오버헤드: 데이터 경합을 방지하기 위해 동기화 메커니즘이 필요하며, 이에 따른 오버헤드가 발생합니다.
  • 복잡성 증가: 데이터 경합과 동기화 문제를 해결하기 위한 로직이 복잡해질 수 있습니다.

Rust에서는 Mutex<T>와 RwLock<T> 등의 동기화 기능을 제공하여 공유 메모리에서 발생할 수 있는 문제를 해결할 수 있습니다. 공유 메모리는 효율성과 통신 비용 절감 측면에서 장점이 있지만, 동기화 문제를 잘 처리해야 합니다.

 

5) 공유 메모리 vs. 메시지 전달

공유 메모리와 메시지 전달 패턴은 각각의 장단점이 있습니다. 공유 메모리는 효율성과 통신 비용 절감에 유리하지만, 데이터 경합과 동기화 문제를 해결해야 합니다. 반면, 메시지 전달 패턴은 안전성과 격리성, 유연성이 높지만, 오버헤드와 복잡성, 지연 시간 등의 단점이 있습니다.

 

어떤 패턴을 선택할지는 프로그램의 요구사항과 특성에 따라 달라집니다. 일반적으로 작업 간 데이터 공유가 많이 필요하다면 공유 메모리를 선택하고, 작업 간 통신이 중요하다면 메시지 전달 패턴을 선택하는 것이 좋습니다.

 

두 패턴을 적절히 혼용하여 장점을 최대화하고 단점을 보완하는 것도 좋은 방법입니다. 예를 들어, 공유 메모리로 데이터를 효율적으로 공유하고, 메시지 전달로 작업 간 통신을 수행할 수 있습니다.

 

정리하자면, Rust에서는 std::sync 모듈을 통해 공유 메모리를 안전하게 사용할 수 있습니다. Mutex<T>와 RwLock<T>를 활용하여 데이터 경합과 동기화 문제를 해결할 수 있습니다. 공유 메모리는 효율성과 통신 비용 절감에 유리하지만, 메시지 전달 패턴과 적절히 혼용하는 것이 좋습니다. 공유 메모리와 메시지 전달 패턴은 각각의 장단점이 있으므로, 프로그램의 요구사항과 특성에 따라 적절히 선택하고 혼용하는 것이 중요합니다.

Atomics

Rust에서 Atomics는 스레드 안전한 단일 값 공유를 위한 기본 구성 요소입니다. 이번에는 Atomics의 개념과 사용법에 대해 자세히 알아보겠습니다.

 

1) Atomics란 무엇인가요?

Atomics는 스레드 안전한 단일 값 공유를 위한 타입 집합입니다. Atomics를 사용하면 여러 스레드에서 동시에 값을 읽고 쓰는 것이 가능해집니다.

 

2) 왜 Atomics가 필요한가요?

공유 메모리 프로그래밍에서는 여러 스레드가 동시에 데이터에 접근할 때 데이터 경합 문제가 발생할 수 있습니다. Atomics는 이러한 문제를 해결하기 위한 방법 중 하나입니다.

Atomics는 원자성(atomicity)를 보장합니다. 원자성이란 연산이 불가분의 단일 단계로 실행되는 것을 의미합니다. 따라서 Atomics를 사용하면 스레드 안전한 값 공유가 가능해집니다.

 

3) Rust의 Atomics 타입

Rust의 std::sync::atomic 모듈에는 다음과 같은 Atomics 타입이 제공됩니다.

  • AtomicBool: 부울 값을 저장하는 원자 타입
  • AtomicIsize, AtomicUsize: 플랫폼에 따라 크기가 결정되는 정수 타입
  • AtomicI8, AtomicU8, AtomicI16, AtomicU16, AtomicI32, AtomicU32, AtomicI64, AtomicU64: 고정 크기의 정수 타입
  • AtomicPtr: 포인터 타입

4) Atomics 사용법

Atomics는 다음과 같이 사용할 수 있습니다.

a. 원자 값 생성

use std::sync::atomic::{AtomicUsize, Ordering};

let atomic_value = AtomicUsize::new(0);

new 정적 메서드를 사용하여 원자 값을 생성할 수 있습니다.

b. 원자 연산

atomic_value.store(42, Ordering::Relaxed);
let value = atomic_value.load(Ordering::Relaxed);
println!("Value: {}", value); // 출력: Value: 42

store 메서드를 사용하여 값을 설정하고, load 메서드를 사용하여 값을 읽을 수 있습니다. Ordering 매개변수는 메모리 순서를 지정합니다.

c. 원자 연산 수행

use std::sync::atomic::Ordering::SeqCst;

let old_value = atomic_value.fetch_add(3, SeqCst);
println!("Old value: {}, New value: {}", old_value, atomic_value.load(SeqCst)); // 출력: Old value: 42, New value: 45

fetch_add, fetch_sub, fetch_and, fetch_or, fetch_xor 등의 메서드를 사용하여 원자 연산을 수행할 수 있습니다.

 

5) Atomics의 주의사항

Atomics를 사용할 때는 다음 사항에 주의해야 합니다.

  • Ordering: 메모리 순서를 적절히 지정해야 합니다. 잘못된 순서는 예기치 않은 동작을 야기할 수 있습니다.
  • 성능 오버헤드: Atomics는 스레드 안전성을 보장하기 위해 추가적인 연산을 수행하므로 오버헤드가 발생합니다.
  • 데이터 크기 제한: Atomics는 작은 크기의 데이터에 적합합니다. 큰 데이터를 처리할 때는 Mutex나 RwLock을 사용하는 것이 좋습니다.

Atomics는 스레드 안전한 단일 값 공유를 위한 매우 유용한 도구입니다. 단, 성능 오버헤드와 데이터 크기 제한 등의 단점이 있으므로 상황에 맞게 적절히 사용해야 합니다.

 

정리하자면, Rust의 Atomics는 std::sync::atomic 모듈에서 제공되며, 스레드 안전한 단일 값 공유를 위한 타입 집합입니다. Atomics를 사용하면 원자성이 보장되어 여러 스레드에서 동시에 값을 읽고 쓸 수 있습니다. 그러나 메모리 순서 지정, 성능 오버헤드, 데이터 크기 제한 등의 주의사항이 있으므로 상황에 맞게 적절히 사용해야 합니다.

참고 자료

[1] The Rust Programming Language Book, Steve Klabnik and Carol Nichols, No Starch Press, 2018.

[2] "Shared-State Concurrency", Rust Documentation, https://doc.rust-lang.org/book/ch16-03-shared-state.html

[3] "Atomics", Rust Documentation, https://doc.rust-lang.org/std/sync/atomic/index.html

[4] "Message Passing Concurrency in Rust", Pascal Hertleif, https://pascalhertleif.de/articles/rust-message-passing/

[5] "Rust Concurrency: Shared Memory and Message Passing", Yosha Wuyts, https://yoshuawuyts.gitbooks.io/rustint/content/concurrency/shared-memory.html

 

 

한 고대 문서 이야기

여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오

gospel79.tistory.com

 

유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법

올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을

stock79.tistory.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

728x90
반응형

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글