Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

スレッド化 - C++ SDK

項目一覧

  • 従う 3 つのルール
  • スレッド間の通信
  • スレッド間で インスタンスを渡す
  • スレッド間で同じ Realm を使用
  • スレッド間で不変のコピーを渡す
  • スケジューラー(実行ループ)
  • Realm の更新
  • Realm のスレッドモデルの詳細
  • Gitとの比較と対比
  • 内部構造

パフォーマンスのあるアプリを作成するには、開発者は、デッドロックや競合状態などの問題を回避する、スレッドセーフで維持可能なマルチスレッド コードを記述する必要があります。 Realm は、パフォーマンスのあるマルチスレッド アプリ専用に設計されたツールを提供します。

Realm のマルチスレッド アプリ用ツールを調べる前に、次の 3 つのルールを理解し、従う必要があります。

読み取りをロックしないでください。
Realm の Multiversion Concurrency Control (MVCC)アーキテクチャにより、読み取り操作のためにロックが必要なくなります。 読み込んだ値が破損したり、部分的に変更された状態になったりすることはありません。 ロックやミューテックスを必要とせずに、任意のスレッドで同じ Realm ファイルから自由に読み取りができます。 各スレッドは読み取りを行う前にすべてのスレッドを待機する必要があるため、不必要にロックするとパフォーマンスのボトルネックが発生します。
バックグラウンド スレッドで書き込みを行う場合は、UI スレッドでの同期書き込みを避けます。
Realm ファイルにはどのスレッドからでも書き込みができますが、一度に書き込みできるのは 1 つだけです。 その結果、同期書込みトランザクション (write transaction) は相互にブロックされます。 UI スレッドで同期書込み (write) を行うと、バックグラウンド スレッドへの書込みが完了するのを待機している間にアプリが応答しなくなる可能性があります。 Device Syncはバックグラウンド スレッドで書込み (write) を実行するため、同期された Realm を持つ UI スレッドでの同期書込みを避ける必要があります。
ライブ オブジェクト、コレクション、または Realm を他のスレッドに渡さないでください。
ライブ オブジェクト、コレクション、Realm インスタンスはスレッド定義です。つまり、これらは作成されたスレッドでのみ有効です。 具体的には、ライブ インスタンスを他のスレッドに渡すことはできません。 ただし、Realm にはスレッド間でオブジェクトを共有するためのいくつかのメカニズムが用意されています。

異なるスレッドから同じ Realm ファイルにアクセスするには、アクセスが必要なすべてのスレッドで Realm インスタンスをインスタンス化する必要があります。 As long as you specify the same configuration, all realm instances will map to the same file on disk.

マルチスレッド環境で Realm を使用する際の重要なルールの 1 つは、 オブジェクトはスレッド定義であることです。他のスレッドで発生した Realm、コレクション、またはオブジェクトのインスタンスにはアクセスできません。 Realm のMultiversion Concurrency Control (MVCC)アーキテクチャでは、オブジェクトのアクティブなバージョンが常に多数存在する可能性があります。 スレッド制限により、そのスレッド内のすべての インスタンスが同じ内部バージョンになります。

スレッド間での通信が必要な場合は、ユースケースに応じていくつかのオプションがあります。

  • 2 つのスレッド上のオブジェクトを変更するには、両方のスレッドでオブジェクトをクエリします。

  • React任意のスレッドで行われた変更に対応するには、Realm の 通知 を使用し 。

  • 現在のスレッドの Realm インスタンス内の別のスレッドに発生した変更を確認するには、Realm インスタンスを更新します。

  • Realm または特定のオブジェクトのインスタンスを別のスレッドと共有するには、 Realmインスタンスまたはオブジェクトへのスレッド_セーフ_リファレンスを共有します。

  • オブジェクトの高速で読み取り専用のビューを他のスレッドに送信するには、オブジェクトを「フリーズ」します。

realm::realmrealm::resultsrealm::objectのインスタンスはスレッド定義です。 つまり、それらを作成したスレッドでのみ使用できます。

スレッド認証された インスタンスは、次のように別のスレッドにコピーできます。

  1. スレッド定義オブジェクトを使用してスレッド_セーフ_参照を初期化します。

  2. ターゲット スレッドに参照を渡します。

  3. ターゲット スレッドの参照を解決します。 参照されたオブジェクトが Realm インスタンスの場合は、 .resolve()を呼び出して解決します。そうでない場合は、参照をrealm.resolve()に移動します。 返されるオブジェクトは、元のスレッドではなくターゲット スレッドで作成されたかのように、ターゲット スレッドでスレッド定義されています。

重要

