É melhor usar mensagens “Sincronizar” ou usar a janela de TThread para IPC entre o fio principal e filho?

StackOverflow https://stackoverflow.com/questions/1806339

  •  05-07-2019
  •  | 
  •  

Pergunta

Eu tenho um multi-threaded VCL gui aplicativo bastante simples escrito com Delphi 2007. Eu fazer algum processamento em vários segmentos criança (até 16 simultâneos) que necessidade de atualizar um controle de grade no meu formulário principal (simplesmente postar cordas a um rede). Nenhuma das crianças tópicos nunca falar uns com os-outros.

Meu projeto inicial envolveu chamando "Sincronizar" do TThread para atualizar o formulário controle de grade dentro do segmento em execução no momento. No entanto, entendo que chamar Sincronizar essencialmente executa como se fosse o segmento principal quando chamado. Com até 16 threads em execução ao mesmo tempo (ea maioria do processamento do segmento criança toma de <1 segundo para ~ 10 segundos) seria mensagens de janela ser um projeto melhor?

Eu comecei-o a trabalhar neste momento em que os postos de rosca criança uma mensagem janelas (que consiste em um registro de várias cordas) e o segmento principal tem um ouvinte e simplesmente atualiza a grade quando uma mensagem é recebida.

Todas as opiniões sobre o melhor método para IPC nesta situação? mensagens de janela ou 'Sincronizar'?

Se eu usar mensagens de janela, que você sugere envolver o código onde eu posto à rede em um TCriticalSection (entrar e sair) do bloco? Ou eu não precisa se preocupar com a segurança do thread desde que eu estou escrevendo para a rede no segmento principal (embora dentro a função do manipulador de mensagem janela)?

Foi útil?

Solução

Editar:

Parece que muitos dos detalhes de implementação mudaram desde Delphi 4 e 5 (as versões do Delphi Eu ainda estou usando para a maioria do meu trabalho), e Allen Bauer comentou o seguinte:

Desde D6, TThread não usa SendMessage mais. Ele usa uma fila de trabalho thread-safe, onde o "trabalho" destinado ao segmento principal é colocado. Uma mensagem é enviada para o segmento principal para indicar que o trabalho está disponível e os fios blocos branco sobre um evento. Quando o ciclo de mensagem principal está prestes a ir ocioso, ele chama de "CheckSynchronize" para ver se qualquer trabalho está esperando. Se assim for, ele processa-lo. Uma vez que um item de trabalho é concluído, o evento em que a discussão de fundo está bloqueado é definido para indicar a conclusão. Introduzido no prazo D2006, foi adicionado método TThread.Queue que não bloqueia.

Obrigado pela correção. Portanto, tome os detalhes na resposta original com um grão de sal.

Mas isso realmente não afeta os pontos principais. Eu ainda sustentam que toda a idéia de Synchronize() é fatalmente falho, e isso vai ser óbvio o momento um tenta manter vários núcleos de uma máquina moderna ocupados. Não "Sincronizar" seus segmentos, deixá-los trabalhar até que eles estão acabados. Tente minimizar todas as dependências entre eles. Especialmente quando atualizando o GUI não há absolutamente não razão para esperar para este para ser concluído. Se usos Synchronize() SendMessage() ou PostMessage(), o bloco de estrada resultante é o mesmo.


O que vós aqui presentes não é uma alternativa em tudo, como usos Synchronize() SendMessage() internamente. Portanto, é mais uma questão de qual arma que deseja usar para atirar no próprio pé com.

Synchronize() tem estado conosco desde a introdução do TThread no Delphi 2 VCL, que é uma vergonha realmente como ele é um dos seus inconvenientes de design maiores na VCL.

Como é que funciona? Ele usa uma chamada SendMessage() a uma janela que foi criado no thread principal, e define os parâmetros de mensagem para passar o endereço de um método sem parâmetros objeto a ser chamado. Desde mensagens do Windows serão processadas apenas no segmento que criou a janela de destino e executa o seu ciclo de mensagem isso irá suspender o fio, manipular a mensagem no contexto do thread principal VCL, chamar o método, e retomar o fio somente após o método terminar a execução.

Então, o que há de errado com ele (e que é semelhante errado com o uso SendMessage() diretamente)? Várias coisas:

  • Forçar qualquer tópico para executar código no contexto de outras forças de rosca dois contexto segmento switches, que desnecessariamente queima ciclos de CPU.
  • Enquanto o segmento VCL processa a mensagem para chamar o método sincronizado não pode processar qualquer outra mensagem.
  • Quando mais de um thread usa este método que irá todas bloco e espera para Synchronize() ou SendMessage() para retornar. Isso cria um gargalo gigante.
  • Há um impasse esperando para acontecer. Se o thread chama Synchronize() ou SendMessage() enquanto segura um objeto de sincronização, eo segmento VCL ao processar as necessidades de mensagens para adquirir o mesmo objeto de sincronização do aplicativo irá travar.
  • O mesmo pode ser dito das chamadas API esperando o identificador segmento -. Utilizando WaitForSingleObject() ou WaitForMultipleObjects() sem alguns meios para processar mensagens fará com que um impasse se o segmento precisa destes maneiras de "sincronizar" com o outro fio

