스레딩 - .NET SDK
C#/.NET 앱을 빠르고 응답성이 뛰어나게 만들려면 시각 자료를 배치하고 사용자 상호작용을 처리하는 데 필요한 컴퓨팅 시간과 데이터를 처리하고 비즈니스 로직을 실행하는 데 필요한 시간의 균형을 맞춰야 합니다. 일반적으로 앱 개발자는 모든 사용자 인터페이스 관련 작업을 위한 메인 스레드 또는 UI 스레드, 프레젠테이션을 위해 UI 스레드로 전송하기 전에 더 많은 워크로드를 계산하는 하나 이상의 백그라운드 스레드 등 여러 스레드에 작업을 분산합니다. 많은 작업을 백그라운드 스레드로 오프로드함으로써 UI 스레드는 워크로드의 크기에 관계없이 높은 반응성을 유지할 수 있습니다. 그러나 교착 상태 및 경쟁 상태와 같은 문제를 방지하면서 스레드로부터 안전하고 성능이 뛰어나며 유지 관리가 가능한 멀티스레드 코드를 작성하는 것은 매우 어려울 수 있습니다. Realm은 이 과정을 간소화하는 것을 목표로 합니다.
중요
SynchronizationContext 스레드
이 페이지 전체에서 '메인 스레드'(또는 ' UI 스레드')와 ' 배경 스레드'를 지칭합니다. 더 정확하게는 메인 스레드 또는 UI 스레드에 대한 언급은 SynchronizationContext 가 있는 모든 스레드를 의미합니다. 이(가) 없는 스레드는 SynchronizationContext
배경 스레드로 간주됩니다.
따라야 할 세 가지 규칙
멀티스레드 앱을 위한 Realm의 도구를 살펴보기 전에 다음 세 가지 규칙을 이해하고 따라야 합니다.
- 읽기 위해 락하지 않도록 합니다.
- Realm의 MVCC(Multiversion Concurrency Control) 아키텍처는 읽기 작업을 잠글 필요가 없습니다. 읽은 값은 절대로 손상되거나 부분적으로 수정된 상태가 되지 않습니다. 잠금이나 뮤텍스 없이도 모든 스레드에서 동일한 Realm 파일을 자유롭게 읽을 수 있습니다. 각 스레드가 읽기 전에 차례를 기다려야 할 수 있으므로 불필요하게 잠그는 것은 성능 병목 현상이 될 수 있습니다.
- UI 스레드에서 동기식 쓰기를 방지합니다.
- 모든 스레드에서 Realm 파일 에 쓰기 (write) 수 있지만 한 번에 하나의 작성자만 있을 수 있습니다. 동기식 쓰기 (write) 트랜잭션(write transaction)은 서로를 차단 합니다. 따라서 메인 스레드에서 동기식 쓰기 (write) 를 수행하면 배경 스레드에서 쓰기 (write) 가 완료될 때까지 기다리는 동안 앱 이 응답하지 않는 것처럼 보일 수 있습니다. 이를 방지하기 위해 SDK는 WriteAsync() 메서드를 제공합니다. 자세한 내용은 비동기 쓰기를 참조하세요.
- 라이브 객체, 컬렉션 또는 영역을 다른 스레드에 전달하지 않도록 합니다.
- 라이브 객체, 컬렉션 및 Realm 인스턴스는 스레드에 한정되어 있으므로 해당 인스턴스가 생성된 스레드에서만 유효합니다. 실질적으로 이는 라이브 인스턴스를 다른 스레드로 전달할 수 없음을 의미합니다. 그러나 Realm은 스레드 간에 객체를 공유하기 위한 몇 가지 메커니즘을 제공합니다.
스레드 간 커뮤니케이션
다른 스레드에서 동일한 Realm 파일에 액세스하려면 액세스가 필요한 모든 스레드에서 영역 인스턴스를 인스턴스화해야 합니다. 동일한 구성을 지정하는 한 모든 영역 인스턴스는 디스크의 동일한 파일에 매핑됩니다.
멀티스레드 환경에서 Realm으로 작업할 때 주요 규칙 중 하나는 객체는 스레드에 한정되어 있다는 것입니다. 즉, 다른 스레드에서 시작된 Realm, 컬렉션 또는 객체의 인스턴스에 액세스할 수 없습니다. Realm의 MVCC(Multiversion Concurrency Control) 아키텍처는 객체의 활성 버전이 언제든지 여러 개일 수 있음을 의미합니다. 스레드 제한은 해당 스레드의 모든 인스턴스가 동일한 내부 버전인지 확인합니다.
스레드 간에 통신해야 하는 경우 사용 사례에 따라 여러 가지 옵션이 있습니다.
Realm 새로고침
메인 UI 스레드(또는 이벤트 루프가 있는 모든 스레드)에서 Realm은 이벤트 루프가 반복될 때마다 객체를 자동으로 새로 고칩니다. 이벤트 루프를 반복하는 사이에 스냅샷으로 작업하게 되므로 개별 메서드는 항상 일관된 뷰를 볼 수 있으며 다른 스레드에서 어떤 일이 발생할지 걱정할 필요가 없습니다.
스레드에서 Realm을 처음 열면 해당 상태는 가장 최근에 성공한 쓰기 커밋이 되며 새로 고침할 때까지 해당 버전으로 유지됩니다. 스레드에 런 루프가 없는 경우(일반적으로 백그라운드 스레드에서 발생) 트랜잭션을 가장 최근 상태로 진행하려면 Realm.Refresh() 메서드를 수동으로 호출해야 합니다.
Realm은 Transaction.Commit()로 쓰기 트랜잭션(write transaction)을 커밋할 때도 새로 고쳐집니다.
참고
정기적으로 Realm을 새로 고치지 못하면 일부 트랜잭션 버전이 '고정'되어 Realm이 해당 버전에서 사용하는 디스크 공간을 재사용하지 못하게 되어 파일 크기가 커질 수 있습니다.
비동기 쓰기
WriteAsync() 메서드는 UI 스레드의 부하를 간단하게 줄일 수 있는 방법을 제공합니다. Realm은 비동기적으로 트랜잭션을 시작하고 커밋하지만 실제 쓰기 차단은 원래 스레드에서 실행됩니다. 따라서 변경 사항을 기다리는 것은 비동기식이지만 콜백은 메인 스레드에서 실행됩니다. 이는 쓰기 차단 이전에 생성된 객체와 쿼리를 스레드세이프 참조에 의존하지 않고도 차단 내에서 사용할 수 있음을 의미합니다.
다음 코드는 AsyncWrite()
을 사용하여 객체를 만드는 두 가지 예를 보여줍니다.
var testItem = new Item { Name = "Do this thing", Status = ItemStatus.Open.ToString(), Assignee = "Aimee" }; await realm.WriteAsync(() => { realm.Add(testItem); }); // Or var testItem2 = await realm.WriteAsync(() => { return realm.Add<Item>(new Item { Name = "Do this thing, too", Status = ItemStatus.InProgress.ToString(), Assignee = "Satya" }); } );
참고
백그라운드 스레드에서 WriteAsync()
를 호출하면 Realm이 스레드에서 동기적으로 실행되므로 Write()를 호출하는 것과 동일합니다.
동결된 객체
스레드에 제한된 라이브 객체는 대부분의 경우 제대로 작동합니다. 그러나 일부 앱(예: 반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱)은 최종적으로 UI 스레드에 도달하기 전에 처리를 위해 불변의 복사본을 여러 스레드로 전송해야 합니다. 매번 딥 카피를 만드는 것은 비용이 많이 들고, Realm은 라이브 인스턴스를 스레드 간에 공유하는 것을 허용하지 않습니다. 이 경우 객체, collection 및 Realm을 동결 할 수 있습니다.
동결하면 디스크에 여전히 존재하며 다른 스레드로 전달될 때 딥 카피가 필요하지 않은 특정 객체, collection 또는 영역에 대한 변경할 수 없는 뷰가 생성됩니다. 스레드 문제에 대한 걱정 없이 스레드 간에 동결된 객체를 자유롭게 공유할 수 있습니다.
동결된 객체로 작업할 때 다음 중 하나를 시도하면 예외가 발생합니다.
동결된 영역에서 쓰기 트랜잭션(write transaction) 열기
동결된 객체 수정
동결된 영역, 컬렉션 또는 객체에 변경 리스너 추가
한 번 동결된 객체는 동결 해제할 수 없습니다. IsFrozen
메서드를 사용하여 객체가 동결되었는지 확인할 수 있습니다. 이 메서드는 항상 스레드로부터 안전합니다.
동결된 객체를 수정하려면 동결되지 않은 영역에서 쿼리한 다음 수정합니다.
동결된 객체는 라이브 상태가 아니며 자동으로 업데이트되지 않습니다. 이는 사실상 정지 시점의 객체 상태에 대한 스냅샷입니다.
영역을 동결하면 그 하위 객체도 동결됩니다.
동결된 객체는 해당 객체를 생성한 라이브 영역이 열려 있는 한 유효합니다. 따라서 모든 스레드가 동결된 객체로 완료될 때까지 라이브 영역을 닫지 않도록 합니다. 라이브 Realm이 닫히기 전에 동결된 Realm을 닫을 수 있습니다.
중요
동결된 객체를 캐싱하는 경우
동결된 객체를 너무 많이 캐싱하면 영역 파일 크기에 부정적인 영향을 미칠 수 있습니다. '너무 많음'은 특정 대상 장치와 Realm 객체의 크기에 따라 다릅니다. 많은 수의 버전을 캐시해야 하는 경우에는 필요한 버전을 영역 외부로 복사하는 것이 좋습니다.
Realm의 스레딩 모델 심층 분석
Realm은 MVCC(Multiversion Concurrency Control) 를 통해 스레드 전반에 걸쳐 안전하고 빠르며 잠금 없는 동시 액세스를 제공합니다. 아키텍처.
Git과 비교 및 대조
Git 과 같은 분산된 버전 관리 시스템에 익숙한 경우 를 사용하면 이미 MVCC를 직관적으로 이해하고 있을 수 있습니다. Git의 두 가지 기본 요소는 다음과 같습니다.
커밋, 즉 원자성 쓰기입니다.
브랜치, 즉 커밋 기록의 다른 버전입니다.
마찬가지로 Realm은 트랜잭션형태의 원자 단위로 커밋된 쓰기가 있습니다. 또한 Realm은 브랜치처럼 주어진 시간에 다양한 버전의 히스토리를 갖고 있습니다.
포크를 통한 배포와 분산을 적극적으로 지원하는 Git과 달리 영역에는 항상 하나의 최신 버전만 있고 항상 최신 버전의 헤드에 데이터를 기록합니다. Realm은 이전 버전에 쓸 수 없습니다. 이는 일리가 있습니다: 데이터는 하나의 최신 버전으로 수렴되어야 합니다.
내부 구조
영역은 B-트리 데이터 구조를 사용하여 구현됩니다. 최상위 노드는 영역의 버전을 나타냅니다. 하위 노드는 해당 버전의 영역에 있는 객체입니다. 영역은 Git이 HEAD 커밋에 대한 포인터를 갖는 것과 마찬가지로 최신 버전에 대한 포인터를 가지고 있습니다.
Realm은 쓰기 중 복사(copy-on-write) 기술을 사용하여 격리 를 보장합니다. 및 내구성 . 변경을 수행하면 Realm은 트리의 관련 부분을 복사하여 기록합니다. 그런 다음 Realm은 두 단계로 변경 사항을 커밋합니다.
Realm은 변경 사항을 디스크에 기록하고 성공 여부를 확인합니다.
그런 다음 Realm은 최신 버전 포인터가 새로 작성된 버전을 가리키도록 설정합니다.
2단계 커밋 프로세스는 쓰기가 도중에 실패하더라도 트리의 관련 부분 복사본이 변경되었기 때문에 원래 버전이 어떤 식으로든 손상되지 않도록 보장합니다. 마찬가지로 영역의 루트 포인터는 새 버전이 유효하다는 것이 보장될 때까지 원본 버전을 가리킵니다.
예시
다음 다이어그램은 커밋 프로세스를 보여줍니다.
영역은 트리 구조로 되어 있습니다. 영역에는 최신 버전인 V1에 대한 포인터가 있습니다.
기록 시 Realm은 V1을 기반으로 V2 버전을 새로 만듭니다. Realm은 수정을 위해 객체의 복사본을 만들고(A 1, C 1) 수정되지 않은 객체에 대한 링크는 계속해서 원본 버전(B, D)을 가리킵니다.
커밋을 확인한 후 Realm은 포인터를 새로운 최신 버전인 V2로 업데이트합니다. 그런 다음 Realm은 더 이상 트리에 연결되지 않은 오래된 노드를 버립니다.
Realm은 메모리 매핑과 같은 제로 카피 기술을 사용하여 데이터를 처리합니다. 영역에서 값을 읽으면 복사본이 아닌 사실상 실제 디스크 값을 보는 것입니다. 이는 라이브 객체의 기본이며 디스크 쓰기가 검증된 후 영역 헤드 포인터가 새 버전을 가리키도록 설정할 수 있는 이유이기도 합니다.
요약
Realm은 다음 세 가지 규칙을 따르면 간단하고 안전한 멀티스레드 코드를 가능하게 합니다.
읽기 위해 락을 하지 마세요.
백그라운드 스레드에 쓰거나 Device Sync를 사용하는 경우 UI 스레드에서 동기식 쓰기를 방지합니다.
다른 스레드에 라이브 객체를 전달하지 마세요.
각 사용 사례에 따라 스레드 간에 객체를 공유하는 적절한 방법이 있습니다.
영역 인스턴스의 다른 스레드에 대한 변경 사항을 보려면 '루프' 스레드에 존재하지 않거나 자동 새로 고침이 비활성화된 인스턴스를 수동으로 새로 고침합니다.
반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱의 경우 객체, 컬렉션 및 영역을 동결하여 얕은 복사본을 다른 스레드에 효율적으로 전달하여 처리할 수 있습니다.
Realm의 멀티버젼 동시성 제어(MVCC) 아키텍처는 Git과 유사합니다. 그러나 Git과는 달리 Realm은 각 영역에 대해 하나의 실제 최신 버전만 있습니다.
Realm은 격리 및 내구성을 보장하기 위해 두 단계로 커밋합니다.