O uso de Threads.@threads deve ser evitado.
Esta macro utiliza um scheduler estático que não funciona muito bem quando temos uma chamada à macro Threads.@threads a partir de uma função que já está rodando em modo multithread.
Só é seguro utilizar esta macro em código top-level quando há garantia de que não há outras tasks executando em modo multithread e para funções que não disparam código em modo multithread.
Na maioria dos casos, deve ser utilizada a função Threads.foreach em vez de Threads.@threads.
Este problema só ocorre pois o scheduler de Threads.@threads é estático.
O PR #43919 adiciona a possibilidade
de scheduler dinâmico, e o PR #44136
torna o scheduler dinâmico a escolha padrão da macro Threads.@threads, o que deverá
corrigir o problema e alterar a recomendação desta seção a partir da versão v1.8 do Julia.
A macro Threads.@spawn deve ser utilizada nos casos de Tasks individuais, e evitada quando há necessidade de criar várias Tasks ao mesmo tempo para dividir uma tarefa em partes a serem processadas em paralelo (normalmente quando há necessidade de chamar Threads.@spawn dentro de um loop).
Neste caso, deve ser utilizada a função Threads.foreach.
A função Threads.foreach deve ser utilizada quando há necessidade de criar várias Tasks ao mesmo tempo para dividir uma tarefa em partes a serem processadas em paralelo.
Esta função funciona de forma similar a criar várias tarefas num loop com Threads.@spawn ou Threads.@threads.
De forma similar a Threads.@spawn, a função Threads.foreach cria (por padrão) tasks em paralelo com um scheduler justo (ver na docstring desta função a descrição do argumento scheduler). Existe um overhead em comparação com Threads.@threads tendo em vista que o scheduler não é estático, porém não há problemas em aninhar chamadas a Threads.@spawn e Threads.foreach, além do fato de que o scheduler estático não é indicado para trabalhos com cargas diferentes.
A seguir, um exemplo de uso do padrão produtor -> consumidores -> redutor.
# loop produtor.
# O `producer_channel` é fechado automaticamente ao final da closure
producer_channel = Channel{TipoItemProduzido}(1, spawn=true) do ch
for ...
# cria item
put!(ch, TipoItemProduzido(...))
end
end
# loop redutor
# O `reduce_channel` é fechado automaticamente ao final da closure
reduce_channel = Channel{TipoItemRedutor}(1, spawn=true) do ch
# consome itens em paralelo, inserindo no canal do redutor
Threads.foreach(producer_channel) do item_produtor
# processa item_produtor
...
put!(ch, TipoItemRedutor(...))
end
end
for item_redutor in reduce_channel
# processa item_redutor
# ....
end