thread_safe_referenceは 1 回だけ解決する必要があります。 それ以外の場合、参照が解放されるまで、ソース邦土は固定されたままになります。 このため、 thread_safe_referenceの有効期間は短くする必要があります。

// Put a managed object into a thread safe reference
auto threadSafeItem =
realm::thread_safe_reference<realm::Item>{managedItem};
// Move the thread safe reference to a background thread
auto thread =
std::thread([threadSafeItem = std::move(threadSafeItem), path]() mutable {
// Open the database again on the background thread
auto backgroundConfig = realm::db_config();
backgroundConfig.set_path(path);
auto backgroundRealm = realm::db(std::move(backgroundConfig));
// Resolve the Item instance via the thread safe
// reference
auto item = backgroundRealm.resolve(std::move(threadSafeItem));
// ... use item ...
});
// Wait for thread to complete
thread.join();

別のスレッド上のオブジェクトを操作する別の方法は、そのスレッドでそのオブジェクトを再度クエリすることです。 しかし、オブジェクトにプライマリキーがない場合、そのオブジェクトをクエリするのは簡単ではありません。 プライマリキーがあるかどうかに関係なく、任意のオブジェクトでthread_safe_referenceを使用できます。

スレッド間で Realm インスタンスを共有することはできません。

スレッド間で同じ Realm ファイルを使用するには、各スレッドで異なる Realm インスタンスを開きます。 同じ構成を使用している限り、すべての Realm インスタンスがディスク上の同じ ファイルにマッピングされます。

スレッドで区切られたライブ オブジェクトはほとんどの場合正常に動作します。 ただし、一部のアプリ(リアクティブなイベントストリームベースのアーキテクチャに基づくものなど)では、UI スレッドで最終的に終了する前に、処理のために不変のコピーを多数のスレッドに送信する必要があります。 毎回深いコピーを作成するとコストが高くなるため、Realm ではライブ インスタンスをスレッド間で共有できません。 この場合、オブジェクト、コレクション、Realm をフリーズして解凍できます。

固定化により、特定のオブジェクト、コレクション、または Realm の不変のビューが作成されます。 固定されたオブジェクト、コレクション、または Realm は引き続きディスク上に存在するため、他のスレッドに渡す際に深くコピーする必要はありません。 スレッドの問題に関係なく、固定されたオブジェクトをスレッド間で自由に共有できます。 Realm を固定すると、その子オブジェクトも固定されます。

凍結されたオブジェクトはライブではなく、自動的に更新されません。 実質的に、停止時にオブジェクトの状態のスナップショットです。 オブジェクトを解凍すると、固定されているオブジェクトのライブ バージョンが返されます。

Realm、コレクション、またはオブジェクトをフリーズするには、 .freeze()メソッドを呼び出します。

auto realm = realm::db(std::move(config));
// Get an immutable copy of the database that can be passed across threads.
auto frozenRealm = realm.freeze();
if (frozenRealm.is_frozen()) {
// Do something with the frozen database.
// You may pass a frozen realm, collection, or objects
// across threads. Or you may need to `.thaw()`
// to make it mutable again.
}
// You can freeze collections.
auto managedItems = realm.objects<realm::Item>();
auto frozenItems = managedItems.freeze();
CHECK(frozenItems.is_frozen());
// You can read from frozen databases.
auto itemsFromFrozenRealm =
frozenRealm.objects<realm::Item>();
CHECK(itemsFromFrozenRealm.is_frozen());
// You can freeze objects.
auto managedItem = managedItems[0];
auto frozenItem = managedItem.freeze();
CHECK(frozenItem.is_frozen());
// Frozen objects have a reference to a frozen realm.
CHECK(frozenItem.get_realm().is_frozen());

固定されたオブジェクトを操作している場合、次のいずれかを実行しようとすると例外がスローされます。

  • 固定された Realm で書込みトランザクションを開始する。

  • 固定されたオブジェクトを変更します。

  • 固定された Realm、コレクション、またはオブジェクトへの変更リスナーの追加。

.is_frozen()を使用して、オブジェクトが固定されているかどうかを確認できます。 これは常にスレッドセーフです。

if (frozenRealm.is_frozen()) {
// Do something with the frozen database.
// You may pass a frozen realm, collection, or objects
// across threads. Or you may need to `.thaw()`
// to make it mutable again.
}

凍結されたオブジェクトは、それを生成したライブ環境が開いている限り有効のままです。 したがって、すべてのスレッドが固定されたオブジェクトの処理を完了するまで、ライブ邦土を閉じないようにします。 ライブ Realm が閉じられる前に、固定された Realm を閉じることができます。

重要

固定オブジェクトのキャッシュについて

