Pergunta

A documentação do Twisted me levou a acreditar que não havia problema em combinar técnicas como reactor.spawnProcess() e threads.deferToThread() na mesma aplicação, que o reator lidaria com isso elegantemente nos bastidores.Ao tentar, descobri que meu aplicativo travava.Usando vários threads sozinhos ou processos filhos sozinhos, está tudo bem.

Olhando para a fonte do reator, descubro que o SelectReactor.spawnProcess() método simplesmente chama os.fork() sem qualquer consideração por vários threads que possam estar em execução.Isto explica os impasses, porque começando com a chamada para os.fork() você terá dois processos com vários threads simultâneos em execução e fazendo sabe-se lá o quê com os mesmos descritores de arquivo.

Minha pergunta para o SO é: qual a melhor estratégia para resolver esse problema?

O que tenho em mente é subclassificar SelectReactor, de modo que seja um singleton e chame os.fork() apenas uma vez, imediatamente quando instanciado.O processo filho será executado em segundo plano e atuará como um servidor para o pai (usando a serialização de objetos por meio de pipes para se comunicar).O pai continua a executar o aplicativo e pode usar threads conforme desejado.Chamadas para spawnProcess() no pai será delegado ao processo filho, que terá a garantia de ter apenas um thread em execução e poderá, portanto, chamar os.fork() com segurança.

Alguém já fez isso antes?Existe uma maneira mais rápida?

Foi útil?

Solução 3

Voltando a esse problema depois de algum tempo, descobri que se fizer isso:

reactor.callFromThread(reactor.spawnProcess, *spawnargs)

em vez disso:

reactor.spawnProcess(*spawnargs)

então o problema desaparece no meu pequeno caso de teste.Há uma observação na documentação do Twisted "Usando Processos" que me levou a tentar isto:"A maior parte do código no Twisted não é seguro para threads.Por exemplo, gravar dados em um transporte a partir de um protocolo não é seguro para threads."

Suspeito que as outras pessoas mencionadas por Jean-Paul que estavam tendo esse problema possam estar cometendo um erro semelhante.A responsabilidade é do aplicativo para garantir que o reator e outras chamadas de API estejam sendo feitas no thread correto.E aparentemente, com exceções muito estreitas, o “encadeamento correto” é quase sempre o encadeamento principal do reator.

Outras dicas

Qual a melhor estratégia para resolver este problema?

Arquive um ticket (talvez depois registrando) descrevendo o problema, de preferência com um caso de teste reproduzível (para máxima precisão).Depois, pode haver alguma discussão sobre qual pode ser a melhor maneira (ou maneiras - diferentes plataformas podem exigir soluções diferentes) de implementá-la.

A ideia de criar imediatamente um processo filho para ajudar na criação de processos filhos foi levantada antes, para resolver o problema de desempenho em torno da colheita de processos filhos.Se essa abordagem resolver agora dois problemas, começa a parecer um pouco mais atraente.Uma dificuldade potencial com esta abordagem é que spawnProcess retorna de forma síncrona um objeto que fornece o PID do filho e permite que sinais sejam enviados a ele.Isso é um pouco mais trabalhoso para implementar se houver um processo intermediário no caminho, já que o PID precisará ser comunicado de volta ao processo principal antes spawnProcess retorna.Um desafio semelhante será apoiar a childFDs argumento, uma vez que não será mais possível apenas herdar os descritores de arquivo no processo filho.

Uma solução alternativa (que pode ser um pouco mais hackeada, mas que também pode ter menos desafios de implementação) pode ser ligar sys.setcheckinterval com um número muito grande antes de ligar os.fork, e, em seguida, restaure o intervalo de verificação original somente no processo pai.Isso deve ser suficiente para evitar qualquer troca de thread no processo até que o os.execvpe ocorre, destruindo todos os fios extras.Isso não está totalmente correto, pois deixará certos recursos (como mutexes e condições) em mau estado, mas você os usa com deferToThread não é muito comum, então talvez isso não afete o seu caso.

O conselho que Jean-Paul dá na sua resposta é bom, mas este deve funciona (e funciona na maioria dos casos).

Primeiro, o Twisted também usa threads para resolução de nomes de host, e eu definitivamente usei subprocessos em processos Twisted que também fazem conexões de clientes.Portanto, isso pode funcionar na prática.

Segundo, fork() não cria vários threads no processo filho. De acordo com o padrão que descreve fork(),

Um processo deve ser criado com um único thread.Se um processo multithread chamar fork(), o novo processo deverá conter uma réplica do thread de chamada ...

Agora, isso não quer dizer que existam não possíveis problemas de multithreading com spawnProcess;o padrão também diz:

...para evitar erros, o processo filho só pode executar operações seguras para sinal assíncrono até que uma das funções exec seja chamada ...

e não acho que haja algo que garanta que apenas operações seguras com sinal assíncrono sejam usadas.

Portanto, seja mais específico quanto ao seu problema exato, pois não é um subprocesso com threads sendo clonados.

fork() no Linux definitivamente deixa o processo filho com apenas um thread.

Presumo que você esteja ciente de que, ao usar threads no Twisted, a ÚNICA API Twisted que os threads podem chamar é callFromThread?Todas as outras APIs Twisted devem ser chamadas apenas a partir do thread principal do reator.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top