Rust는 C ++의 동시성 기능과 어떻게 다른가요?


35

질문

Rust를 배우기 위해 시간을 소비해야하는지 결정하기 위해 Rust가 C ++의 동시성 기능을 근본적으로 충분히 향상시키는 지 이해하려고 노력하고 있습니다.

구체적으로, 관용적 녹은 관용적 C ++의 동시성 기능을 어떻게 향상 시키나요?

개선 (또는 발산)은 대부분 구문 상입니까, 아니면 실질적으로 패러다임의 개선 (발산)입니까? 아니면 다른 것입니까? 아니면 실제로 개선 (분화)이 아닙니까?


이론적 해석

나는 최근에 C ++ 14의 동시성 기능을 가르치려고 노력해 왔으며 뭔가 잘못된 느낌이 들었습니다. 기분이 상쾌합니다. 무엇 오프 느낌? 말하기 어렵다.

컴파일러가 동시성에 관한 올바른 프로그램을 작성하도록 실제로 도움을 주려고하지 않은 것처럼 느껴집니다. 마치 컴파일러가 아닌 어셈블러를 사용하는 것처럼 느껴집니다.

분명히, 동시성에 관해서는 미묘하고 결함이있는 개념으로 고통받는 것은 전적으로 가능합니다. 아마도 나는 스테이트 풀 프로그래밍과 데이터 레이스 사이의 Bartosz Milewski의 긴장을 풀지 않았을 것입니다. 어쩌면 컴파일러에 사운드 동시 방법론이 얼마나 많은지 그리고 OS에 얼마나 많은지 잘 모르겠습니다.

답변:


56

더 나은 동시성 이야기는 Rust 프로젝트의 주요 목표 중 하나이므로 목표 달성을 위해 프로젝트를 신뢰한다면 개선이 필요합니다. 완전 면책 조항 : 나는 Rust에 대해 높은 의견을 가지고 있으며 그것에 투자하고 있습니다. 요청에 따라 가치 판단을 피하고 (IMHO) 개선 보다는 차이점을 설명하려고 노력할 것 입니다.

안전하고 안전하지 않은 녹

"녹"은 두 가지 언어로 구성되어 있습니다. 하나는 시스템 프로그래밍의 위험으로부터 당신을 격리시키기 위해 열심히 노력하며, 그러한 열망이없는 더 강력한 언어입니다.

안전하지 않은 녹은 불쾌하고 잔인한 언어로 C ++과 매우 흡사합니다. 임의로 위험한 일을하고, 하드웨어와 대화하고, 메모리를 수동으로 관리하고, 발로 자신을 쏠 수 있습니다. 프로그램의 정확성이 궁극적으로 당신의 손에 있다는 점에서 C와 C ++와 매우 흡사합니다. 그와 관련된 다른 모든 프로그래머의 손. 키워드를 사용하여이 언어를 선택하면 unsafeC 및 C ++에서와 같이 단일 위치에서 한 번의 실수만으로 전체 프로젝트가 중단 될 수 있습니다.

Safe Rust는 "기본"이며, 대부분의 Rust 코드는 안전하며 코드에서 키워드 unsafe를 언급 하지 않으면 안전한 언어를 떠나지 않습니다. 이 코드의 나머지 부분은 unsafe코드가 안전 Rust가 당신에게 너무 열심히 일한다는 보장을 모두 어길 수 있기 때문에 대부분 그 언어와 관련이 있습니다 . 반대로, unsafe코드는 사악 하지 않으며 커뮤니티에서 그렇게 취급하지 않습니다 (그러나 필요하지 않을 때는 강력히 권장하지 않습니다).

안전한 코드가 사용하는 추상화를 만들 수 있기 때문에 위험하지만 중요합니다. 안전하지 않은 코드는 형식 시스템을 사용하여 다른 사람이 코드를 잘못 사용하지 못하도록 방지하므로 Rust 프로그램에 안전하지 않은 코드가 있으면 안전 코드를 방해 할 필요가 없습니다. Rust의 타입 시스템에는 C ++에는없는 툴이 있고 동시성 추상화를 구현하는 안전하지 않은 코드가 이러한 툴을 효과적으로 사용하기 때문에 다음과 같은 차이점이 있습니다.

비 차이 : 공유 / 변경 가능한 메모리

Rust는 메시지 전달에 더 중점을두고 공유 메모리를 매우 엄격하게 제어하지만 공유 메모리 동시성을 배제하지 않고 공통 추상화 (잠금, 원자 연산, 조건 변수, 동시 콜렉션)를 명시 적으로 지원합니다.

또한 C ++ 및 기능적 언어와 달리 Rust는 전통적인 명령형 데이터 구조를 정말 좋아합니다. 표준 라이브러리에는 영구 / 불변 링크 목록이 없습니다. 있다 std::collections::LinkedList그러나 그것은처럼 std::listC ++에서와 같은 이유로 좌절 std::list(캐시의 잘못된 사용).

그러나 Rust는이 섹션의 제목 ( "공유 / 변경 가능 메모리")과 관련하여 C ++과 하나의 차이점이 있습니다. 즉, 메모리를 "공유 XOR 변경 가능", 즉 메모리가 동일하게 공유 및 변경 가능하지 않아야합니다. 시각. "자신의 스레드의 개인 정보 보호"에서 원하는대로 메모리를 변경하십시오. 이것을 공유 가변 메모리가 기본 옵션이며 널리 사용되는 C ++과 대조하십시오.

공유 xor- 변경 가능 패러다임은 아래의 차이점에 매우 중요하지만, 익숙해지는 데 시간이 걸리고 상당한 제약이 따르는 매우 다른 프로그래밍 패러다임이기도합니다. 때때로이 패러다임을 선택 해제해야합니다 (예 : 원자 유형) ( AtomicUsize공유 가변 메모리의 본질 임). 잠금은 또한 동시 읽기 및 쓰기를 배제하므로 (한 스레드 쓰기 동안 다른 스레드는 읽거나 쓸 수 없음) 공유 xor 변경 가능 규칙을 준수합니다 .

비 차이 : 데이터 레이스는 정의되지 않은 동작 (UB)

Rust 코드에서 데이터 레이스를 시작하면 C ++에서와 마찬가지로 게임이 끝납니다. 모든 베팅이 종료되었으며 컴파일러는 원하는대로 할 수 있습니다.

그러나 안전한 Rust 코드에 데이터 레이스 (또는 그 문제에 대한 UB) 가 없다는 것은 확실한 보장 이 아닙니다 . 이것은 핵심 언어와 표준 라이브러리로 확장됩니다. unsafeUB를 트리거 하는 (타사 라이브러리를 포함하지만 표준 라이브러리 제외) 사용하지 않는 Rust 프로그램을 작성할 수 있다면 버그로 간주되어 수정됩니다 (이미 여러 번 발생했습니다). 물론 이것은 C ++과는 대조적으로 UB로 프로그램을 작성하는 것이 쉽지 않습니다.

차이점 : 엄격한 잠금 규칙

C는 달리 ++, 녹 (의 잠금 std::sync::Mutex, std::sync::RwLock등) 소유 가 보호하는 것 데이터를. 잠금을 설정 한 다음 설명서에서만 잠금과 관련된 일부 공유 메모리를 조작하는 대신 잠금을 유지하지 않으면 공유 데이터에 액세스 할 수 없습니다. RAII 가드는 잠금을 유지하고 동시에 잠금 된 데이터에 대한 액세스를 제공합니다 (이 정도는 C ++로 구현할 수 있지만 std::잠금 은 아닙니다 ). 수명 시스템은 잠금을 해제 한 후 데이터에 계속 액세스 할 수 없도록합니다 (RAII 가드 제거).

물론 유용한 데이터가없는 잠금 ( Mutex<()>) 을 가질 수 있으며 , 해당 잠금과 명시 적으로 연결하지 않고 일부 메모리 만 공유 할 수 있습니다. 그러나 잠재적으로 동기화되지 않은 공유 메모리가 있어야합니다 unsafe.

차이 : 우발적 공유 방지

메모리를 공유 할 수는 있지만 명시 적으로 요청할 때만 공유합니다. 예를 들어 메시지 전달 (예 :의 채널 std::sync)을 사용하는 경우 수명 시스템은 다른 스레드로 보낸 후에 데이터에 대한 참조를 유지하지 않습니다. 잠금 뒤에서 데이터를 공유하려면 잠금을 명시 적으로 구성하여 다른 스레드에 제공하십시오. 동기화되지 않은 메모리를 나와 공유하려면 unsafe을 사용해야 unsafe합니다.

이것은 다음 지점과 관련이 있습니다.

차이점 : 스레드 안전성 추적

Rust 타입 시스템은 스레드 안전 개념을 추적합니다. 특히, Sync특성은 데이터 경쟁의 위험없이 여러 스레드가 공유 할 수있는 유형을 나타내며 Send한 스레드에서 다른 스레드로 이동할 수있는 유형을 나타냅니다 . 이는 프로그램 전체에서 컴파일러에 의해 시행되므로 라이브러리 디자이너는 이러한 정적 검사 없이는 어리석게 위험한 최적화를 수행합니다. 예를 들어, 여러 스레드에서 std::shared_ptra shared_ptr가 사용되는 경우 UB를 피하기 위해 항상 원자 연산을 사용하여 참조 카운트를 조작하는 C ++ . 녹 있습니다 RcArc해당에 차이가있는 Rc 용도 이외의 원자 refcount는 운영 및 스레드 (즉, 구현하지 않습니다하지 않습니다 Sync또는 Send) 상태가 Arc매우 흡사shared_ptr (두 특성을 모두 구현).

유형 unsafe 수동으로 동기화를 구현 하는 데 사용 하지 않는 경우 특성의 유무가 올바르게 추론됩니다.

차이 : 매우 엄격한 규칙

컴파일러가 일부 코드에 데이터 레이스 및 다른 UB가 없음을 절대 확신 할 수 없으면 period는 컴파일되지 않습니다 . 앞에서 언급 한 규칙과 기타 도구를 사용하면 훨씬 멀리 갈 수 있지만 조만간 올바른 작업을 수행하려고하지만 컴파일러의 통지를 벗어나는 미묘한 이유가 있습니다. 잠금이없는 까다로운 데이터 구조 일 수도 있지만 "공유 배열의 임의 위치에 쓰지만 모든 위치가 하나의 스레드 만 작성되도록 인덱스가 계산 됨"과 같은 평범한 것일 수도 있습니다.

이 시점에서 글 머리 기호를 물고 불필요한 동기화를 약간 추가하거나 컴파일러가 정확성을 보거나 (가능할 때가 종종 어렵거나 때로는 불가능한 경우) 코드를 변경하거나 코드에 빠질 수 있습니다 unsafe. 그럼에도 불구하고 추가적인 정신적 오버 헤드이며 Rust는 unsafe코드 의 정확성을 보장하지 않습니다 .

차이 : 적은 도구

앞에서 언급 한 차이점 때문에 Rust에서는 데이터 경쟁이있는 코드를 작성하는 경우 (또는 사용 후 사용 가능 또는 이중 사용 가능 또는 이중 사용 가능)가 훨씬 더 드 '니다. 이것이 좋지만, 이러한 오류를 추적하기위한 생태계가 청소년과 소규모 커뮤니티를 고려할 때 예상되는 것보다 훨씬 저개발 된 것은 불행한 부작용이 있습니다.

valgrind 및 LLVM의 thread sanitizer와 같은 도구는 원칙적으로 Rust 코드에 적용될 수 있지만, 실제로 작동하는지 여부는 도구마다 다릅니다 (특히 도구를 찾을 수 없기 때문에 작동하기 어려운 도구도 가능) 방법에 대한 -date 자원). Rust가 현재 실제 사양, 특히 공식 메모리 모델이 부족하다는 사실은 실제로 도움이되지 않습니다.

요컨대, unsafeRust 코드를 올바르게 작성하는 것은 C ++ 코드를 올바르게 작성하는 것보다 어렵 습니다. 두 언어 모두 기능과 위험 측면에서 대략 비교할 수 있습니다. 물론 이것은 전형적인 Rust 프로그램이 비교적 적은 양의 unsafe코드 만을 포함 할 것이라는 사실에 비추어야만 하지만 C ++ 프로그램은 완전히 C ++입니다.


