Skip to content

Instantly share code, notes, and snippets.

@felipenoris
Last active February 12, 2022 23:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felipenoris/bea2a6a58d5cdeb062eb3fc25aa1ada7 to your computer and use it in GitHub Desktop.
Save felipenoris/bea2a6a58d5cdeb062eb3fc25aa1ada7 to your computer and use it in GitHub Desktop.

Notas Sobre Multithreading em Julia

Sobre o uso de Threads.@threads

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.

Sobre o uso de Threads.@spawn

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.

Sobre o uso de 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment