AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 77053783
Accepted
DotFX
DotFX
Asked: 2023-09-07 00:28:06 +0800 CST2023-09-07 00:28:06 +0800 CST 2023-09-07 00:28:06 +0800 CST

Uma grande questão sobre multithreading e suas nuances. O que é e como trabalhar corretamente? Mutex, semáforos

  • 772

Eu li a documentação. Agora, eu meio que entendo o que encadear uma função faz no Godot. Eu o uso com mais ou menos sucesso (mas depois de adicionar mais alguns bits ao código, o projeto simplesmente trava). Eu também li a API thread-safe e essa é uma zona cinzenta para mim. Mutexes também são. E semáforos. Não sou um cara de TI, então é mais como um Explique como se eu fosse 5)

Com o que especificamente posso ou não usar threading? Posso chamar a função de outro nó? Ou devo colocar um novo thread diretamente nesse nó?
O que são mutexes? O que lockinge unlockingum mutex faz?
O que são semáforos?
Quando usar o quê?

godot
  • 1 1 respostas
  • 28 Views

1 respostas

  • Voted
  1. Best Answer
    Theraot
    2023-09-07T07:28:28+08:002023-09-07T07:28:28+08:00

    Como se você tivesse 5 anos? OK... Na terra do código...


    Thread único vs multithread

    A execução do código flui de uma instrução para a próxima. Às vezes, ele salta pulando algumas seções condicionais (quando há uma instrução ifou uma match) ou entra em loop (quando há uma instrução forou ). whileMas não importa o que aconteça, é sempre a execução de uma única instrução por vez.

    Isso é o que chamamos de execução de "threaded único". Mas um dia, o "multi-threaded" chegou à terra do código...

    Quando existem vários threads, cada um tem seu próprio fluxo de execução. Enquanto um thread está em uma instrução, o outro está em alguma outra instrução.

    Isso pode ser bom, porque pode permitir que o código execute mais trabalho ao mesmo tempo. Mas também pode ser ruim, porque o que um thread está fazendo pode atrapalhar os outros.

    Assim é preciso ter cuidado para que os fios não se enrosquem...


    No tópico principal

    Mesmo que exista multi-threading, ainda existe o thread principal, que é o mesmo que você tinha quando a execução era de thread único. E muitas coisas estão vinculadas ao thread principal (principalmente a UI está vinculada ao thread que criou a janela, e a primeira janela é criada pelo thread principal). E tentar acessá-los de outro tópico causará problemas... Do mundano ao desagradável.


    Execução adiada

    Nossa principal ferramenta para isso é a execução diferida, pois isso sempre acontecerá na thread principal, independente de qual thread a solicitou. E como um thread faz apenas uma coisa por vez, não haverá problemas.

    Chamadas adiadas são uma boa maneira de outros threads entregarem resultados ao thread principal. Mas fazer tudo com execução adiada desperdiça o potencial dos threads.

    Como a chamada adiada acontecerá algum tempo depois no thread principal, do ponto de vista dos threads secundários, a chamada adiada é disparada e esquecida (o thread secundário não pode aguardá-la ou obter um resultado dela, ele continua executando agora mesmo).

    Você pode usar uma chamada adiada de um thread secundário para fornecê-la Callable. Mas se o thread principal chamar o Callable, ele estará sendo executado no thread principal. Estou lhe dizendo que isso não é uma solução alternativa.


    Quero que você imagine este cenário: você tem alguma computação complexa ou está realizando alguma operação IO grande (por exemplo, lendo um arquivo grande), o que levará algum tempo...

    Se você fizer isso no thread principal, ele estará ocupado fazendo isso e não atualizará o resto do seu jogo/aplicativo. Como resultado, o usuário verá que o jogo/aplicativo congela.

    Para evitar isso, você pode usar um secundário Thread, onde você faz o trabalho. Mas no final você quer mostrar um resultado, ou quer mostrar alguma barra de progresso enquanto ele está em execução. Para fazer isso, o thread secundário pode usar uma chamada adiada.


    Rosqueamento preventivo

    Para poder fazer com que vários threads trabalhem com os mesmos valores, precisamos entendê-los melhor. Notavelmente, o threading é preventivo ( geralmente ), o que significa que o thread pode ser suspenso e retomado a qualquer momento. Às vezes sob seu controle, às vezes fora de seu controle. Como resultado, o que quer que um thread esteja fazendo pode ser deixado temporariamente em um estado não pescado.

    Algumas instruções são atômicas, o que significa que não podem ser subdivididas e, portanto, um thread as executa ou não. Eles não podem ficar pela metade. Infelizmente, pode depender da quantidade de dados e da plataforma (às vezes a leitura ou escrita não é uma operação única, mas deve ser feita em partes)… Então, a menos que seja explicitamente declarado que algo é garantido como atômico, não confie nisso .

    Assim, uma das coisas que pode acontecer é que um thread esteja escrevendo uma variável, mas escreva apenas na metade, e outro thread leia assim e... ah, não, comportamento indefinido! caos! trava!


    Algo que não será atômico ( normalmente ) é incrementar uma variável. Ele pode ser dividido em ler o valor da variável, calcular o valor incrementado e depois escrevê-lo.

    Neste caso, um thread pode ler o valor, calcular o valor incrementado... E ser antecipado! Outro thread entra, lê o valor, calcula o valor incrementado e grava-o. Agora o primeiro thread retoma e grava o valor que calculou... Mas substitui o trabalho do segundo thread! O primeiro thread não sabia que o segundo alterou o valor! Como resultado, o valor foi incrementado apenas uma vez, não duas vezes.

    Isso é conhecido como problema ABA , consulte também Tempo de verificação para tempo de uso . É também um exemplo de condição de corrida .


    Fechaduras

    Se você precisar de um thread secundário para esperar por algo, use um bloqueio (mutex ou semáforo).

    Queremos que outros threads esperem até terminarmos de calcular e escrever valores antes de lerem os resultados. E também para impedi-los de escrever qualquer coisa que possa atrapalhar o trabalho que estamos fazendo em um tópico.

    Chamamos onde queremos controlar o acesso dos threads de "Seções Críticas".

    E nós temos ferramentas para isso!


    Mutex

    Uma ferramenta para controle de threads é o Mutex! O que significa Exclusão Mútua! Quando um thread pega o Mutex, ele o bloqueia, mas apenas um thread pode rodar com ele por vez, todos os outros threads serão suspensos até que o thread que executou com o mutex o libere.

    Mas às vezes você não quer que seu thread espere até que o mutex seja liberado; nesse caso, você pode fazer com que eles tentem bloquear o mutex e, se falharem, estarão livres para fazer outra coisa. Apenas não os deixe enlouquecer na seção crítica.

    Às vezes você tem um thread gerando valores para vários outros threads usarem. Neste caso, a seção crítica quando os threads leem o valor não é mutuamente exclusiva.


    Semáforo

    Outra ferramenta para controle de threads é o Semáforo. Isso é útil quando você tem um thread produzindo valores e outro thread os consumindo . E, claro, você não quer que o thread entre para consumir valores até que eles estejam lendo.

    O que você faz é fazer com que os threads consumidores esperem no semáforo e então o thread produtor pode sinalizar ao semáforo para permitir a passagem de um thread. E o semáforo permitirá a passagem de tantos threads quanto o thread do produtor sinalizar.

    *Infelizmente não temos muito mais em Godot. Eu desejaria bloqueios sofisticados de leitor-gravador, ou incrementos atômicos, ou operações interligadas, ou...


    Reordenando

    Por que não usamos apenas boolpara indicar se os resultados estão prontos? Outro tópico poderia ler, e só acessar se for true... Porque travessuras!

    Primeiro de tudo, embora Godot não faça isso com o GDScript, o compilador C++ usado para compilar Godot pode reordenar instruções desde que esteja convencido de que o resultado é o mesmo (e isso é verificado estaticamente sem considerar outros threads).

    Em geral, os compiladores C++ são livres para compilar o código como quiserem, desde que o resultado seja como se o código fizesse o que o programador escreveu (esta é a liberdade que lhes permite introduzir otimizações, por exemplo, remover uma instrução para escrever uma variável que aparentemente ninguém lê).

    Segundo, mesmo que o compilador não reordene as instruções, a CPU ainda poderá fazê-lo. Embora isso seja raro nas CPUs usadas em computadores desktop, não é tanto em outras arquiteturas.

    E terceiro, as CPUs possuem cache. Em particular, as CPUs multi-core modernas possuem cache por núcleo. E eles lerão os dados do cache, se houver, em vez de lê-los da RAM.

    Como resultado, um thread pode não ver as alterações que outro thread faz imediatamente ou na mesma ordem. Assim, mesmo que vejam o boolconjunto como true, ainda poderão ver valores obsoletos para outras variáveis.

    Para evitar a reordenação, usamos algo chamado "barreiras de memória" (e mecanismos semelhantes, que seriam usados ​​para implementar bloqueios)... No entanto, não faz muito sentido entrar nisso, já que não temos meios de fazer isso a partir do GDScript. Só podemos usar Mutexe Semaphoree…


    Grupos de tópicos

    Ainda preciso falar sobre os novos grupos de threads. Se você definir o grupo de threads de a Nodecomo PROCESS_THREAD_GROUP_MAIN_THREADele será executado no thread principal. Se você definir como PROCESS_THREAD_GROUP_SUB_THREAD, um novo thread será executado. E se você configurá-lo, PROCESS_THREAD_GROUP_INHERITele será executado no mesmo thread do pai.

    Isso permite que você defina um conjunto de Nodes que irão operar em uma thread separada, e desde que funcionem apenas entre si, não haverá problemas, já que todos estão trabalhando na mesma thread. E você pode usar uma chamada adiada para fornecer resultados ao thread principal.

    Também temos um conjunto de funções Nodeque visam tornar segura a comunicação com subthreads (os métodos que estão threadno nome da Nodeclasse). Eles possuem barreiras de memória integradas.


    Sinais e espera

    Quando você aguarda um sinal (o que você pode fazer em threads secundários, mas não faça isso), a chamada retorna. Ou seja, o fluxo de execução sairá do método. E devolverá e armazenará o objeto a posição de onde saiu o fluxo de execução, para que possa ser retomado posteriormente, quando o sinal aguardado for emitido.

    Então, quando o sinal for emitido, o thread principal pegará aquele objeto que representa a posição de execução e continuará a partir daí! Como resultado, aguardando no thread secundário, resultando na troca de threads.

    Nota: este mecanismo pode mudar no futuro, pois os grupos de threads ainda são experimentais e provavelmente serão expandidos para cobrir este caso.


    Na segurança do thread

    Oh, segurança do fio! Isso significa que algo é seguro para ser chamado diretamente de qualquer thread. Ou dito de outra forma, ele é escrito internamente sob a suposição de que pode ser chamado de vários threads simultaneamente, e existem precauções para evitar que isso cause qualquer comportamento inesperado ou travamentos... Em palavras simples: os desenvolvedores cuidaram de o multi-threading, então você não precisa.

    E, como aponta a documentação, a árvore de cena NÃO é segura para threads. O que torna qualquer operação na árvore de cena uma seção crítica por padrão.


    Uma nota sobre física

    Outra coisa a considerar é que Godot pode usar um thread separado para física (que você habilita nas configurações do projeto). Isso também se aplicará a algumas ligações recebidas Nodeda física.

    • 1

relate perguntas

  • GDScript: a função de rotação não funciona corretamente

  • Pergunta rápida sobre Godot 4 exportando uma variável definida

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    destaque o código em HTML usando <font color="#xxx">

    • 2 respostas
  • Marko Smith

    Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}?

    • 1 respostas
  • Marko Smith

    Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)?

    • 2 respostas
  • Marko Smith

    Por que as compreensões de lista criam uma função internamente?

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 respostas
  • Marko Smith

    Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)?

    • 4 respostas
  • Marko Smith

    Por que o construtor de uma variável global não é chamado em uma biblioteca?

    • 1 respostas
  • Marko Smith

    Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto?

    • 1 respostas
  • Marko Smith

    Somente operações bit a bit para std::byte em C++ 17?

    • 1 respostas
  • Martin Hope
    fbrereto Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi Por que as compreensões de lista criam uma função internamente? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A formato fmt %H:%M:%S sem decimais 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python std::views::filter do C++20 não filtrando a visualização corretamente 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa Por que o construtor de uma variável global não é chamado em uma biblioteca? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev Por que os compiladores perdem a vetorização aqui? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan Somente operações bit a bit para std::byte em C++ 17? 2023-08-17 17:13:58 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve