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

python并发编程之多进程、多线程、异步和协程详解

Recentemente ho studiato la concorrenza in Python, quindi ho fatto una somma delle concorrenza multi-processo, multi-thread, asincrona e coroutine.
1. Thread multipli

I thread multipli permettono la presenza di più controlli all'interno di un processo, in modo da far sì che più funzioni siano in stato attivo contemporaneamente, permettendo così che le operazioni delle funzioni vengano eseguite contemporaneamente. Anche se il computer ha un solo CPU, è possibile ottenere l'effetto di esecuzione simultanea dei thread attraverso la continua commutazione tra le istruzioni dei diversi thread.

Le thread multipli sono equivalenti a un sistema di concorrenza (concunrrency). I sistemi di concorrenza eseguono generalmente più compiti contemporaneamente. Se più compiti possono condividere risorse, specialmente quando scrivono contemporaneamente a un variabile, è necessario risolvere il problema di sincronizzazione, come nel sistema di vendita dei biglietti del treno a più thread: due istruzioni, una per controllare se i biglietti sono stati venduti, e un'altra per vendere i biglietti contemporaneamente da più finestre, potrebbe accadere di vendere biglietti inesistenti.

In condizioni di concorrenza, l'ordine di esecuzione delle istruzioni è deciso dal kernel. All'interno della stessa thread, le istruzioni vengono eseguite in ordine, ma è difficile determinare chiaramente quale istruzione eseguirà prima tra le istruzioni di diverse thread. Pertanto, è necessario considerare i problemi di sincronizzazione multi-thread. La sincronizzazione (synchronization) significa che in un determinato periodo di tempo è permesso solo a una thread di accedere a una risorsa.

1. Modulo thread

2. Modulo threading
threading.Thread crea una thread.

Aggiungere un lock mutex per determinare se ci sono biglietti in eccesso e vendere i biglietti, in modo che non si verifichi una situazione in cui una thread ha appena determinato che non ci sono biglietti in eccesso, mentre un'altra thread esegue l'operazione di vendita dei biglietti.

#! /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 "Finestra:", tid,", rimanenti biglietti:", 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()

2. Coroutines (noto anche come microthread, fibra)

Le coroutines, diversamente dal scheduling prevaricante delle thread, sono di scheduling cooperativo. Le coroutines sono anche single-thread, ma permettono di scrivere codice non umano che richiederebbe un modo asincrono + callback in modo sembrante sincrono.

1. Nella python, i coroutines possono essere implementate da generatori (generator).

Prima di tutto, è necessario avere una solida comprensione di generatori e yield.

Chiamare una funzione python comune di solito inizia dall'esecuzione del primo codice della funzione e termina con la frase return, un'eccezione o l'esecuzione della funzione (che può anche essere considerata come un implicito return di None).

Una volta che la funzione ha restituito il controllo al chiamante, significa che tutto è finito. A volte è possibile creare una funzione che può produrre una sequenza per “salvare il proprio lavoro”, questo è il generatore (funzione che utilizza la parola chiave yield).

La capacità di “produrre una sequenza” è perché la funzione non restituisce come di solito. Il significato implicito di return è che la funzione sta restituendo il controllo dell'esecuzione al punto da cui è stata chiamata. Mentre il significato implicito di "yield" è che il trasferimento del controllo è temporaneo e volontario, la nostra funzione recupérerà il controllo in futuro.

Ecco un esempio di produttore/consumatore:

#! /usr/bin/python
#-* coding: utf-8 -*
# __author__ ="tyomcat"
import time
import sys
# Produttore
def produce(l):
  i=0
  while 1:
    if i < 10:
      l.append(i)
      yield i
      i=i+1
      time.sleep(1)
    else:
      return   
# Consumatore
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 il programma esegue produce(yield i), restituisce un generatore e si sospende l'esecuzione. Quando chiamiamo p.next() in custom, il programma torna a produce(yield i) per continuare l'esecuzione. In questo modo, l'elemento viene nuovamente aggiunto a l, quindi stampiamo l.pop(), fino a quando p.next() solleva l'eccezione StopIteration.

2、Stackless Python

3、modulo greenlet

L'esecuzione basata su greenlet ha prestazioni che sono solo leggermente inferiori a Stackless Python, circa la metà della velocità di Stackless Python, e quasi un ordine di grandezza più veloce rispetto ad altre soluzioni. In realtà, greenlet non è un vero meccanismo di concorrenza, ma una transizione tra diversi blocchi di codice di esecuzione di diverse funzioni nella stessa thread, implementando "tu esegui un po', io esegui un po'" e deve essere specificato quando e a quale punto eseguire la transizione.

4、modulo eventlet

3、Processi multipli
1、Processo figlio (modulo subprocess)