固定オブジェクトがキャッシュされすぎると、Realm ファイルのサイズに悪影響が及ぶ可能性があります。 「多すぎる」は、特定のターゲット デバイスと Realm オブジェクトのサイズによって異なります。 多数のバージョンをキャッシュする必要がある場合は、Realm から必要なものをコピーすることを検討してください。

固定されたオブジェクトを変更するには、オブジェクトを解凍する必要があります。 あるいは、解凍されていない Realm でその をクエリしてから変更することもできます。 ライブ オブジェクト、コレクション、または Realm で.thaw()を呼び出すと、それ自体が返されます。

オブジェクトまたはコレクションを解凍すると、参照する Realm も解凍されます。

// Read from a frozen database.
auto frozenItems = frozenRealm.objects<realm::Item>();
// The collection that we pull from the frozen database is also frozen.
CHECK(frozenItems.is_frozen());
// Get an individual item from the collection.
auto frozenItem = frozenItems[0];
// To modify the item, you must first thaw it.
// You can also thaw collections and realms.
auto thawedItem = frozenItem.thaw();
// Check to make sure the item is valid. An object is
// invalidated when it is deleted from its managing database,
// or when its managing realm has invalidate() called on it.
REQUIRE(thawedItem.is_invalidated() == false);
// Thawing the item also thaws the frozen database it references.
auto thawedRealm = thawedItem.get_realm();
REQUIRE(thawedRealm.is_frozen() == false);
// With both the object and its managing database thawed, you
// can safely modify the object.
thawedRealm.write([&] { thawedItem.name = "Save the world"; });

固定のコレクションに追加する場合は、コレクションを含む オブジェクトと追加するオブジェクトの両方を解凍する必要があります。

固定されたオブジェクトをスレッド間で渡す場合も同じルールが適用されます。 一般的なケースとして、バックグラウンド スレッドで関数を呼び出して、UI をブロックする代わりに処理を実行する場合があります。

この例では、固定された Realm 内の 2 つのオブジェクトをクエリします。

  • Itemオブジェクトのリスト プロパティを持つProjectオブジェクト

  • Itemオブジェクト

ItemProjectitemsリスト コレクションに追加する前に、両方のオブジェクトを認可する必要があります。 Projectオブジェクトのみを生成し、 Itemは生成しない場合、Realm はエラーをスローします。

// Get frozen objects.
// Here, we're getting them from a frozen database,
// but you might also be passing them across threads.
auto frozenItems = frozenRealm.objects<realm::Item>();
// The collection that we pull from the frozen database is also frozen.
CHECK(frozenItems.is_frozen());
// Get the individual objects we want to work with.
auto specificFrozenItems = frozenItems.where(
[](auto const& item) { return item.name == "Save the cheerleader"; });
auto frozenProjects =
frozenRealm.objects<realm::Project>().where(
[](auto const& project) {
return project.name == "Heroes: Genesis";
});
;
auto frozenItem = specificFrozenItems[0];
auto frozenProject = frozenProjects[0];
// Thaw the frozen objects. You must thaw both the object
// you want to append and the object whose collection
// property you want to append to.
auto thawedItem = frozenItem.thaw();
auto thawedProject = frozenProject.thaw();
auto managingRealm = thawedProject.get_realm();
managingRealm.write([&] { thawedProject.items.push_back(thawedItem); });
アイテムモデル
struct Item {
std::string name;
};
REALM_SCHEMA(Item, name)
プロジェクトモデル
struct Project {
std::string name;
std::vector<realm::Item*> items;
};
REALM_SCHEMA(Project, name, items)

一部のプラットフォームまたはフレームワークでは、アプリの有効期間中にイベントを継続的に処理するスケジューラー(またはループを実行)が自動的に設定されます。 Realm C++ SDK は、次のプラットフォームまたはフレームワーク上のスケジューラーを検出し、使用します。

  • macOS, iOS, tvOS, watchOS

  • Android

  • Qt

Realm は スケジューラーを使用して、Device Sync のアップロードやダウンロードなどの作業をスケジュールします。

プラットフォームにサポートされているスケジューラーがない場合、またはカスタムスケジューラーを使用する場合は、Realm::schema を実装し、 インスタンスを Realm の構成に使用する Realm::db_config に 渡します。Realm は渡したスケジューラーを使用します。