6
화면의 +25 upvote 스위치는 어디에 있습니까? 찾을 수 없습니다! 이 유익한 답변은 대단히 감사합니다. 그것이 다루는 요점에 대해서는 분명한 질문이 없습니다. 다른 점으로는 Rust의 문서를 이해한다면 Rust는 [a] 통합 테스트 시설과 [b] Cargo라는 빌드 시스템을 가지고 있습니다. 이러한 관점에서 합리적으로 생산 준비가 되었습니까? 또한 Cargo와 관련하여 빌드 프로세스에 쉘, Python 및 Perl 스크립트, LaTeX 컴파일 등을 추가하는 것이 유머합니까?
thb

2
@thb 테스트 대상은 매우 중요하지만 (예 : 조롱 없음) 기능적입니다. 카고는 Rust와 재현성에 중점을두고 있지만 소스 코드에서 최종 아티팩트에 이르는 모든 단계를 다루는 최선의 옵션이 아닐 수 있음을 의미합니다. 당신은 쓸 수있는 빌드 스크립트를 하지만 당신이 언급하는 모든 것들에 대한 적절하지 않을 수 있습니다. (그러나 사람들은 정기적으로 빌드 스크립트를 사용하여 C 라이브러리를 컴파일하거나 기존 버전의 C 라이브러리를 찾으므로 순수한 녹 이상을 사용할 때 카고가 작동을 멈추는 것처럼 보이지 않습니다.)

2
그건 그렇고, 가치가있는 것에 대해, 당신의 대답은 꽤 결정적입니다. C ++을 좋아하기 때문에 C ++에는 거의 필요한 모든 기능이 갖추어져 있기 때문에 C ++이 안정적이고 널리 사용되기 때문에 가능한 모든 비 가벼움 목적으로 C ++을 사용하는 데 만족했습니다 (Java에 대한 관심을 개발 한 적이 없습니다) 예를 들어). 하지만 지금 우리는 동시성을 가지고 있고, C ++ (14)가 보인다 나에게 그것을 고민 할 수 있습니다. 나는 10 년 안에 자발적으로 새로운 프로그래밍 언어를 시도하지는 않았지만 (Haskell이 더 나은 옵션으로 나타나지 않으면) Rust를 시도해야한다고 생각합니다.
thb

Note that if a type doesn't use unsafe to manually implement synchronization, the presence or absence of the traits are inferred correctly.실제로는 여전히 unsafe요소로도 수행됩니다. 원시 포인터만이 아니 Sync거나 Share기본 포인터를 포함하는 구조체 에는 아무것도 없습니다 .
Hauleth

ŁukaszNiemier @ 괜찮아 운동 일어날 수 있지만, 안전하지 않은-사용 유형은 바람 수있는 억원 방법이있다 Send거나 Sync심지어는 정말 안하지만은.

-2

녹도 얼랭과 바둑과 비슷합니다. 버퍼 및 조건부 대기가있는 채널을 사용하여 통신합니다. Go와 마찬가지로 공유 메모리를 수행하고 원자 참조 카운트 및 잠금을 지원하며 스레드에서 스레드로 채널을 전달할 수 있도록함으로써 Erlang의 제한을 완화합니다.

그러나 Rust는 한 걸음 더 나아갑니다. Go는 당신이 옳은 일을한다고 믿지만 Rust는 당신과 함께 앉아 잘못된 일을하려고하면 멘토를 배정합니다. Rust의 멘토는 컴파일러입니다. 스레드를 통해 전달되는 값의 소유권을 결정하고 잠재적 인 문제가있는 경우 컴파일 오류를 제공하기 위해 정교한 분석을 수행합니다.

다음은 RUST 문서에서 인용 한 것입니다.

소유권 규칙은 안전한 동시 코드 작성을 지원하므로 메시지 전송에 중요한 역할을합니다. 동시 프로그래밍에서 오류를 방지하는 것은 Rust 프로그램 전체에서 소유권에 대해 생각할 필요가 있다는 이점을 통해 얻을 수있는 이점입니다. — 가치의 소유권을 가진 메시지 전달.

Erlang이 draconian이고 Go가 자유 상태 인 경우 Rust는 유모 상태입니다.

프로그래밍 언어의 동시성 이데올로기 에서 자세한 정보를 찾을 수 있습니다 : Java, C #, C, C +, Go 및 Rust


2
Stack Exchange에 오신 것을 환영합니다! 자신의 블로그에 링크 할 때마다 명시 적으로 명시해야합니다. 도움말 센터를 참조하십시오 .
Glorfindel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.