Segmentação - C++ SDK
Nesta página
Para criar aplicativos de alto desempenho, os desenvolvedores devem escrever código multithread seguro e sustentável que evite problemas como deadlocks e condições de corrida. O Realm fornece ferramentas projetadas especificamente para aplicativos de alto desempenho e várias threads.
Três regras a seguir
Antes de explorar as ferramentas do Realm para aplicações de várias threads, entenda e siga estas três regras:
- Não trave a leitura:
- A arquitetura decontrole de concorrência multiversão (MVCC) do Realm elimina a necessidade de bloqueio para operações de leitura. Os valores que você lê nunca serão corrompidos ou em um estado parcialmente modificado. Você pode ler livremente o mesmo Arquivo de Realm em qualquer thread sem a necessidade de bloqueios ou mutexes. O bloqueio desnecessário seria um gargalo de desempenho, pois cada thread poderia precisar esperar sua vez antes de ler.
- Evite escritas síncronas na thread da UI se escrever em uma thread em background:
- Você pode escrever em um arquivo Realm de qualquer thread, mas só pode haver um escritor por vez. Consequentemente, transações de escrita síncronas bloqueiam umas às outras. Uma escrita síncrona na thread da UI pode fazer com que seu aplicativo pareça não responsivo enquanto espera que uma escrita em uma thread de segundo plano seja concluída. O Device Sync escreve em uma thread de background, então você deve evitar escritas síncronas na thread da UI com realms sincronizados.
- Não passe objetos ativos, coleções ou realms para outras threads:
- Objetos ativos, collections e instâncias de Realm são confinados à thread: ou seja, são válidos somente na thread em que foram criados. De forma prática, isso significa que não é possível passar instâncias ativas para outras threads. No entanto, o Realm oferece vários mecanismos para compartilhar objetos entre threads.
Comunicação entre threads
Para acessar o mesmo arquivo Realm de diferentes threads, você deve instanciar uma instância de realm em cada thread que precisa de acesso. Desde que você especifique a mesma configuração, todas as instâncias de realm mapearão para o mesmo arquivo no disco.
Uma das principais regras para trabalhar com o Realm em um ambiente de várias threads é que os objetos são confinados pela thread: não é possível acessar as instâncias de um domínio, coleção ou objeto originado em outras threads. A arquitetura de Controle de concorrência multiversão (MVCC) do Realm significa que pode haver muitas versões ativas de um objeto a qualquer momento. O confinamento de threads garante que todas as instâncias nesse thread tenham a mesma versão interna.
Quando há necessidade de comunicação entre threads, você tem várias opções, dependendo do seu caso de uso:
Para modificar um objeto em dois threads, consulte o objeto em ambos os threads.
Para reagir às alterações feitas em qualquer thread, use as notificaçõesdo Realm's.
Para ver as mudanças que aconteceram em outro thread na instância de domínio do thread atual, atualize sua instância de domínio.
Para compartilhar uma instância de um realm ou objeto específico com outro thread, compartilhe uma thread_safe_reference para a instância ou objeto de realm.
Para enviar uma visualização rápida e somente leitura do objeto para outros threads, "freeze" o objeto.
Passe instâncias entre threads
As instâncias de realm::realm
, realm::results
e realm::object
são confinadas por thread . Isso significa que você só pode usá-las no tópico onde as criou.
Você pode copiar instâncias confinadas por thread para outra thread da seguinte maneira:
Inicialize um thread_safe_reference com o objeto confinado à thread.
Passe a referência para a thread de destino.
Resolva a referência na thread de destino. Se o objeto referenciado for uma Instância de Realm, resolva-o ligando para
.resolve()
; caso contrário, mova a referência pararealm.resolve()
. O objeto retornado agora está confinado à thread na thread de destino, como se tivesse sido criado na thread de destino em vez de na thread original.
Importante
Você deve resolver um thread_safe_reference
exatamente uma vez. Caso contrário, o Realm de origem permanecerá fixado até que a referência seja desalocada. Por essa razão, thread_safe_reference
deve ser de curta duração.
// 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();
Outra maneira de trabalhar com um objeto em outro thread é fazer uma query por ele novamente nesse thread. But if the object does not have a primary key, it is not trivial to query for it. Você pode usar thread_safe_reference
em qualquer objeto, independentemente de ele ter ou não uma chave primária.
Use o mesmo Realm em todos os threads
Não é possível compartilhar instâncias de realm entre threads.
Para usar o mesmo arquivo domínio em todos os threads, abra uma instância de domínio diferente em cada thread. Contanto que você use a mesma configuração, todas as instâncias do domínio serão mapeadas para o mesmo arquivo no disco.
Passe cópias imutáveis entre threads
Objetos vivos, confinados ao thread, funcionam bem na maioria dos casos. No entanto, alguns aplicativos -- aqueles baseados em arquiteturas reativas, baseadas em fluxo de eventos, por exemplo -- precisam enviar cópias imutáveis para vários threads para processamento antes de, finalmente, terminarem no thread da UI. Fazer uma cópia profunda toda vez seria caro, e o Realm não permite que instâncias vivas sejam compartilhadas entre threads. Nesse caso, você pode congelar e descongelar objetos, collections e realms.
O congelamento cria uma visão imutável de um objeto, collection ou domínio específico. O objeto, collection ou realm congelado ainda existe no disco e não precisa ser copiado profundamente quando passado para outros segmentos. Você pode compartilhar livremente o objeto congelado entre threads sem se preocupar com problemas de thread. Quando você congela um realm, seus objetos filhos também ficam congelados.
Objetos congelados não estão ativos e não são atualizados automaticamente. Eles são efetivamente snapshots do estado do objeto no momento do congelamento. Descongelar um objeto retorna uma versão ativa do objeto congelado.
Para congelar um Realm, collection ou objeto, chame o método .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());
Quando estiver trabalhando com objetos congelados, uma tentativa de realizar qualquer uma das seguintes ações gera uma exceção:
Abrir uma transação de gravação em um realm congelado.
Modificar um objeto congelado.
Adicionar um ouvinte de alteração a um realm, collection ou objeto congelado.
Você pode utilizar .is_frozen()
para verificar se o objeto está congelado. Isso é sempre seguro para threads.
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. }
Objetos congelados continuam válidos desde que o domínio ativo que os gerou permaneça aberto. Portanto evite fechar o domínio ativo até que todas as threads tenham terminado com os objetos congelados. Você pode fechar um domínio congelado antes que o domínio ativo seja fechado.
Importante
Sobre o armazenamento em cache de objetos congelados
Armazenar muitos objetos congelados pode ter um impacto negativo no tamanho do arquivo do domínio. "Muitos" depende do dispositivo-alvo específico e do tamanho dos seus objetos domínio. Se você precisar armazenar em cache um grande número de versões, considere copiar o que você precisa fora do domínio.
Modificar um objeto congelado
Para modificar um objeto congelado, você deve descongelá-lo. Alternativamente, você pode consultar por ele em um reino não congelado e, então, modificá-lo. Chamar .thaw()
em um objeto ativo, collection ou realm retorna a si mesmo.
Descongelar um objeto ou collection também descongela o realm a que ele faz referência.
// 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"; });
Anexar a uma coleção congelada
Quando você adiciona a uma collection congelada, você deve descongelar o objeto que contém a collection e o objeto que deseja adicionar.
A mesma regra se aplica ao passar objetos congelados entre threads. Um caso comum pode ser chamar uma função em um thread em background para fazer algum trabalho em vez de bloquear a interface do usuário.
Neste exemplo, consultamos dois objetos em um Realm congelado:
Um objeto
Project
que tem uma propriedade de lista deItem
objetosUm objeto
Item
Devemos descongelar ambos os objetos antes de podermos adicionar o Item
à collection de lista items
no Project
. Se descongelarmos apenas o objeto Project
, mas não o Item
, o Realm lançará um erro.
// 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)
Agendadores (loop de eventos)
Some platforms or frameworks automatically set up a scheduler (or run loop), which continuously processes events during the lifetime of your app. O Realm C++ SDK detecta e usa agendadores nas seguintes plataformas ou frameworks:
macOS, iOS, tvOS, watchOS
Android
Qt
O Realm usa o agendador para agendar trabalhos, como upload e download do Realm Mobile Sync.
Se sua plataforma não tiver um agendador compatível ou se você quiser usar um agendador personalizado, implemente o realm::scheduler e passe a instância para o realm::db_config que você usa para configurar o realm. O Realm usará o agendador que você passar para ele.
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; } } }
Atualização de Realms
Em qualquer thread controlado por um agendador ou loop de execução, o Realm atualiza automaticamente os objetos no início de cada iteração do loop de execução. Entre as iterações do loop de eventos, você trabalhará no snapshot, de modo que os métodos individuais sempre vejam uma exibição consistente e nunca precisem se preocupar com o que acontece em outros threads.
Quando você abre pela primeira vez um realm em um thread, seu estado será a mais recente confirmação de escrita bem-sucedida e ele permanecerá nessa versão até ser atualizado. Se um thread não for controlado por um loop de execução, então o realm.refresh() o método deve ser chamado manualmente para avançar a transação para o estado mais recente.
realm.refresh();
Observação
Não atualizar Realm regularmente pode fazer com que algumas versões de transação fiquem "fixadas", impedindo que o Realm reutilize o espaço em disco usado por essa versão e levando a tamanhos de arquivo maiores.
Modelo de threading do Realm em detalhes
O Realm fornece acesso seguro, rápido, sem bloqueios e simultâneo em threads com seu MVCC (Multiversion Concurrency Control) arquitetura.
Comparação e contraste com Git
Se você está familiarizado com um sistema de controle de versão distribuído como o Git, talvez já tenha uma compreensão intuitiva do MVCC. Dois elementos fundamentais do Git são:
Confirmações, que são escritas atômicas.
Ramificações, que são versões diferentes do histórico de confirmações.
Da mesma forma, o Realm tem gravações confirmadas atomicamente na forma de transações. O Realm também tem muitas versões diferentes da história a qualquer momento, como ramificações.
Ao contrário do Git, que suporta ativamente a distribuição e a divergência por meio de forking, um realm só tem uma versão mais recente verdadeira em um determinado momento e sempre escreve para o head dessa versão mais recente. O Realm não pode escrever em uma versão anterior. Isso significa que seus dados convergem para uma versão mais recente da verdade.
Estrutura interna
Um domínio é implementado usando uma estrutura de dados B+tree.. O nó de alto nível representa uma versão do domínio; nós secundários são objetos nessa versão do domínio. O domínio tem um ponteiro para sua versão mais recente, assim como o Git tem um ponteiro para sua confirmação de CABEÇALHO.
O Realm usa uma técnica de cópia em gravação para garantir isolamento e durabilidade. Quando você faz alterações, o Realm copia a parte relevante da árvore para gravar. Em seguida, Realm confirma as alterações em duas fases:
O Realm escreve as alterações no disco e verifica se foi feito.
Em seguida, o Realm coloca o ponteiro da versão mais recente apontando para a versão recém-escrita.
Esse processo de confirmação em duas etapas garante que, mesmo que a gravação falhe parcialmente, a versão original não será corrompida de forma alguma, pois as alterações foram feitas em uma cópia da parte relevante da árvore. Da mesma forma, o ponteiro raiz do domínio apontará para a versão original até que a nova versão seja válida.
Exemplo
O diagrama a seguir ilustra o processo de confirmação:
O realm é estruturado como uma árvore. O realm tem um ponteiro para a sua última versão, V1.
Ao escrever, o Realm cria uma nova versão V2 com base na V1. Realm faz cópias de objetos para modificação (A 1 , C 1), enquanto links para objetos não modificados continuam a apontar para as versões originais (B, D).
Após validar o commit, o Realm atualiza o ponteiro para a nova versão mais recente, V2. O Realm então descarta nós antigos que não estão mais conectados à árvore.
Realm usa técnicas de cópia zero, como mapeamento de memória, para lidar com dados. Quando você lê um valor do realm, você está virtualmente olhando para o valor no disco real, não uma cópia dele. Esta é a base para objetos vivos. É também por isso que um ponteiro de head de realm pode ser definido para apontar para a nova versão depois que a gravação no disco tiver sido validada.