English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Programação Concorrente em Python: Multithreading, Multiprocessing, Asincronismo e Coroutines

Recentemente, aprendi sobre concurrency em Python, então fiz uma resumo sobre multiprocessamento, threads, assíncrono e coroutines.
I. Threads

As threads permitem que existam múltiplas controle dentro de um único processo, para que várias funções possam estar ativas ao mesmo tempo, permitindo que as operações de várias funções sejam executadas ao mesmo tempo. Mesmo em computadores com um único CPU, isso pode ser feito através da troca contínua de instruções entre diferentes threads, criando o efeito de threads executando simultaneamente.

As threads são equivalentes a um sistema de concorrência (concunrrency). Os sistemas de concorrência geralmente executam várias tarefas ao mesmo tempo. Se várias tarefas puderem compartilhar recursos, especialmente quando escrevem em um mesmo variável ao mesmo tempo, é necessário resolver problemas de sincronização, como o sistema de bilhetagem de trens de múltiplas threads: duas instruções, uma para verificar se os bilhetes estão esgotados e outra para várias janelas vender bilhetes ao mesmo tempo, o que pode levar à venda de bilhetes inexistentes.

Em situações de concorrência, a ordem de execução das instruções é determinada pelo núcleo. Dentro da mesma thread, as instruções são executadas na ordem, mas é difícil determinar qual thread executará primeiro entre diferentes threads. Portanto, é necessário considerar problemas de sincronização de múltiplas threads. Sincronização (synchronization) significa que, durante um período de tempo, apenas uma thread pode acessar um recurso.

1、módulo thread

2、módulo threading
threading.Thread cria uma thread.

Adicionar mutex lock para julgar se há bilhetes restantes e vender bilhetes, para evitar que uma thread julgue que não há bilhetes restantes enquanto outra thread executa a operação de venda.

#! /usr/bin/python
#-* coding: utf-8 -*
# __author__ ="tyomcat"
import threading
import time
import os
def booth(tid):
  global i
  global lock
  while True:
    lock.acquire()
    if i!=0:
      i=i-1
      print "Janela:", tid,", tickets restantes:", i
      time.sleep(1)
    else:
      print "Thread_id", tid, "No more tickets"
      os._exit(0)
    lock.release()
    time.sleep(1)
i = 100
lock=threading.Lock()
for k in range(10):
  new_thread = threading.Thread(target=booth, args=(k,))
  new_thread.start()

II. Goroutines (também conhecidas como microthreads, fibers)

Goroutines, diferentemente do escalonamento preemptivo de threads, são escalonamento cooperativo. Goroutines também são de thread única, mas permitem que o que antes precisaria de assincronismo+Código não humano escrito em estilo de chamada de volta pode ser escrito de maneira que pareça síncrona.

1、Goroutines no Python podem ser implementadas por gerações (generator).

Primeiro, é necessário ter uma compreensão sólida de gerações e yield.

Chamar uma função Python comum geralmente começa na primeira linha de código do função e termina com uma instrução return, uma exceção ou a execução do função (também pode ser considerada como implicitamente retornando None).

Quando uma função devolve o controle ao chamador, isso significa que tudo acabou. Às vezes, pode-se criar uma função que gera uma sequência para “salvar seu trabalho”, isso é um gerador (função que usa a palavra-chave yield).

Pode “gerar uma sequência” porque a função não retorna da maneira que geralmente se entende. O significado implícito de return é que a função está devolvendo o controle da execução para onde foi chamada. Enquanto isso, o significado implícito de "yield" é que a transferência de controle é temporária e voluntária, e nossa função ainda recuperará o controle no futuro.

Vamos ver o produtor/Exemplo de consumidor:

#! /usr/bin/python
#-* coding: utf-8 -*
# __author__ ="tyomcat"
import time
import sys
# Produzidor
def produce(l):
  i=0
  while 1:
    if i < 10:
      l.append(i)
      yield i
      i=i+1
      time.sleep(1)
    else:
      return   
# Consumidor
def consume(l):
  p = produce(l)
  while 1:
    try:
      p.next()
      while len(l) > 0:
        print l.pop()
    except StopIteration:
      sys.exit(0)
if __name__ == "__main__":
  l = []
  consume(l)

Quando o programa atinge o produce(yield i), ele retorna um gerador e pausa a execução. Quando chamamos p.next() no custom, o programa volta ao produce(yield i) para continuar a execução. Dessa forma, o l.append(i) adiciona elementos, e então print l.pop(), até que p.next() gere a exceção StopIteration.

2、Stackless Python

3、módulo greenlet

A implementação baseada em greenlet tem desempenho apenas ligeiramente inferior ao do Stackless Python, aproximadamente metade do Stackless Python, e quase uma ordem de magnitude mais rápida que outras soluções. De fato, o greenlet não é um mecanismo de concorrência real, mas sim a troca entre diferentes blocos de código de execução de funções dentro da mesma thread, implementando 'você roda um pouco, eu rodo um pouco', e é necessário especificar quando e para onde fazer a troca.

4、módulo eventlet

三、Multiprocessamento
1、subprocess (pacote subprocess)

No Python, através do pacote subprocess, podemos criar um subprocesso e executar um programa externo.

