데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Openlab - 7차 Rust 스터디

      lambda 24.07.23
      138 4 0
      DEVOTEE 요약
      7주차 스터디는 러스트의 동시성과 객체지향 프로그래밍을 다루었으며, 스레드, 메시지 패싱, Arc/Mutex, Sync와 Send 트레이트를 통해 안전한 동시성 구현 방법과 트레이트 객체를 이용한 다형성, 객체지향 디자인 패턴을 소개하였습니다. 동시성 부분에서 설명이 부족한 점은 아쉬웠지만, 책을 통해 많은 부분을 배웠습니다. 마지막 주까지 열심히 공부해 나가기로 했습니다.
      DEVOTEE 추천 블로그

      7주차 스터디는 7월 1일에 진행 했습니다.

      길고 길었던 스터디는 어느덧 마지막 회차만 남겨두고 있습니다. 러스트에 대해 깊게 알게 되진 못했지만 그래도 책 한권을 거의 다 훑어 보았다는 것이 고무적인네요.

      아직 한주차가 남았고 가장 중요한 챕터들도 남았으니 마지막까지 힘내보겠습니다.

      이번 7주차에는 러스트의 핵심이라고 볼 수 있는 동시성에 대한 부분과, 아직 논란거리로 남아있는 러스트는 객체지향 언어인가에 대한 부분들을 다루어 보았습니다.

      사실 조금 실망스러운 점은 동시성에 대한 내용이 이 책에서 너무 조금 다루어졌다는 것과, 객체지향에 대한 부분도 너무 짧게 넘어간 것 같은 부분입니다.

      번역에 대한 부분도 조금 아쉽기는 하나, 이 책을 기반으로 이제 다음 러스트에 관한 책들을 읽을 때는 더 쉽게 이해할 수 있을 것 같습니다.



      16장, 17장에 대한 간단한 정리

      16. 겁 없는 동시성

      러스트는 메모리 안전성과 데이터 경합 문제를 방지하면서 안전하게 동시성을 구현할 수 있는 강력한 도구를 제공합니다.

      이번 글에서는 스레드를 이용한 코드의 동시 실행, 메시지 패싱을 통한 스레드 간 데이터 전송, 공유 상태 동시성, 그리고 Sync와 Send 트레이트를 사용한 확장 가능한 동시성에 대해 알아보겠습니다.

      16.1. 스레드를 이용하여 코드를 동시에 실행하기

      러스트에서 동시성을 구현하는 가장 기본적인 방법은 스레드를 사용하는 것입니다. std::thread 모듈을 통해 스레드를 생성하고 관리할 수 있습니다.

      스레드 생성 및 실행

      use std::thread;
      use std::time::Duration;
      
      fn main() {
          let handle = thread::spawn(|| {
              for i in 1..10 {
                  println!("hi number {} from the spawned thread!", i);
                  thread::sleep(Duration::from_millis(1));
              }
          });
      
          for i in 1..5 {
              println!("hi number {} from the main thread!", i);
              thread::sleep(Duration::from_millis(1));
          }
      
          handle.join().unwrap();
      }

      위 예제에서 thread::spawn을 사용하여 새로운 스레드를 생성하고 실행합니다.

      메인 스레드와 생성된 스레드는 동시에 실행됩니다. handle.join()을 호출하여 생성된 스레드가 종료될 때까지 기다립니다.

      스레드와 데이터 공유

      스레드 간에 데이터를 공유하려면 move 키워드를 사용하여 데이터를 클로저로 이동시켜야 합니다.

      use std::thread;
      
      fn main() {
          let v = vec![1, 2, 3];
      
          let handle = thread::spawn(move || {
              println!("Here's a vector: {:?}", v);
          });
      
          handle.join().unwrap();
      }

      위 예제에서 move 키워드를 사용하여 벡터 v를 클로저로 이동시킵니다. 이를 통해 생성된 스레드에서 v를 사용할 수 있습니다.

      16.2. 메시지 패싱을 사용하여 스레드 간 데이터 전송하기

      러스트에서는 메시지 패싱을 통해 스레드 간에 안전하게 데이터를 전송할 수 있습니다.

      std::sync::mpsc 모듈을 사용하여 채널을 생성하고, 이를 통해 데이터를 주고받을 수 있습니다.

      채널 생성 및 메시지 전송

      use std::sync::mpsc;
      use std::thread;
      use std::time::Duration;
      
      fn main() {
          let (tx, rx) = mpsc::channel();
      
          thread::spawn(move || {
              let val = String::from("hi");
              tx.send(val).unwrap();
          });
      
          let received = rx.recv().unwrap();
          println!("Got: {}", received);
      }

      위 예제에서 mpsc::channel을 사용하여 송신자(tx)와 수신자(rx)를 생성합니다. 생성된 스레드는 송신자를 통해 메시지를 전송하고, 메인 스레드는 수신자를 통해 메시지를 수신합니다.

      다중 송신자

      채널에는 여러 개의 송신자를 연결할 수 있습니다. 이를 통해 여러 스레드에서 데이터를 전송할 수 있습니다.

      use std::sync::mpsc;
      use std::thread;
      use std::time::Duration;
      
      fn main() {
          let (tx, rx) = mpsc::channel();
      
          for i in 0..5 {
              let tx = tx.clone();
              thread::spawn(move || {
                  let val = format!("hi number {}", i);
                  tx.send(val).unwrap();
                  thread::sleep(Duration::from_millis(100));
              });
          }
      
          for received in rx {
              println!("Got: {}", received);
          }
      }

      위 예제에서 tx.clone()을 사용하여 여러 송신자를 생성하고, 각 송신자는 개별 스레드에서 메시지를 전송합니다. 수신자는 모든 메시지를 수신하고 출력합니다.

      16.3. 공유 상태 동시성

      러스트에서는 여러 스레드 간에 데이터를 공유할 수 있도록 Arc와 Mutex를 제공합니다.

      Arc는 원자적 참조 카운트(Atomic Reference Count) 스마트 포인터로, 데이터를 여러 소유자로 공유할 수 있게 합니다. Mutex는 데이터에 대한 상호 배제를 제공합니다.

      Arc와 Mutex 사용 예제

      use std::sync::{Arc, Mutex};
      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());
      }

      위 예제에서 Arc::new를 사용하여 Mutex를 감싸고, 이를 통해 여러 스레드가 안전하게 데이터를 공유할 수 있습니다.

      각 스레드는 counter.lock().unwrap()을 사용하여 Mutex를 잠그고, 공유 데이터를 안전하게 변경합니다.

      16.4. Sync와 Send 트레이트를 이용한 확장 가능한 동시성

      러스트의 동시성은 Sync와 Send 트레이트에 의해 관리됩니다.

      Send 트레이트는 타입이 다른 스레드로 이동될 수 있음을 나타내고, Sync 트레이트는 타입이 여러 스레드에서 안전하게 접근될 수 있음을 나타냅니다.

      Send 트레이트

      Send 트레이트는 타입이 다른 스레드로 이동될 수 있음을 나타냅니다. 대부분의 기본 타입은 Send를 구현합니다.

      use std::thread;
      
      fn main() {
          let v = vec![1, 2, 3];
      
          let handle = thread::spawn(move || {
              println!("{:?}", v);
          });
      
          handle.join().unwrap();
      }

      위 예제에서 벡터 v는 Send 트레이트를 구현하므로, thread::spawn을 통해 다른 스레드로 이동할 수 있습니다.

      Sync 트레이트

      Sync 트레이트는 타입이 여러 스레드에서 안전하게 접근될 수 있음을 나타냅니다. Arc<T>와 Mutex<T>는 Sync를 구현합니다.

      use std::sync::{Arc, Mutex};
      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());
      }

      위 예제에서 Arc<Mutex<T>>는 여러 스레드에서 안전하게 접근할 수 있습니다.

      Mutex는 내부 데이터를 보호하며, Arc는 참조 카운트를 통해 여러 스레드가 안전하게 데이터를 공유할 수 있게 합니다.

      결론

      러스트는 메모리 안전성과 데이터 경합 문제를 방지하면서 안전하게 동시성을 구현할 수 있는 강력한 도구를 제공합니다.

      스레드를 사용하여 코드를 동시에 실행하고, 메시지 패싱을 통해 스레드 간에 안전하게 데이터를 전송할 수 있습니다.

      Arc와 Mutex를 사용하여 공유 상태 동시성을 구현할 수 있으며, Sync와 Send 트레이트를 통해 타입의 스레드 안전성을 관리할 수 있습니다.

      이러한 기능을 잘 활용하면 러스트 프로그램을 더욱 효율적이고 안전하게 작성할 수 있습니다.


      17. 러스트의 객체 지향 프로그래밍 기능들

      러스트는 객체 지향 프로그래밍(OOP) 패러다임을 완전히 지원하지 않지만, 여러 객체 지향 프로그래밍 기능들을 제공합니다.

      이를 통해 러스트에서 객체 지향 프로그래밍의 장점을 활용할 수 있습니다.

      이번 글에서는 객체 지향 언어의 특성, 트레이트 객체를 사용하여 다양한 타입의 값을 허용하는 방법, 그리고 객체 지향 디자인 패턴을 구현하는 방법에 대해 알아보겠습니다.

      17.1. 객체 지향 언어의 특성

      객체 지향 프로그래밍은 데이터와 이를 조작하는 함수를 하나의 단위로 묶어 관리하는 프로그래밍 패러다임입니다. 객체 지향 언어의 주요 특성은 다음과 같습니다:

      1. 캡슐화(Encapsulation): 데이터와 함수를 하나의 객체로 묶어 외부로부터 보호합니다.

      2. 상속(Inheritance): 객체가 다른 객체의 속성과 메서드를 상속받아 재사용합니다.

      3. 다형성(Polymorphism): 동일한 인터페이스를 통해 다른 타입의 객체를 다룰 수 있습니다.

      러스트는 상속을 지원하지 않지만, 캡슐화와 다형성은 트레이트와 구조체를 통해 구현할 수 있습니다.

      17.2. 트레이트 객체를 사용하여 다른 타입의 값 허용하기

      러스트에서 트레이트 객체는 다형성을 구현하는 데 사용됩니다. 트레이트 객체를 사용하면 여러 타입의 값을 동일한 인터페이스로 처리할 수 있습니다.

      트레이트 객체 정의

      먼저, 트레이트를 정의합니다.

      trait Draw {
          fn draw(&self);
      }

      그 다음, 이 트레이트를 구현하는 구조체를 정의합니다.

      struct Button {
          label: String,
      }
      
      impl Draw for Button {
          fn draw(&self) {
              println!("Drawing a button with label: {}", self.label);
          }
      }
      
      struct TextField {
          text: String,
      }
      
      impl Draw for TextField {
          fn draw(&self) {
              println!("Drawing a text field with text: {}", self.text);
          }
      }

      트레이트 객체 사용

      Box<dyn Draw>를 사용하여 트레이트 객체를 생성할 수 있습니다. 이를 통해 다양한 타입의 값을 동일한 트레이트를 통해 다룰 수 있습니다.

      fn main() {
          let button = Button { label: String::from("OK") };
          let text_field = TextField { text: String::from("Hello") };
      
          let components: Vec<Box<dyn Draw>> = vec![
              Box::new(button),
              Box::new(text_field),
          ];
      
          for component in components {
              component.draw();
          }
      }

      위 예제에서 Vec<Box<dyn Draw>>는 Draw 트레이트를 구현한 객체들의 벡터를 나타냅니다. 각각의 객체는 draw 메서드를 호출할 수 있습니다.

      17.3. 객체 지향 디자인 패턴 구현하기

      러스트에서는 여러 객체 지향 디자인 패턴을 트레이트와 구조체를 사용하여 구현할 수 있습니다.

      이번 섹션에서는 전략 패턴(Strategy Pattern)과 옵저버 패턴(Observer Pattern)을 살펴보겠습니다.

      전략 패턴

      전략 패턴은 런타임에 알고리즘을 선택할 수 있게 하는 디자인 패턴입니다. 이를 통해 알고리즘을 클래스 외부에서 정의하고, 런타임에 이를 객체에 주입할 수 있습니다.

      trait Strategy {
          fn execute(&self);
      }
      
      struct ConcreteStrategyA;
      impl Strategy for ConcreteStrategyA {
          fn execute(&self) {
              println!("Executing strategy A");
          }
      }
      
      struct ConcreteStrategyB;
      impl Strategy for ConcreteStrategyB {
          fn execute(&self) {
              println!("Executing strategy B");
          }
      }
      
      struct Context {
          strategy: Box<dyn Strategy>,
      }
      
      impl Context {
          fn new(strategy: Box<dyn Strategy>) -> Context {
              Context { strategy }
          }
      
          fn set_strategy(&mut self, strategy: Box<dyn Strategy>) {
              self.strategy = strategy;
          }
      
          fn execute_strategy(&self) {
              self.strategy.execute();
          }
      }
      
      fn main() {
          let strategy_a = Box::new(ConcreteStrategyA);
          let strategy_b = Box::new(ConcreteStrategyB);
      
          let mut context = Context::new(strategy_a);
          context.execute_strategy();
      
          context.set_strategy(strategy_b);
          context.execute_strategy();
      }

      위 예제에서 Context는 Strategy 트레이트를 구현한 객체를 받아서 사용합니다. set_strategy 메서드를 통해 런타임에 알고리즘을 변경할 수 있습니다.

      옵저버 패턴

      옵저버 패턴은 객체의 상태 변화를 관찰하고, 그 변화를 다른 객체에 통지하는 디자인 패턴입니다.

      use std::cell::RefCell;
      use std::rc::Rc;
      
      trait Observer {
          fn update(&self, message: &str);
      }
      
      struct ConcreteObserver {
          name: String,
      }
      
      impl Observer for ConcreteObserver {
          fn update(&self, message: &str) {
              println!("{} received message: {}", self.name, message);
          }
      }
      
      struct Subject {
          observers: Vec<Rc<RefCell<dyn Observer>>>,
      }
      
      impl Subject {
          fn new() -> Subject {
              Subject {
                  observers: Vec::new(),
              }
          }
      
          fn register_observer(&mut self, observer: Rc<RefCell<dyn Observer>>) {
              self.observers.push(observer);
          }
      
          fn notify_observers(&self, message: &str) {
              for observer in &self.observers {
                  observer.borrow().update(message);
              }
          }
      }
      
      fn main() {
          let observer1 = Rc::new(RefCell::new(ConcreteObserver {
              name: String::from("Observer 1"),
          }));
          let observer2 = Rc::new(RefCell::new(ConcreteObserver {
              name: String::from("Observer 2"),
          }));
      
          let mut subject = Subject::new();
          subject.register_observer(observer1.clone());
          subject.register_observer(observer2.clone());
      
          subject.notify_observers("Hello, observers!");
      }

      위 예제에서 Subject는 옵저버들을 관리하며, 상태가 변경될 때 옵저버들에게 통지합니다.

      Observer 트레이트를 구현한 객체는 update 메서드를 통해 메시지를 받습니다.

      결론

      러스트는 객체 지향 프로그래밍의 여러 특성을 트레이트와 구조체를 통해 구현할 수 있습니다.

      트레이트 객체를 사용하여 다양한 타입의 값을 다룰 수 있으며, 여러 객체 지향 디자인 패턴을 구현할 수 있습니다.

      이러한 기능을 잘 활용하면 러스트에서도 객체 지향 프로그래밍의 장점을 충분히 활용할 수 있습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      lambda 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기