• B6b364a4920a7ba8220a449635ca0c59?s=80&d=mm

    Changes from SoonBin

    SoonBin - about 4 years ago (Sep 14, 2015, 12:09 PM)
    동시성
  • Changes have been accepted Merged
      Looks like something's not quite right here.. We've been notified of the issue, and will get back to you soon.
      concurrency
      # 4.6. 동시성 (Concurrency) - 100%

      동시성과 병렬성은 컴퓨터 과학에서 무척이나 중요한 주제이며, 오늘날 실무에서도 중요하게 취급됩니다. 컴퓨터의 코어 수는 날이 갈수록 늘어나고 있지만 아직도 많은 프로그래머들은 이 코어들을 충분히 활용할 준비가 되어있지 않습니다.

      Rust의 메모리 보호(memory safety)는 동시성 환경에서도 똑같이 적용됩니다. 동시처리(concurrent) Rust 프로그램에서도 필수적으로 데이터 경합이 없도록 메모리 보호처리가 이루어져야 합니다. 여기서 Rust의 타입 시스템이 동시처리 코드에 대해 컴파일 단계에서 검증할 수 있도록 도와줍니다.

      Rust의 동시성 기능에 대해 이야기하기 전에, 한 가지 짚고 넘어가야 할 것이 있습니다. 바로 Rust는 이 모든 것이 언어가 아닌 표준 라이브러리를 통해 제공될 정도로 저수준이란 점입니다. 그렇기 때문에 만약 Rust가 동시성을 다루는 방식이 어딘가 마음에 들지 않는다면 직접 구현할 수도 있습니다. [mio](https://github.com/carllerche/mio)는 이 원칙이 적용된 실제 예입니다.

      배경지식: `Send` 와 `Sync`

      동시처리 프로그램은 검증(reason about)하기 어렵습니다. 하지만 Rust의 강하고(strong), 정적(static) 타입 시스템이 검증을 도와줍니다. 예를 들어 Rust는 두 트레잇을 제공해 동시성을 가질지도 모르는 코드를 이해하도록 돕습니다.

      ### `Send`

      여기서 다룰 첫 트레잇은 [`Send`](../std/marker/trait.Send.html)입니다. 타입 `T`가 `Send`를 구현(implement)하는 건 이 타입이 스레드 사이에서 소유권을 안전하게 넘겨줄 수 있다고 컴파일러에게 알려주는 것입니다.

      이를 위해 일정한 제한을 두는 것이 중요합니다. 예를 들어 만약 두 스레드 사이에 채널이 있다면 어떤 데이터를 다른 스레드에게 채널을 통해 보내고 싶을 수도 있습니다. 그러므로 우리는 그 타입을 위해 `Send`를 반드시 구현해야 합니다.

      반대로, 스레드 안전하지 않은 라이브러리를 FFI로 감쌀 때는 `Send`를 구현하지 않아야 컴파일러가 현재 스레드를 떠나지 않도록 강제할 것입니다.

      `Sync`

      두 번째 트레잇은 [`Sync`](../std/marker/trait.Sync.html)라고 합니다. 타입`T`가 `Sync`를 구현하는 건 이 타입을 가진 무언가가 여러 스레드에서 동시에 사용돼도 메모리 불안전성을 절대 불러오지 않는다고 컴파일러에게 알려주는 것입니다.

      예를 들어, 원자적 참조 계수(atomic reference count)를 가진 불변 데이터(immutable data)의 공유는 스레드 안전합니다. Rust는 `Arc`와 같은 타입을 제공하여 `Sync`를 구현, 스레드 사이의 공유를 안전하게 합니다.

      이 두 트레잇은 타입 시스템으로 하여금 동시성을 지닌 코드의 속성에 대한 강한 보장을 가능하게 합니다. 왜인지 설명하기 전에 먼저 동시처리 Rust 프로그램을 만드는 법을 배워보도록 합시다!

      ## Threads

      Rust's standard library provides a library for threads, which allow you to
      run Rust code in parallel. Here's a basic example of using `std::thread`:

      ```rust
      use std::thread;

      fn main() {
      thread::spawn(|| {
      println!("Hello from a thread!");
      });
      }
      ```

      The `thread::spawn()` method accepts a closure, which is executed in a
      new thread. It returns a handle to the thread, that can be used to
      wait for the child thread to finish and extract its result:

      ```rust
      use std::thread;

      fn main() {
      let handle = thread::spawn(|| {
      "Hello from a thread!"
      });

      println!("{}", handle.join().unwrap());
      }
      ```

      Many languages have the ability to execute threads, but it's wildly unsafe.
      There are entire books about how to prevent errors that occur from shared
      mutable state. Rust helps out with its type system here as well, by preventing
      data races at compile time. Let's talk about how you actually share things
      between threads.

      ## Safe Shared Mutable State

      Due to Rust's type system, we have a concept that sounds like a lie: "safe
      shared mutable state." Many programmers agree that shared mutable state is
      very, very bad.

      Someone once said this:

      > Shared mutable state is the root of all evil. Most languages attempt to deal
      > with this problem through the 'mutable' part, but Rust deals with it by
      > solving the 'shared' part.

      The same [ownership system](ownership.html) that helps prevent using pointers
      incorrectly also helps rule out data races, one of the worst kinds of
      concurrency bugs.

      As an example, here is a Rust program that would have a data race in many
      languages. It will not compile
      러스트의 표준 라이브러리 중에는 동시처리가 가능하도록 하는 스레드 관련 라이브러리들이 있습니다. 가장 간단한 예시는 `std::thread`입니다:

      ```rust
      use std::thread;

      fn main() {
      thread::spawn(|| {
      println!("Hello from a thread!");
      });
      }
      ```

      `thread::spawn()`은 클로저를 받아, 새로운 스레드 안에서 실행하는 메소드입니다. 자식 스레드(child thread)들이 끝날때까지 기다리고 그 결과들을 받아올 수 있도록 해주는 핸들(handle)을 반환값으로 가집니다.

      ```rust
      use std::thread;

      fn main() {
      let handle = thread::spawn(|| {
      "Hello from a thread!"
      });

      println!("{}", handle.join().unwrap());
      }
      ```

      많은 언어들이 스레드들을 사용할 수 있도록 되어있지만, 매우 위험합니다. 공유된 가변 상태(shared mutable state)때문에 발생하는 에러를 어떻게 해결할 것인가에 대한 책들만 해도 수십권이 될 것입니다. 러스트는 타입 시스템, 더 나아가 컴파일 차원에서 데이터 레이스를 방지하여 해결합니다. 스레드 간에 어떻게 공유가 이루어지는지 보도록 합시다.

      ## Safe Shared Mutable State

      러스트의 타입 시스템 덕분에, 마치 거짓말 같이 들리는 능력을 얻었습니다:
      "안전한 공유된 가변 상태(safe shared mutable state)"
      많은 프로그래머들이 공유된 가변 상태는 매우, 매우 나쁘다는 것을 공감하고 있습니다.
      누군가는 이렇게 말합니다:

      >공유된 가변 상태는 모든 악의 근원(root of all evil)이다. 대부분의 언어들은 이 문제를
      >'가변'의 관점에서 해결하려 한다, 그러나 러스트는 '공유'의 관점에서 문제를 해결했다.

      같은 맥락으로 [소유권 시스템](ownership.html)은 포인터를 잘못 사용하는 것을 방지함으로써 동시성의 악명 높은 문제, 데이터 레이스를 해결하는 데에도 도움을 줍니다.

      한 예로, 데이터 레이스 문제를 가진 러스트 프로그램이 있습니다. 이것은 컴파일 자체가 되지 않을 것입니다
      :

      ```ignore
      use std::thread;

      fn main() {
      let mut data = vec![1u32, 2, 3];

      for i in 0..3 {
      thread::spawn(move || {
      data[i] += 1;
      });
      }

      thread::sleep_ms(50);
      }
      ```

      This gives us an error이런 에러 메시지를 출력합니다:

      ```text
      8:17 error: capture of moved value: `data`
      data[i] += 1;
      ^~~~
      ```

      In this case, we know that our code _should_ be safe, but Rust isn't sure. And
      it's actually not safe: if we had a reference to `data` in each thread, and the
      thread takes ownership of the reference, we have three owners! That's bad. We
      can fix this by using the `Arc` type, which is an atomic reference counted
      pointer. The 'atomic' part means that it's safe to share across threads.

      `Arc` assumes one more property about its contents to ensure that it is safe
      to share across threads: it assumes its contents are `Sync`. But in our
      case, we want to be able to mutate the value. We need a type that can ensure
      only one person at a time can mutate what's inside. For that, we can use the
      `Mutex` type. Here's the second version of our code. It still doesn't work,
      but for a different reason
      이 경우에, 우리 코드가 _아마_ 안전하다고 생각할지도 모르지만, 러스트는 그렇지 않은가 봅니다. 그리고 실제로 안전하지 않습니다: 각 스레드가 `data`의 참조를 가지고 있고, 참조의 소유권도 가지고 있으니, 소유자가 셋이나 있습니다! 좋지 않군요. 이것을 원자적 참조 계수 포인터(atomic reference counted pointer)라 불리는 `Arc` 타입을 사용해 해결할 수 있습니다. 여기서 원자적(atomic)은 스레드들 간에 공유가 이루어지는 것에 안전하다는 것을 의미합니다.

      `Arc`는 원자적(atomic) 속성에서 하나 더 가정합니다: 바로 그 내용물이 `Sync`라고 가정하는 것입니다. 그렇지만 여기서 우리는 값을 변경할 수 있도록 하고 싶습니다. 때문에 반드시 한번에 한 사람만 값을 변경할 수 있다는 것을 보장하는 타입이 필요합니다. `Mutex` 타입입니다. 여기 우리의 수정된 코드가 있습니다. 여전히 작동하지 않지만, 이유가 다릅니다
      :

      ```ignore
      use std::thread;
      use std::sync::Mutex;

      fn main() {
      let mut data = Mutex::new(vec![1u32, 2, 3]);

      for i in 0..3 {
      let data = data.lock().unwrap();
      thread::spawn(move || {
      data[i] += 1;
      });
      }

      thread::sleep_ms(50);
      }
      ```

      Here's the error에러 메세지:

      ```text
      :9:9: 9:22 error: the trait `core::marker::Send` is not implemented for the type `std::sync::mutex::MutexGuard<'_, collections::vec::Vec>` [E0277]
      :11 thread::spawn(move || {
      ^~~~~~~~~~~~~
      :9:9: 9:22 note: `std::sync::mutex::MutexGuard<'_, collections::vec::Vec>` cannot be sent between threads safely
      :11 thread::spawn(move || {
      ^~~~~~~~~~~~~
      ```

      You see, [`Mutex`](../std/sync/struct.Mutex.html) has a
      [`lock`](../std/sync/struct.Mutex.html#method.lock)
      method which has this signature:

      ```ignore
      fn lock(&self) -> LockResult>
      ```

      Because `Send` is not implemented for `MutexGuard`, we can't transfer the
      guard across thread boundaries, which gives us our error.

      We can use `Arc` to fix this. Here's the working version
      보셨듯이, [`Mutex`](../std/sync/struct.Mutex.html)는 다음의 명세를 가진 [`lock`](../std/sync/struct.Mutex.html#method.lock) 메소드를 가지고 있습니다.

      ```ignore
      fn lock(&self) -> LockResult>
      ```

      `MutexGuard`에 `Send`가 구현되지 않았기 때문에, guard를 스레드 너머로 전달할 수 없어 에러가 발생하는 것입니다.

      `Arc`를 사용해 고쳐보죠. 작동하는 버전입니다
      :

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

      fn main() {
      let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));

      for i in 0..3 {
      let data = data.clone();
      thread::spawn(move || {
      let mut data = data.lock().unwrap();
      data[i] += 1;
      });
      }

      thread::sleep_ms(50);
      }
      ```

      We now call `clone()` on our `Arc`, which increases the internal count. This
      handle is then moved into the new thread. Let's examine the body of the
      thread more closely
      `Arc`의 `clone()`을 호출합니다. 이것은 내부 계수(internal count)를 상승시킵니다. 그리고 이 핸들은 새로운 스레드로 옮겨집니다. 스레드의 몸체 부분을 좀 더 자세히 보도록 하죠:

      ```rust
      # use std::sync::{Arc, Mutex};
      # use std::thread;
      # fn main() {
      # let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));
      # for i in 0..3 {
      # let data = data.clone();
      thread::spawn(move || {
      let mut data = data.lock().unwrap();
      data[i] += 1;
      });
      # }
      # thread::sleep_ms(50);
      # }
      ```

      First, we call `lock()`, which acquires the mutex's lock. Because this may fail,
      it returns an `Result`, and because this is just an example, we `unwrap()`
      it to get a reference to the data. Real code would have more robust error handling
      here. We're then free to mutate it, since we have the lock.

      Lastly, while the threads are running, we wait on a short timer. But
      this is not ideal: we may have picked a reasonable amount of time to
      wait but it's more likely we'll either be waiting longer than
      necessary or not long enough, depending on just how much time the
      threads actually take to finish computing when the program runs.

      A more precise alternative to the timer would be to use one of the
      mechanisms provided by the Rust standard library for synchronizing
      threads with each other. Let's talk about one of them: channels.

      ## Channels

      Here's a version of our code that uses channels for synchronization, rather
      than waiting for a specific time
      thread::spawn(move || {
      let mut data = data.lock().unwrap();
      data[i] += 1;
      });
      # }
      # thread::sleep_ms(50);
      # }
      ```

      먼저, Mutex의 잠금을 얻는 `lock()`을 호출합니다. 실패할 수도 있기 때문에, `Result`를 반환합니다. 그리고 이것은 단지 예제이기 때문에, `unwrap()`을 사용해 데이터의 참조를 얻어옵니다. 실제 코드는 여기서 좀 더 에러 핸들링에 엄격해야 합니다. 우리가 잠금을 가지고 있기 때문에, 여기서는 마음대로 값을 수정할 수 있습니다.

      마지막으로 스레드들이 실행하고 있는 중에, 짧은 타이머를 기다립니다. 그러나 이것이 최선은 아닙니다: 딱 맞는 시간을 기다릴 수도 있게 되겠지만, 스레드들이 계산을 끝내는데 얼마나 걸리느냐에 따라 대부분 들쭉날쭉하게 기다릴 것입니다. (더 늦거나 빠르게)

      더 정확한 방법 중 하나는 스레드들간의 동기화를 제공하는 러스트 표준 라이브러리를 사용하는 것입니다. 그 중 하나를 소개하죠: 채널(channels)

      ## Channels

      이것이 특정한 시간을 기다리기보다 채널을 사용해 동기화를 한 코드입니다
      :

      ```rust
      use std::sync::{Arc, Mutex};
      use std::thread;
      use std::sync::mpsc;

      fn main() {
      let data = Arc::new(Mutex::new(0u32));

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

      for _ in 0..10 {
      let (data, tx) = (data.clone(), tx.clone());

      thread::spawn(move || {
      let mut data = data.lock().unwrap();
      *data += 1;

      tx.send(());
      });
      }

      for _ in 0..10 {
      rx.recv();
      }
      }
      ```

      We use the `mpsc::channel()` method to construct a new channel. We just `send`
      a simple `()` down the channel, and then wait for ten of them to come back.

      While this channel is just sending a generic signal, we can send any data that
      is `Send` over the channel
      `mpsc::channel()`를 사용해 새로운 채널을 생성했습니다. `send`에 간단한 `()`을 보내주었고, 10개가 돌아오기를 기다립니다.

      채널은 제네릭 신호(generic signal)를 보내기 때문에, 어떤 데이터든지 `Send`에 써서 보낼 수 있습니다
      !

      ```rust
      use std::thread;
      use std::sync::mpsc;

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

      for _ in 0..10 {
      let tx = tx.clone();

      thread::spawn(move || {
      let answer = 42u32;

      tx.send(answer);
      });
      }

      rx.recv().ok().expect("Could not receive answer");
      }
      ```

      A `u32` is `Send` because we can make a copy. So we create a thread, ask it to calculate
      the answer, and then it `send()`s us the answer over the channel.


      ## Panics

      A `panic!` will crash the currently executing thread. You can use Rust's
      threads as a simple isolation mechanism:
      복사할 수 있기 때문에 `u32`를 `Send`에 보냈습니다. 스레드를 만들고, answer를 계산하기를 요청하고, `send()`로 answer를 채널로 보냅니다.


      ## Panics

      `panic!`은 현재 실행중인 스레드를 멈춥니다. 간단한 격리(isolation) 매커니즘의 일환으로 러스트의 스레드를 사용할 수 있습니다:


      ```rust
      use std::thread;

      let result = thread::spawn(move || {
      panic!("oops!");
      }).join();

      assert!(result.is_err());
      ```

      Our `Thread` gives us a `Result` back, which allows us to check if the thread
      has panicked or not
      우리의 `Thread`는 스레드가 패닉되었는지 체크해주는 `Result`를 돌려줍니다.