생산자와 소비자

생산자와 소비자 패턴은 스레드 사이에서 작업을 분배할 때 자주 사용합니다. 생산자 스레드가 소비자 스레드에 데이터를 전달하므로 데이터를 공유하거나 잠글 필요가 없습니다. .NET은 이 패턴을 폭넓게 지원합니다. 기본적인 예로 System.Collections.Concurrent의 BlockingCollection을 사용하는 C# 코드를 살펴보겠습니다.

using System;
using System.Threading;
using System.Collections.Concurrent;

var messages = new BlockingCollection<string>();
var producer = new Thread(() =>
{
    for (var n = 1; i < 10; i++)
        messages.Add($"Message #{n}");
    messages.CompleteAdding();
});

producer.Start();

// 여기서는 메인 스레드가 소비자입니다.
foreach (var message in messages.GetConsumingEnumerable())
    Console.WriteLine(message);

producer.Join();

Rust에서는 _채널_로 같은 동작을 구현할 수 있습니다. 표준 라이브러리의 mpsc::channel은 생산자 여럿과 소비자 하나를 지원합니다. 위 C# 예제를 Rust로 옮기면 대략 다음과 같습니다.

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

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

    let producer = thread::spawn(move || {
        for n in 1..10 {
            tx.send(format!("Message #{}", n)).unwrap();
        }
    });

    // 여기서는 메인 스레드가 소비자입니다.
    for received in rx {
        println!("{}", received);
    }

    producer.join().unwrap();
}

.NET에도 System.Threading.Channels 네임스페이스에 채널이 있습니다. 주로 async와 await를 사용하는 태스크 및 비동기 프로그래밍을 위해 설계되었습니다. Rust에서 비동기 코드에 적합한 채널은 Tokio 런타임이 제공합니다.