Substituição de filas no RTOS


12

Para comunicação entre tarefas ou para compartilhar dados entre duas tarefas do RTOS, usamos Filas. Mas o problema com as filas é que elas são lentas ... Elas copiam dados no buffer, depois no Mutex Handling e na transferência de dados. É irritantemente lento se você precisar transferir grandes dados. Outro problema é se a mesma fila for acessada por várias tarefas. Em seguida, a imagem fica assim: - Primeiro, aguarde para acessar a fila e, em seguida, na fila Processamento interno de mutex e depois na transferência de dados.

Isso aumenta a sobrecarga no sistema. Qual poderia ser o substituto eficiente para filas?

(Acho que essa pergunta é independente do RTOS que usamos. A maioria dos RTOS manipula filas somente dessa maneira)


O que você quer dizer com uma fila acessada por várias tarefas? Você quer dizer postar na fila ou ler a partir da fila? Várias tarefas devem poder postar em uma fila com o mínimo de sobrecarga. O RTOS deve manipular a mutex para que uma postagem seja uma operação atômica. Para 99% das tarefas, você deve ter um loop pendente em uma fila e processar a mensagem. Uma fila (normalmente) deve ser lida apenas por uma tarefa. Você provavelmente precisa examinar seu design e como está usando filas em vez de substituí-las.
Erik

@Erik: Desculpe! Estou usando o mecanismo que você mencionou .... Queria dizer outra coisa e escrevi diferente .... Vou editar isso !! Obrigado por apontar o erro! Estou aguardando o acesso à fila no meu código!
Swanand

Respostas:


7

As filas operam dessa maneira porque esse é um modelo de transação seguro para threads para comunicação entre tarefas. Você corre o risco de corrupção de dados e / ou problemas de propriedade em qualquer esquema menos rigoroso.

Você está copiando os dados em um buffer na memória e passando um ponteiro com os elementos da fila ou tentando passar todos os dados nos próprios elementos da fila? Se você não estiver passando ponteiros, obterá um aumento no desempenho fazendo isso, em vez de passar um byte de cada vez através dos elementos da fila.


2
Eu ia dizer a mesma coisa. Se você passar os ponteiros para os dados nas filas, poderá aumentar a velocidade, mas certifique-se de não terminar com dois threads tentando usar e alterar os dados.
Kortuk