Quando chamamos comandos do sistema, o módulo os é o primeiro a ser considerado. Usamos os.system() e os.popen() para operar. No entanto, esses comandos são muito simples e não conseguem completar operações complexas, como fornecer entrada para um comando em execução ou ler a saída do comando, determinar o estado de execução do comando, gerenciar a paralelidade de múltiplos comandos etc. Neste caso, o comando Popen do subprocess pode completar eficazmente as operações necessárias

>>> import subprocess
>>> command_line=raw_input()
ping -c 10 www.baidu.com
>>> args=shlex.split(command_line)
>>> p=subprocess.Popen(args)

Usando subprocess.PIPE para conectar a entrada e saída de múltiplos sub-processos, formando um pipe (canal):

import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()
print(out)

método communicate() lê dados do stdout e stderr e os insere no stdin.

2)、multi-processos (pacote multiprocessing)

(1)、o pacote multiprocessing é o pacote de gerenciamento de multi-processos do Python. Semelhante a threading.Thread, ele pode usar o objeto multiprocessing.Process para criar um processo.

O pool de processos (Process Pool) pode criar múltiplos processos.

apply_async(func,args) - pega um processo do pool de processos para executar func, args são os parâmetros de func. Ele retornará um objeto AsyncResult, você pode chamar o método get() para obter o resultado.

close() - o pool de processos não criará novos processos

join() - espera que todos os processos do pool estejam concluídos. É necessário chamar o método close() do Pool antes de join().

#! /usr/bin/env python
# -*- coding:utf-8  -*-
# __author__ == "tyomcat"
# "Meu computador tem4个cpu"
from multiprocessing import Pool
import os, time
def long_time_task(name):
  print 'Executar tarefa %s (%s)...' % (name, os.getpid())
  start = time.time()
  time.sleep(3)
  end = time.time()
  print 'Tarefa %s executa %0.2f segundos.' % (name, (end - start))
if __name__=='__main__':
  print 'Processo pai %s.' % os.getpid()
  p = Pool()
  for i in range(4):
    p.apply_async(long_time_task, args=(i,))
  print 'Aguardando conclusão de todos os subprocessos...'
  p.close()
  p.join()
  print 'Todos os subprocessos concluídos.'

(2)、recursos compartilhados entre múltiplos processos

Usando memória compartilhada e objetos Manager: usar um processo como servidor, estabelecer Manager para realmente armazenar recursos.

Outros processos podem acessar o Manager através de parâmetros ou endereços, estabelecer conexão e operar recursos no servidor.

#! /usr/bin/env python
# -*- coding:utf-8  -*-
# __author__ == "tyomcat"
from multiprocessing import Queue,Pool
import multiprocessing,time,random
def write(q):
  for value in ['A','B','C','D']:
    print "Put %s to Queue!" % value
    q.put(value)
    time.sleep(random.random())
def read(q,lock):
  while True:
    lock.acquire()
    if not q.empty():
      value=q.get(True)
      print "Get %s from Queue" % value
      time.sleep(random.random())
    else:
      break
    lock.release()
if __name__ == "__main__":
  manager=multiprocessing.Manager()
  q=manager.Queue()
  p=Pool()
  lock=manager.Lock()
  pw=p.apply_async(write,args=(q,))
  pr=p.apply_async(read,args=(q,lock))
  p.close()
  p.join()
  print
  print "Todos os dados foram escritos e lidos"

Quarto, assíncrono

Independentemente de thread ou processo, eles usam a mesma arquitetura de sincronismo. Quando ocorre um bloqueio, a performance diminui significativamente, não aproveitando plenamente o potencial do CPU, desperdiçando o investimento em hardware, e mais importante, causando o entrelaçamento rígido dos módulos de software, dificultando a expansão e a mudança futuras.

Independentemente de processo ou thread, cada bloqueio e troca requer uma chamada de sistema (system call), primeiro fazer o CPU correr o programa de调度 do sistema operacional, e então o programa de调度 decide qual processo (thread) deve correr.

Hoje, os servidores assíncronos populares são baseados em eventos (como nginx).

No modelo de evento de driver de evento assíncrono, operações que causariam bloqueio são convertidas em operações assíncronas. A thread principal é responsável por iniciar essa operação assíncrona e tratar o resultado dessa operação. Como todas as operações de bloqueio são convertidas em operações assíncronas, teoricamente, a maior parte do tempo da thread principal é gasto em tarefas de cálculo reais, reduzindo o tempo de调度 de multi-thread, então a performance desse modelo geralmente é melhor.

Isso é tudo o que há no artigo. Espero que ajude na sua aprendizagem e que você também apoie o Tutorial de Grito.

Declaração: O conteúdo deste artigo é extraído da internet, pertence ao respectivo proprietário. O conteúdo é contribuído e carregado voluntariamente pelos usuários da internet. Este site não possui direitos autorais, não foi editado manualmente e não assume responsabilidades legais relacionadas. Se você encontrar conteúdo suspeito de violação de direitos autorais, por favor, envie e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas. Apenas após verificação, o site deletará o conteúdo suspeito de violação de direitos autorais.)

Você também pode gostar