Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

채널(Channels)

지금까지 우리가 만들었던 스레드들은 수명이 상당히 짧았습니다. 입력을 받고, 계산을 마친 뒤, 결과를 반환하고 곧바로 종료되는 식이었죠.

하지만 우리가 만들 티켓 관리 시스템은 조금 다른 방식이 필요합니다. 바로 **클라이언트-서버 아키텍처(Client-server architecture)**입니다.

우리는 티켓 데이터를 관리하는 **장기 실행 서버 스레드(Server thread)**를 하나 두고, 여러 개의 **클라이언트 스레드(Client threads)**가 이 서버에 접근하도록 할 것입니다. 클라이언트는 새로운 티켓을 추가하라는 **명령(Command)**이나 티켓 상태를 보여달라는 **쿼리(Query)**를 서버에 보낼 수 있어야 합니다. 물론 이 클라이언트 스레드들은 동시에 실행될 것입니다.

통신 방식의 변화

지금까지 살펴본 부모-자식 간의 통신 방식은 매우 제한적이었습니다.

  • 생성된 스레드가 부모의 데이터를 빌려오거나 소유권을 가져옴
  • 스레드가 종료(Join)될 때 부모에게 결과를 반환함

이 방식만으로는 클라이언트-서버 구조를 구현하기 어렵습니다. 서버 스레드가 이미 실행 중인 상태에서 클라이언트가 언제든 데이터를 주고받을 수 있어야 하기 때문이죠.

이런 상황에서 구원투수로 등장하는 것이 바로 **채널(Channels)**입니다.

채널(Channels)이란?

Rust 표준 라이브러리는 std::sync::mpsc 모듈을 통해 다중 생산자, 단일 소비자(Multiple Producer Single Consumer, MPSC) 채널을 제공합니다. 채널은 데이터를 담는 용량이 정해진 바운드(Bounded) 방식과 제한이 없는 언바운드(Unbounded) 방식이 있는데, 일단은 언바운드 방식을 먼저 살펴보겠습니다.

채널은 다음과 같이 생성합니다.

use std::sync::mpsc::channel;

// 송신자(sender)와 수신자(receiver) 한 쌍이 반환됩니다.
let (sender, receiver) = channel();

채널은 데이터를 보내는 **송신자(Sender)**와 데이터를 받는 **수신자(Receiver)**로 구성됩니다. 송신자에서 send를 호출해 데이터를 넣고, 수신자에서 recv를 호출해 데이터를 꺼냅니다.

다중 생산자(Multiple Producers)

Sender는 복제가 가능합니다. 즉, 여러 개의 송신자를 만들어 각 클라이언트 스레드에 하나씩 나눠줄 수 있고, 이들은 모두 동일한 채널로 데이터를 보낼 수 있습니다.

반면, Receiver는 복제할 수 없습니다. 하나의 채널에는 오직 하나의 수신자만 존재할 수 있죠.

이것이 바로 **MPSC(다중 생산자, 단일 소비자)**라는 이름이 붙은 이유입니다!

메시지 타입(Message Type)

SenderReceiver는 제네릭 타입 T를 사용합니다. 채널을 통해 주고받을 메시지의 타입을 자유롭게 정할 수 있다는 뜻입니다. u64 같은 기본 타입부터 복잡한 구조체나 열거형(Enum)까지 무엇이든 가능합니다.

채널의 오류 처리

sendrecv 메서드는 실패할 가능성이 있습니다.

  • 수신자(Receiver)가 메모리에서 사라지면(Drop), 송신자가 send를 할 때 오류가 발생합니다.
  • 모든 송신자(Sender)가 사라지고 채널이 비어 있다면, 수신자가 recv를 할 때 오류가 발생합니다.

간단히 말해, 채널의 한쪽 끝이 닫히면 더 이상 정상적인 통신을 할 수 없다는 뜻입니다.

Exercise

The exercise for this section is located in 07_threads/05_channels