pode ser “clonado” (Fork), e o processo clonado se torna um processo filho, esse processo é idêntico ao pai, mas a partir do momento do fork pode tomar um rumo bem diferente do processo pai (as variáveis e descritores de arquivos são copiados e perdem referência em relação ao pai) • Spawn: No windows um processo pode criar outro inteiramente novo, injetar as variáveis presentes e agrupá-lo ao processo que o criou o processo “pai” (Spawning), esse modelo é bem mais lento que o fork, pois depende uma abstração maior para ser gerado
sí e correm paralelamente e independentes, pois rodam em espaços de memória diferentes • Threads compartilham informações, pois rodam no mesmo espaço de memória • Processos filhos devem ser devidamente controlados, para não virarem zumbis, e devido a sua natureza de serem clones, gerar um clone dentro de um loop sem delimitações adequadas, o processo filho pode continuar o loop gerando subclones • Threads devem rodar em modo thread-safe, deve se tomar muito cuidado com corrupção de dados • Em ambos modelos, quando necessitam de conexão a outros sistemas como RDBM’s, só podem fazê-lo se os mesmos forem compatíveis (multitenancy)
são cedidos a um thread, para que ele seja executado, até que tenha de ceder processamento para o próximo na fila de prioridade • Priority: os threads são executados em fila e cada SO se utiliza de um algoritmo diferente para ordená-los • State: estado atual do thread, “runnable”, “waiting”, etc • Quanta: “clock ticks”, ciclos de processamento, cada thread é dado uma quantidade de quantas entre 2, 6 ou 12 e cada quanta (ciclo), dura 1 ms no linux e entre 12 e 15 ms no windows • Performance: Tendo tudo isso em vista poderemos entender algumas implicações de performance
de memória compartilhada • Ao compartilhar memória é muito importante tomar cuidado com a concorrência (escritas simultâneas), para não corromper os dados • É comum usar locks (travas), para impedir escrita concorrente nesses dados • Locks podem se tornar um gargalo de performance se mal usadas, pois travam também os processos concorrentes que podem ficar esperando a liberação das travas para acessar os dados
possível ter acesso a dados em comum • Devemos lembrar que em processos diferentes, mesmo compartilhando a memória, um processo filho não consegue em tese ficar parado esperando que outros processos enviem dados, a não ser que use mensageria via stream, sockets, key-value storages ou uma implementação do shared memory que trave a leitura enquanto outro processo estiver escrevendo • Já os threads tem acesso total as variáveis um do outro e usam sistema de travas, onde um thread entra em modo de espera até que outro mande um sinal de liberação, isso é comumente chamado de locks e semaphores
nível • Implementa tanto processos quanto threads • Possui mecanismos tanto de shared memory quanto de mensageria • Simples e robusto, trata todas as complexidades de fork inerentes aos SO’s • Mecanismos de locks e semaphores extremamente simplificados • Nas implementações de médio e alto nível é possível usar pools de processos ou threads
um callable para executar em um thread separado • queue.Queue: classe “thread safe” que é usada como uma fila de dados compartilhados entre threads (pode ser também usada como fila comum em aplicações python) • multiprocessing.Process: classe de mesma interface de threading.Thread que faz fork de processos e executa apenas um callable específico e retorna para o processo pai • multiprocessing.Pool: classe que cria um pool de processos, em sua assinatura recebe o número de processos a serem criados. Essa classe possui métodos que distribuem processos usando “round-robin”
permite compartilhamento entre processos “forkados”, gera locks • multiprocessing.pool.ThreadPool: Gera um pool de threads com um callback de excução. • concurrent.futures.ThreadPoolExecutor: implementação high level que utiliza a arquitetura de executor e utiliza um pool de threads • concurrent.futures.ProcessPoolExecutor: implementação high level que utiliza a arquitetura de executor e utiliza um pool de processos
Process recebem um callable em seu construtor • Ambas possuem métodos de controle de fluxo, start inicia o processo e não bloqueia, run roda o próximo e bloqueia até que seja concluído • A classe Queue é uma estrutura de fila FIFO que é também Thread-Safe
para threads • São criados processos ou threads na quantidade especificada que ficam abertos aguardando callables para serem executados • Ganho de performance, pois não precisa abrir uma thread a cada callable, executa na thread já aberta
mandar o executor executar um callable, ele retorne um objeto Future que contém informações sobre o processo a ser executado • O método result retornará ao processo/thread pai o valor retornado pelo callable, se houver uma exceção no callable, result vai lançá-lo • Interface única
acessos simultâneos entre múltiplos threads • Estruturas que evitam deadlocks (trava recursiva, onde uma thread espera pela outra de forma recursiva) • Técnicas como mutual exclusion (trava escrita e leitura enquanto uma thread escreve), Objetos imutáveis, operações atômicas, Re-entrance (código que gera cópias dos valores e manipula apenas individualmente)
em tempo real quando está sendo executado • Os opcodes por ele gerados não são thread-safe, portanto, não é possível que diversos threads modifiquem os opcodes ao mesmo tempo • O Cpython então implementa o GIL, uma trava que impede escritas e leituras simultâneas aos opcode gerados em runtime, gerando performance muito ruim em determinados casos • Ainda não se descobriu uma forma de remover o GIL sem degradar a performance do Cpython em processos single threaded
JIT que gera bytecodes que podem ser executados diretamente pela máquina • Além do PyPy ser um interpretador muito mais rápido, os bytecodes gerados são thread-safe, ou seja, não existe GIL no PyPy • Isso permite uma programação multi-thread muito mais fluida • O PyPy já vem com o modo stackless e greenlets embutidos
implementação do que é chamada de microthreads • Microthreads são threads que não são controladas pelo SO e sim pelo próprio interpretador, ou seja, não ocorre context switching e dispensa uso direto dos mecanismos de locking • A comunicação entre “stacklets” ocorre via mensageria (envio de mensagens), ou seja, uma implementação pub-sub que evita acessos simultâneos aos dados