struct MyScheduler : realm::scheduler {
MyScheduler() {
// ... Kick off task processor thread(s) and run until the scheduler
// goes out of scope ...
}
~MyScheduler() override {
// ... Call in the processor thread(s) and block until return ...
}
void invoke(std::function<void()>&& task) override {
// ... Add the task to the (lock-free) processor queue ...
}
[[nodiscard]] bool is_on_thread() const noexcept override {
// ... Return true if the caller is on the same thread as a processor
// thread ...
}
bool is_same_as(const realm::scheduler* other) const noexcept override {
// ... Compare scheduler instances ...
}
[[nodiscard]] bool can_invoke() const noexcept override {
// ... Return true if the scheduler can accept tasks ...
}
// ...
};
int main() {
// Set up a custom scheduler.
auto scheduler = std::make_shared<MyScheduler>();
// Pass the scheduler instance to the realm configuration.
auto config = realm::db_config{path, scheduler};
// Start the program main loop.
auto done = false;
while (!done) {
// This assumes the scheduler is implemented so that it
// continues processing tasks on background threads until
// the scheduler goes out of scope.
// Handle input here.
// ...
if (shouldQuitProgram) {
done = true;
}
}
}

スケジューラーまたは実行ループによって制御される任意のスレッドでは、Realm はすべての実行ループ反復の開始時にオブジェクトを自動的に更新します。 ループの反復処理の合間に、スナップショットを操作するため、個々のメソッドは常に一貫したビューを参照し、他のスレッドで何が起こるかを心配する必要はありません。

スレッドでRealmを初めて開くと、その状態は最新の成功した書込みコミットの状態になり、更新されるまでそのバージョンは維持されます。 スレッドが実行ループによって制御されていない場合、 Realm.refresh() トランザクションを最新の状態に進めるには、 メソッドを手動で呼び出す必要があります。

realm.refresh();

注意

Realm を定期的に更新しないと、一部のトランザクション バージョンが「固定」され、そのバージョンで使用されるディスク領域が再利用されなくなり、ファイル サイズが大きくなる可能性があります。

Realmは マルチバージョン同時実行制御(MVCC) により、安全で高速、ロックフリーの同時アクセスを提供します。 アーキテクチャ。

Git のような分散バージョン管理システムに理解がある場合 、MVCC について直感的に理解しているかもしれません。Git の 2 つの基本要素は次のとおりです。

  • アトミックな書込みである コミット 。

  • ブランチはコミット履歴の異なるバージョンです。

同様に、Realm にはトランザクションの形式でアトミックにコミットされた書込み (write) があります。 Realm には、ブランチと同様に、常に多くの異なるバージョンの履歴があります。

フォークを通じて分散と相違をアクティブにサポートする Git とは異なり、Realm には常に最新バージョンが 1 つだけあり、常にその最新バージョンのヘッドに書込まれます。 Realm は以前のバージョンに書込むことはできません。 つまり、データは 1 つの最新バージョンの真実のバージョンに変換されます。

Realm は B+ ツリー を使用して実装されています データ構造。最上位の ノードは Realm のバージョンを表します。子ノードは、そのバージョンの Realm 内のオブジェクトです。 Realm には最新バージョンへのポインターがあります。これは、Git が ヘッドコミット へのポインターを持っているのと同様です。

Realm はコピーオンライト手法を使用して 分離 を確保します と の 耐久性 。変更を加えると、Realm は書き込みのためにツリーの関連部分をコピーします。 その後、Realm は 2 つのフェーズで変更をコミットします。

  • Realm は変更をディスクに書き込み、成功を確認します。

  • 次に、Realm は最新バージョンのポインターを新しく書込まれたバージョンを指すように設定します。

この 2 段階のコミット プロセスにより、書込み (write) が部分的に失敗した場合でも、ツリーの関連する部分のコピーに対して変更が行われていたため、元のバージョンは一切破損しないことが保証されます。 同様に、Realm のルートポインターは、新しいバージョンの有効性が保証されるまで、元のバージョンを指します。

次の図は、コミット プロセスを示しています。

Realm は書き込み用にツリーの関連部分をコピーし、ポインターを更新して最新バージョンに置き換えます。
クリックして拡大します
  1. Realm はツリーとして構成されています。 Realm には最新バージョン V1 へのポインターがあります。

  2. 書き込み時に、Realm は V1 に基づいて新しいバージョン V2 を作成します。 Realm は変更対象のオブジェクトのコピー(A 1 、C 1 )を作成しますが、変更されていないオブジェクトへのリンクは引き続き元のバージョン(B、D)を指します。

  3. コミットを検証した後、Realm はポインターを新しい最新バージョン(V2)に更新します。 その後、Realm はツリーに接続されなくなった古いノードを破棄します。

Realm はメモリ マッピングなどのゼロコピー メソッドを使用してデータを処理します。 Realm から値を読み取る場合、ほぼそのコピーではなく、実際のディスク上の値が表示されます。 これがライブ オブジェクトの基盤です。 これは、ディスクへの書き込みが検証された後に、Realm ヘッダー ポインターを新しいバージョンを指すように設定できる理由でもあります。

戻る

フィルター データ