Semaphore (세마포어) 란 무엇인가?
세마포어는 주로 동시에 수행될 수 있는 작업의 수를 제한하는 데 사용되는 동기화 도구다.
세마포어는 일종의 카운터로,
이 카운터는 동시에 실행될 수 있는 스레드나 프로세스의 최대 수를 나타낸다.
스레드가 세마포어를 사용하려고 시도할 때마다,
세마포어의 카운트가 감소하고, 스레드가 작업을 완료하면 카운트가 증가한다.
카운트가 0이 되면, 다른 스레드는 세마포어가 다시 사용 가능해질 때까지 대기해야 한다.
Semaphore (세마포어) 의 동작 방식
예를 들어 특정 작업을 멀티스레드를 사용해서
실행시키려고 한다고 가정해 보자.
4개의 스레드를 사용할 것이며,
세마포어 카운트는 2라고 가정해 보자.
그럼 아래와 같은 그림으로 도식화가 가능하다.
각 작업을 스레드에 할당하고,
4개의 스레드가 wait list에 대기하는 모습을 볼 수 있다.
그럼 각 스레드를 실행시켜 보도록 하자.
Thread-1에 할당된 작업이 실행되고,
Semaphore count 가 1 감소된 것을 볼 수 있다.
아직 Semaphore count 가 1개 남아있으므로(permit 상태),
추가적인 스레드를 실행시킬 수 있다.
하나의 스레드를 wait list에서 추가적으로 실행시키게 되면,
Semaphore count 가 1 감소하여 0이 되고,
더 이상 스레드가 실행할 수 없는 상태가 된다. (umpermitted)
위의 그림과 같이 Thread-1의 작업이 끝나면,
Semaphore count 가 다시 1 증가하고,
permit 상태가 되어 추가적인 스레드 작업을 진행할 수 있다.
결국 아래와 같은 시나리오로 흘러갈 것이다.
Semaphore (세마포어)의 사용 예제
RUST의 표준 라이브러리에는 Semaphore 가 존재하지 않는다.
tokio crate를 사용하는 것을 추천한다.
Cargo.toml 에 아래와 같이 tokio crate 를 추가하길 바란다.
tokio = { version = "1.0", features = ["full"] }
메인 코드는 아래와 같다.
RUST 는 소유권개념이 있는 언어이기 때문에,
세마포어를 사용한다고 해도 여러 스레드에서 안전하게
소유권을 공유해야 하기 때문에 Arc 를 사용해야 한다.
#[tokio::main]
async fn main() {
// 커스텀 로거
set_global_logger();
let semaphore = Arc::new(Semaphore::new(3)); // 최대 3개의 스레드 동시 접근 가능
let mut handles = vec![];
for i in 0..10 { // 10개의 스레드 생성 시도
let permit = semaphore.clone();
let handle = tokio::spawn(async move {
let _permit = permit.acquire().await.expect("Failed to acquire permit");
info!("Task {} is running", i);
// 작업을 수행, 예를 들어 async sleep을 사용할 수 있다.
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
info!("Task {} is done", i);
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await;
}
}
해당 코드를 실행해 보면,
로거 파일에는 아래와 같이 로깅이 되는 모습을 볼 수 있다.
'RUST' 카테고리의 다른 글
[RUST] RWLock (Read-Write Lock) (0) | 2024.05.05 |
---|---|
[RUST] 뮤텍스(Mutex) 란 (0) | 2024.05.03 |
[RUST] Rc, Arc 란 (1) | 2024.04.30 |
[RUST] 트레이트 (Traits) (0) | 2024.01.23 |
[RUST] 제네릭 (Generics) (0) | 2024.01.22 |