In Python, attraverso il pacchetto subprocess, si può forzare un processo figlio e eseguire un programma esterno.

Quando si chiama un comando del sistema, il modulo os è il primo da considerare. Si utilizzano os.system() e os.popen() per eseguire l'operazione. Tuttavia, questi comandi sono troppo semplici e non possono completare operazioni complesse come fornire input a un comando in esecuzione o leggere l'output del comando, determinare lo stato di esecuzione del comando, gestire la parallela esecuzione di più comandi, ecc. In questo caso, il comando Popen del modulo subprocess può completare efficacemente le operazioni necessarie

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

Utilizzando subprocess.PIPE, connetti l'input e l'output di più sotto-processi per formare un tubo (pipe):

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)

Il metodo communicate() legge i dati da stdout e stderr e li inserisce in stdin.

2. Processi multipli (pacchetto multiprocessing)

(1) Il pacchetto multiprocessing è il pacchetto di gestione dei processi multipli in Python. Simile a threading.Thread, può utilizzare l'oggetto multiprocessing.Process per creare un processo.

Il pool di processi (Process Pool) può creare più processi.

apply_async(func,args) prendere un processo dal pool di processi per eseguire func, args sono i parametri di func. Ritorna un oggetto AsyncResult, su cui puoi chiamare il metodo get() per ottenere il risultato.

close() il pool di processi non crea più nuovi processi

join() attendere tutti i processi nel pool. È necessario chiamare il metodo close() del Pool prima di chiamare join().

!/usr/bin/env python
# -*- coding:utf-8  -*-
# __author__ == "tyomcat"
# "Il mio computer ha 4 cpu"
from multiprocessing import Pool
import os, time
def long_time_task(name):
  print 'Esegui compito %s (%s)...' % (name, os.getpid())
  start = time.time()
  time.sleep(3)
  end = time.time()
  print 'L'attività %s è durata %0.2f secondi.' % (name, (end - start))
if __name__=='__main__':
  print 'Processo padre %s.' % os.getpid()
  p = Pool()
  for i in range(4):
    p.apply_async(long_time_task, args=(i,))
  print 'In attesa che tutti i sottoprocessi siano completati...'
  p.close()
  p.join()
  print 'Tutti i sottoprocessi completati.'

(2) Condivisione delle risorse tra processi multipli

Tramite memoria condivisa e oggetti Manager: utilizzare un processo come server, stabilire un Manager per archiviare effettivamente le risorse.

Altri processi possono accedere al Manager tramite parametri o secondo l'indirizzo, stabilire una connessione e operare sulle risorse del server.

!/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 "Tutti i dati sono stati scritti e letti"

Quattro, asincrono

Sia che si tratti di thread o di processo, utilizzano entrambi i同步 bit, quando si verifica un blocco, la prestazione si riduce drasticamente, non può sfruttare appieno le potenzialità del CPU, spreca l'investimento hardware, e più importante, provoca l'indurimento del modulo software, il.tight coupling, non può essere tagliato, non favorevole all'espansione e alla variazione futura.

Sia che si tratti di processo o di thread, ogni blocco, ogni passaggio richiede di cadere in un chiamata di sistema (system call), prima di far funzionare il CPU il programma di调度 del sistema operativo, poi il programma di调度 decide quale processo (thread) deve funzionare. Inoltre, quando diversi thread accedono a codice mutuale, è necessario aggiungere un lock.

Oggi i server asincroni popolari sono basati su modelli di eventi (ad esempio nginx).

Nel modello di modello di evento asincrono, le operazioni che causano il blocco vengono trasformate in operazioni asincrone, la thread principale è responsabile dell'inizializzazione di questa operazione asincrona e del trattamento del risultato di questa operazione asincrona. Poiché tutte le operazioni di blocco vengono trasformate in operazioni asincrone, teoricamente la maggior parte del tempo della thread principale è dedicata al trattamento delle vere e proprie attività di calcolo, riducendo il tempo di调度 delle thread, quindi la performance di questo modello è generalmente migliore.

Questo è tutto il contenuto dell'articolo, spero che sia utile per il vostro studio, e vi prego di sostenere fortemente il tutorial Shouting.

Dichiarazione: il contenuto di questo articolo è stato preso da Internet, è di proprietà dei rispettivi autori, il contenuto è stato contribuito volontariamente dagli utenti di Internet e caricato autonomamente, il sito web non detiene il diritto di proprietà, non è stato editato manualmente e non assume alcuna responsabilità legale. Se trovi contenuti sospetti di violazione del copyright, è gradito inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare, fornendo prove pertinenti. Una volta verificata, il sito eliminerà immediatamente i contenuti sospetti di violazione del copyright.