러스트의 소유권과 타입시스템은 런타임이 아닌 컴파일 타임에 다양한 동시성 에러를 처리할 수 있게 한다. 그결과 운영환경 배포전 개발 과정에서 코드의 오류를 수정 할 수 있다. 러스트 팀은 이런 기능에 ‘자신있는 동시성(fearless concerrency)’ 라는 별명을 붙였다. 자신있는 동시성 덕분에 버그 없는 코드를 작성하고 새로운 버그의 출현에 대한 걱정없이 쉽게 리팩토링 할 수 있다.
코드를 동시에 실행하기 위한 스레드
- 요즘 사용하는 대부분 OS는 프로그램의 코드를 process로 실행하며, OS가 한번에 여러개의 process를 관리한다.
- 프로그램 안에서는 동시에 실행되는 독립된 부분이 있을수 있다. 이렇게 독립된 부분을 실행하는 기능을 스레드(threads)라고 한다.
- 프로그램의 연산을 여러개의 스레드로 분리하면 성능은 향상되지만 복잡도가 증가한다.
- 스레드는 동시에 실행되므로 다른 스레드에서 실행되는 코드의 순서를 보장할 수 없다. 이런 특징은 다음과 같은 문제의 원인이 된다.
- 경합 상태(race conditions): 스레드가 일정하지 않은 순서로 데이터나 자원에 접근하는 상황
- 데드락(deadlocks): 두 스레드가 모두 서로 다른 자원의 사용을 마칠 때까지 대기해서 두 스레드 모두 대기 상태에 놓이는 상황
- 특정 상황에서만 발생해서 재현이나 수정이 어려운 버그
- 러스트는 저수준 언어라서 러스트의 표준 라이브러리는 1:1스레드 구현만을 지원한다. 하지만 오버헤드를 감수하더라도 스레드의 실행을 더 상세히 제어하고 싶은경우 등을 위한 크레이트도 존재한다.
스레드 간 데이터 교환을 위한 메시지 전달
공유 상태 동시성
- 대부분 프로그래밍 언어에서 채널은 단일 소유권을 의미한다. 일단 값을 채널로 보내면 그 값은 더 이상 유효하지 않기 때문이다.
- 공유 메모리 동시성은 다중 소유권과 유사하다. 여러개의 스레드가 같은 메모리의 데이터에 동시에 접근할 수 있기 때문이다.
- 스마트 포인터를 이용하면 다중 소유권을 적용할수 있다.
- 다중 소유권은 여러 소유자를 관리해야하므로 복잡도가 증가한다.
- 러스트의 타입 시스템과 소유권 규칙은 이런 관리에 드는 노력을 상당 부분 해소한다.
- 러스트에서 메모리 공유에 가장 보편적으로 사용되는것이 Mutex이다.
Mutex
Sync와 Send Trait로 동시성 확장하기
- 러스트는 표준 라이브러리 외에 언어차원 에서도 두개의 동시성 개념을 지원한다.
- std::marker Trait인 Sync와 Send Trait이 바로 그것이다.
- Send Trait는 구현하는 타입이 소유권이 다른 스레드로 이전될 수 있을음 표시하는 마커다.
- 러스트의 거의 모든 타입은 Send Trait를 구현하지만 몇가지 예외도 있다.
Rc<T>
는 Send Trait를 구현하지 않기 때문에 Rc<T>
값을 복제해서 그 소유권을 다른 스레드로 이전하면 두 스레드가 동시에 참조 카운터를 변경하게된다. 그래서 Rc<T>
는 스레드 안정성을 위한 성능 손실이 없는 단일 스레드 환경에서만 사용해야 한다.
- Sync Trait는 여러 스레드가 타입을 안전하게 참조할 수 있음을 표시하기 위한 트레이트다.
- 어떤 타입 T가 Sync Trait을 구현하고 있고, &T(T의 참조)가 Send Trait를 구현하고 있다면 이 참조는 다른 스레드로 안전하게 전달 할 수 있다.
- 거의 모든 기본 자료형은 역시 Sync Trait를 구현하고 있다.
Rc<T>
스마트 포인터는 Send Trait를 구현하지 않는 것과 같은 이유로 Sync Trait도 구현하지 않는다.
- Send와 Sync Trait로 구성된 타입은 자동으로 구현되기 때문에 트레이트를 직접 구현할 필요가 없을 뿐더러 직접 구현하는것은 안전하지 않다. 게다가 마커 Trait이기 때문에 따로 구현해야할 메서드도 없다.