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