Como o @Kortuk disse, eu preciso "garantir que você não termine com dois threads tentando usar e alterar os dados" ... O que significa aumento da sobrecarga ... Eu não quero muito processamento! :(
Swanand

Portanto, não há substituição para as filas ... Em vez da fila de dados, preciso usar a fila de ponteiros!
Swanand

1
@Swanand, se planejar seu aplicativo de forma que as filas sejam unidirecionais (ou seja, você nunca lê a mesma fila em duas tarefas) e processar os dados armazenados no ponteiro imediatamente, liberte-o e você não terá problemas em compartilhar os dados. Haverá uma sobrecarga maior, pois você poderá criar várias filas para transmitir dados de maneira confiável, mas esse é o custo de se fazer negócios em um ambiente de várias tarefas.
precisa saber é o seguinte

7

Uma maneira fácil é colocar um ponteiro para os dados na fila e consumir os dados usando o ponteiro.

Observe que você está negociando segurança por desempenho dessa maneira, pois precisa se certificar de que:

  1. o buffer permanece válido até que o consumidor tenha consumido os dados
  2. alguém desaloca o buffer

Se você não estiver usando memória alocada dinamicamente, não precisará desalocá-la, mas precisará garantir que a área de memória não seja reutilizada antes que os dados sejam consumidos.


6

As filas sem bloqueio podem ser implementadas para o caso de produtor único / consumidor único, e muitas vezes você pode arquitetar seu software para minimizar o número de filas de múltiplos produtores ou vários consumidores.

Uma fila sem bloqueio pode ser construída da seguinte forma: aloque uma matriz dos elementos a serem comunicados e também dois números inteiros, chamados Head e Tail. Head é um índice na matriz, onde o próximo item será adicionado. Tail é um índice na matriz, onde o próximo item está disponível para remoção. A tarefa do produtor lê H e T para determinar se há espaço para adicionar um item; grava o item no índice H e atualiza H. As tarefas do consumidor lê H e T para determinar se há dados disponíveis, lê dados do índice T e atualiza T. Basicamente, é um buffer de anel acessado por duas tarefas e o A ordem das operações (insira, atualize H; remova e atualize T) garante que a corrupção de dados não ocorra.

Se você tiver uma situação com vários produtores e um único consumidor, ou um único produtor e vários consumidores, terá efetivamente algum tipo de limitação de recurso e não há mais nada além de usar a sincronização, pois é mais provável que o limitador de desempenho ser o único produtor / consumidor que uma sobrecarga do SO com o mecanismo de bloqueio.

Mas se você tem vários produtores e consumidores, vale a pena gastar tempo (no espaço de design) para ver se você não consegue um mecanismo de comunicação mais coordenado; em um caso como esse, serializar tudo através de uma única fila definitivamente torna a eficiência da fila o determinante central do desempenho.


1
Eu estava adicionando +1 a isso, mas você está incorreto: é possível implementar filas sem bloqueio para vários leitores e escritores, elas são apenas mais complicadas. (veja o artigo de Michael + Scott sobre Filas sem bloqueio google.com/search?q=michael%20scott%20queue )
Jason S

1
@ Jason S - o documento da Scott reivindica especificamente a reentrada para as operações de inserção e remoção sem trava? Nesse caso, se você pode extrair e publicar, faça isso, seria um recurso inestimável para muitos. O leitor deve observar que o artigo citado faz uso de instruções especiais da máquina, enquanto minha posição no post acima não assumiu tais instruções.
JustJeff

1
Sim, o custo dos algoritmos sem bloqueio geralmente é uma dependência do CAS ou de instruções equivalentes. Mas como a reentrada entra em jogo aqui? Faz sentido para mutexes + estruturas de bloqueio, mas não para operações de estrutura de dados.
Jason S

2

Pode-se obter uma operação eficiente em uma fila de um único produtor sem bloqueios se a própria fila contiver itens pequenos o suficiente para trabalhar com uma primitiva exclusiva para carregamento de cargas, troca de comparação ou similar e pode-se usar um valor reservado ou valores reservados para um slot de fila vazio. Ao escrever na fila, o gravador faz uma troca de comparação para tentar armazenar seus dados no próximo slot vazio; se isso falhar, o gravador tenta o seguinte slot. Embora a fila mantenha um ponteiro para o próximo slot vazio, o valor do ponteiro é "consultivo". Observe que, se um sistema usa troca de comparação em vez de exclusivo da loja de carregamento, pode ser necessário ter uma 'família' de valores diferentes de 'espaço vazio'. Caso contrário, se entre o tempo em que o gravador encontrar um slot de fila vazio e tentar gravar nele, outro escritor escreve o slot e o leitor lê, o primeiro escritor, sem saber, colocava seus dados em um local onde o leitor não os via. Esse problema não ocorre em sistemas que usam exclusivo de armazenamento de carga, pois o exclusivo de armazenamento detectaria que os dados foram gravados, mesmo tendo sido gravados novamente no valor antigo.


1

Você pode acessar as filas com mais eficiência escrevendo na parte superior da fila. Normalmente, a maior parte do RTOS oferece suporte para adicionar à frente da fila, o que não requer a aquisição de mutex. Mas certifique-se de usar a adição à frente da fila o mínimo possível, onde você deseja executar os dados mais rapidamente. Normalmente, as estruturas de filas têm limite máximo de tamanho, portanto, você não pode colocar todos os dados na fila, portanto, passar o ponteiro é sempre fácil.

Felicidades!!


1

Filas não são inerentemente lentas. A implementação deles pode ser.

Se você estiver copiando dados às cegas e usando uma fila síncrona, verá um desempenho atingido.

Como outros pôsteres indicaram, existem alternativas sem bloqueio. O caso de produtor único / consumidor único é direto; para vários produtores e consumidores, o algoritmo de fila sem bloqueio de Michael e Scott (esses são os sobrenomes) é o padrão e é usado como base para o ConcurrentLinkedQueue do Java .

É possível otimizar a necessidade de filas em certos casos, mas eles fornecem garantias de simultaneidade que geralmente fornecem enormes benefícios de simplificação para os sistemas, permitindo dissociar tarefas.


Do artigo de Michael & Scott: "é o claro algoritmo de escolha para máquinas que fornecem uma primitiva atômica universal (por exemplo, compare e troque ou carregue condicional vinculado / armazenado)". Embora isso possa não bloquear exatamente um encadeamento, existe uma forma de sincronização acontecendo aqui.
JustJeff

Você tem um ponto; isso pode diminuir o requisito de simultaneidade do acesso exclusivo a uma barreira de memória.
Jason S
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.