Então, o que usar em vez disso? Várias opções, vou descrever alguns:

  • Use PostMessage() vez de SendMessage() (ou PostThreadMessage() se os dois tópicos são ambos não o segmento VCL). É importante ainda para não usar quaisquer dados nos parâmetros mensagem que será já não é válida quando a mensagem chega, como the envio e recebimento de rosca não são sincronizados em tudo, por isso alguns outros meios têm de ser utilizados para se certificar de que qualquer corda, referência de objeto ou pedaço de memória ainda são válidos quando a mensagem é processada, embora o segmento de envio não pode mesmo existir mais.

  • Criar estruturas de dados thread-safe, colocar os dados a eles a partir de seus segmentos de trabalho, e consumi-los a partir do thread principal. Use PostMessage() apenas para alertar o segmento VCL que os novos dados chegou a ser processado, mas não postar mensagens de cada vez. Se você tem um fluxo contínuo de dados que você poderia mesmo ter a votação segmento VCL para dados (talvez usando um temporizador), mas esta é apenas a versão de um homem pobre.

  • Não use as ferramentas de baixo nível em tudo, não mais. Se você é, pelo menos em Delphi 2007, o download do OmniThreadLibrary e começar a pensar em termos de tarefas, não threads . Esta biblioteca tem uma série de facilidades para a troca de dados entre threads e sincronização. Ele também tem uma implementação de pool de threads, que é uma coisa boa - quanto tópicos que você deve usar não só dependem da aplicação, mas também sobre o hardware que está sendo executado, tantas decisões só pode ser feita em tempo de execução. OTL lhe permitirá executar tarefas em um thread do pool, de modo a melodia sistema de lata o número de threads simultâneos em tempo de execução.

Editar:

Na re-ler eu perceber que você não pretende usar SendMessage() mas PostMessage() - bem, alguns dos acima não se aplica quando, mas vou deixá-lo no lugar. No entanto, há mais alguns pontos em sua pergunta que eu quero para o endereço:

Com até 16 threads em execução ao mesmo tempo (ea maioria do processamento do segmento criança toma de <1 segundo para ~ 10 segundos) seria mensagens de janela ser um projeto melhor?

Se você postar uma mensagem de cada thread uma vez a cada segundo ou mesmo um período mais longo, em seguida, o projeto é bom. O que você não deve fazer é centenas de correio ou mais mensagens por thread por segundo, porque a fila de mensagens do Windows tem um comprimento finito e mensagens personalizadas não deve interferir com mensagem normal processamento muito (o seu programa iria começar a aparecer sem resposta).

onde as mensagens segmento infantil uma mensagem janelas (que consiste em um registro de várias cordas)

A janela de mensagem não pode conter um registro. Ele carrega dois parâmetros, um dos tipo WPARAM, o outro do tipo LPARAM. Você só pode converter um ponteiro para tal registro um a um destes tipos, por isso, a vida útil das necessidades de registro para ser gerenciado de alguma forma. Se você alocar dinamicamente o que você precisa para libertá-la também, que é propenso a erros. Se você passar um ponteiro para um registro na pilha ou a um campo objeto que você precisa ter certeza de que ainda é válida quando a mensagem é processada, o que é mais difícil para as mensagens enviadas do que para as mensagens enviadas.

Você sugere envolver o código onde eu posto à rede em um TCriticalSection (entrar e sair) do bloco? Ou eu não precisa se preocupar com a segurança do thread desde que eu estou escrevendo para a rede no segmento principal (embora dentro a função do manipulador de mensagem janela)?

Não há necessidade de fazer isso, como a chamada PostMessage() retornará imediatamente, assim que nenhuma sincronização é necessário neste momento . Você vai precisar se preocupar com a segurança do segmento, infelizmente, você não pode saber quando . Você tem que certificar-se de que o acesso aos dados é thread-safe, pela sempre bloqueio dos dados de acesso, usando objetos de sincronização. Não há realmente uma maneira de conseguir isso para os registros, os dados sempre pode ser acessado diretamente.

Outras dicas

BTW, você também pode usar TThread.Queue() em vez de TThread.Synchronize() . Queue() é a versão assíncrona, ele não bloqueia o segmento de chamada:

(Queue está disponível desde D8).

Eu prefiro Synchronize() ou Queue(), porque é muito mais fácil de entender (para outros programadores) e melhor OO de mensagem simples envio (com nenhum controle sobre isso ou capaz de depurá-lo!)

Enquanto eu tenho certeza que existe um jeito certo e um jeito errado. Eu escrevi o código usando ambos os métodos e o que eu voltem sempre ao é o método SendMessage e eu não sei por que.

O uso do SendMessage vs Sincronizar realmente não faz qualquer diferença. Ambos trabalham essencialmente o mesmo. Eu acho que a razão pela qual eu continuo usando SendMessage é que percebo uma quantidade maior de controle, mas eu não sei.

A rotina SendMessage faz com que o segmento de chamada para fazer uma pausa e esperar até que a janela de destino termina o processamento da mensagem enviada. Por isso, o segmento principal aplicativo é essencialmente syncronized para o segmento criança pedindo a duração da chamada. Você não precisa usar uma seção crítica no manipulador de janelas mensagem.

A transferência de dados é essencialmente uma forma, a partir do segmento de chamada para o thread principal do aplicativo. Você pode retornar um valor de tipo inteiro no message.result mas nada que aponta para um objeto de memória no segmento principal.

Uma vez que os dois tópicos são "sincronizado" que esse ponto ea linha principais aplicativos está amarrado responder ao SendMessage então você também não precisa se preocupar com outros tópicos entrando e destruindo seus dados ao mesmo Tempo. Então, não, você não precisa se preocupar com seções críticas ou outros tipos de medidas de segurança fio.

Para coisas simples que você pode definir uma única mensagem (wm_threadmsg1) e usar os campos wParam e lParam para transferência (inteiro) mensagens de status e para trás. Para obter exemplos mais complexos você pode passar uma corda passando-a através do lparam e typecasting-lo de volta a um inteiro longo. A-la longint (pchar (myvar)) ou o uso PWideChar se seu usando D2009 ou mais recente.

Se você já tenho que trabalhar com os métodos Sincronizar então eu não se preocupar com a reformulação-lo para fazer uma mudança.

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