Heim >Backend-Entwicklung >Python-Tutorial >Python-Multithreading

Python-Multithreading

高洛峰
高洛峰Original
2016-10-20 11:43:451219Durchsuche

Erläutern wir zunächst das Anwendungsszenario von Multithreading: Wenn Python mehrere Aufgaben verarbeitet, sind diese Aufgaben asynchroner Natur und erfordern mehrere gleichzeitige Transaktionen. Die laufende Reihenfolge jeder Transaktion kann unsicher, zufällig und unvorhersehbar sein. Vorhergesagt. Rechenintensive Aufgaben können sequentiell in mehreren Teilaufgaben ausgeführt oder im Multithread-Verfahren verarbeitet werden. I/O-intensive Aufgaben sind jedoch nur schwer im Single-Threading-Verfahren zu bewältigen. Wird kein Multi-Threading verwendet, können sie nur mit einem oder mehreren Timern bewältigt werden.

Lassen Sie uns über Prozesse und Threads sprechen: Ein Prozess (manchmal auch Schwergewichtsprozess genannt) ist eine Ausführung eines Programms. Genauso wie wenn wir ps -aux | verwenden, gibt es immer einen, der von ihm selbst generiert wird Bei diesem Grep-Prozess verfügt jeder Prozess über einen eigenen Adressraum, Speicher, Datenstapel und andere Hilfsdaten, die seine Laufbahn aufzeichnen. Daher kann jeder Prozess keine Informationen direkt austauschen und nur die Kommunikation zwischen Prozessen nutzen. IPC).

Der größte Unterschied zwischen Threads (Lightweight-Prozessen) und Prozessen besteht darin, dass alle Threads im selben Prozess ausgeführt werden, dieselbe Betriebsumgebung und denselben Datenraum verwenden. Daher können Threads bequemer als Prozesse Daten austauschen und miteinander kommunizieren und Transaktionen gleichzeitig ausführen.

Um die Beziehung zwischen Speicherprozessen und Threads leicht zu verstehen, können wir eine Analogie ziehen: Vergleichen Sie die CPU mit einem Umzugsunternehmen, und dieses Umzugsunternehmen verfügt zunächst nur über ein Auto (einen Prozess). Diese Firma ist sehr arm und hat nur einen Mitarbeiter (einziger Thread). Dann hat der Chef nur bis zu 5 Häuser pro Tag umgezogen Mitarbeiter (mehr Thread), auf diese Weise wird jeder Mitarbeiter dazu gebracht, jeweils nur in ein Haus zu ziehen und sich dann auszuruhen, das Auto aufzugeben und andere in das nächste Haus ziehen zu lassen. Dies scheint nicht der Fall zu sein Verbessert tatsächlich die Effizienz, erhöht aber die Kosten, oder? Das liegt daran, dass die globale Interpretersperre GIL (Global Interpreter Lock) die Thread-Sicherheit gewährleistet (sicherstellt, dass Daten sicher gelesen werden können), dh nur ein Thread kann auf der CPU ausgeführt werden Gleichzeitig ist dies ein einzigartiger Mechanismus von Python. Selbst wenn Ihre Betriebsumgebung über zwei CPUs verfügt, verwendet die virtuelle Python-Maschine nur eine CPU, was bedeutet, dass GIL direkt dazu führt, dass CPython die Leistung nicht nutzen kann von physischen Multi-Cores zur Beschleunigung des Betriebs. Eine ausführliche Erklärung (Probleme, die aus der Geschichte übrig geblieben sind, Hardware entwickelt sich zu schnell) finden Sie in diesem Blog:

http://blog.sina.com.cn/s/blog_64ecfc2f0102uzzf.html

in In der Python-Kernprogrammierung empfiehlt der Autor aus folgenden Gründen dringend, nicht das Thread-Modul, sondern das Threading-Modul zu verwenden:

1. Wenn der Haupt-Thread beendet wird, werden alle anderen Threads beendet ohne gelöscht zu werden, und das Thread-Modul kann den sicheren Ausgang aller untergeordneten Threads nicht schützen. Das heißt, das Thread-Modul unterstützt keine Daemons.

2. Die Attribute des Thread-Moduls können mit dem Threading in Konflikt stehen.

3. Das Low-Level-Thread-Modul verfügt über sehr wenige Synchronisationsprimitive (eigentlich gibt es nur eines, das schlafen sollte).

1. Thread-Modul

Das Folgende sind zwei Beispielcodes ohne Verwendung von GIL und mit Verwendung von GIL:

1. Codebeispiel ohne Verwendung von GIL:

from time import sleep,ctime
import thread

def loop0():
    print 'start loop 0 at: ',ctime()
    sleep(4)
    print 'loop 0 done at: ',ctime()
def loop1():
    print 'start loop 1 at: ',ctime()
    sleep(2)
    print 'loop 1 done at: ',ctime()
def main():
    print 'start at: ',ctime()
    thread.start_new_thread(loop0,())
    thread.start_new_thread(loop1,())
    sleep(6)
    print 'all loop is done, ' ,ctime()

if __name__=='__main__':
    main()
 

输出结果:

start at:  Thu Jan 28 10:46:27 2016
start loop 0 at:   Thu Jan 28 10:46:27 2016

start loop 1 at:   Thu Jan 28 10:46:27 2016
loop 1 done at:  Thu Jan 28 10:46:29 2016
loop 0 done at:  Thu Jan 28 10:46:31 2016
all loop is done,  Thu Jan 28 10:46:33 2016

Wie aus der obigen Ausgabe ersichtlich ist, haben wir erfolgreich zwei Threads gestartet und sie mit dem Hauptthread synchronisiert. Bei 2 Sekunden war Schleife1 zuerst abgeschlossen, bei 4 Sekunden war Schleife0 abgeschlossen und nach weiteren 2 Sekunden war der Hauptthread abgeschlossen. Der gesamte Hauptthread hat 6 Sekunden verstrichen und Schleife0 und Schleife1 werden gleichzeitig abgeschlossen.

2. Codebeispiel mit GIL:

import thread
from time import sleep,ctime
loops = [4,2]
def loop(nloop,nsec,lock):
    print 'start loop',nloop,'at: ',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at:',ctime()
    lock.release()
def main():
    print 'starting at:',ctime()
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()                          #创建锁的列表,存在locks中
        lock.acquire()                         
        locks.append(lock)                                      
    for i in nloops:
        thread.start_new_thread(loop,(i,loops[i],locks[i]))    #创建线程,参数为循环号,睡眠时间,锁
    for i in nloops:
        while locks[i].locked():                              #等待循环完成,解锁
            pass
    print 'all DONE at:',ctime()
if __name__ == '__main__':
    main()
 

以上输出如下:

starting at: Thu Jan 28 14:59:22 2016
start loop  0  at:   Thu Jan 28 14:59:22 2016

start loop  1  at:   Thu Jan 28 14:59:22 2016
loop 1 done at: Thu Jan 28 14:59:24 2016
loop 0 done at: Thu Jan 28 14:59:26 2016
all DONE at: Thu Jan 28 14:59:26 2016

Dauert 4 Sekunden, was die Effizienz verbessert und besser ist als die Verwendung einer Funktion „sleep()“ im Hauptthread Es ist vernünftiger, es zeitlich festzulegen.

2. Threading-Modul

1. Thread-Klasse

In der Thread-Klasse können Sie die folgenden drei Methoden verwenden, um Threads zu erstellen:

(1) Erstellen Sie eine Thread-Instanz und übergeben Sie ihr eine Funktion

(2) Erstellen Sie eine Thread-Instanz und übergeben Sie ihr ein aufrufbares Klassenobjekt

(3) Leiten Sie vom Thread eine Unterklasse ab und erstellen Sie ein Objekt dieser Unterklasse

Methode (1)

__author__ = 'dell'
import threading
from time import sleep,ctime
def loop0():
    print 'start loop 0 at:',ctime()
    sleep(4)
    print 'loop 0 done at:',ctime()
def loop1():
    print 'start loop 1 at:',ctime()
    sleep(2)
    print 'loop 1 done at:',ctime()
def main():
    print 'starting at:',ctime()
    threads = []
    t1 = threading.Thread(target=loop0,args=())          #创建线程
    threads.append(t1)
    t2 = threading.Thread(target=loop1,args=())
    threads.append(t2)
    for t in threads:
        t.setDaemon(True)<span style="white-space:pre">    </span>      #开启守护线程(一定要在start()前调用)
        t.start()<span style="white-space:pre">        </span>      #开始线程执行
    for t in threads:<span style="white-space:pre">                    </span>
        t.join()<span style="white-space:pre">        </span>      #将程序挂起阻塞,直到线程结束,如果给出数值,则最多阻塞timeout秒

if __name__ == &#39;__main__&#39;:
    main()
    print &#39;All DONE at:&#39;,ctime()

在这里,就不用像thread模块那样要管理那么多锁(分配、获取、释放、检查等)了,同时我也减少了循环的代码,直接自己编号循环了,得到输出如下:
 

starting at: Thu Jan 28 16:38:14 2016
start loop 0 at: Thu Jan 28 16:38:14 2016
start loop 1 at: Thu Jan 28 16:38:14 2016
loop 1 done at: Thu Jan 28 16:38:16 2016
loop 0 done at: Thu Jan 28 16:38:18 2016
All DONE at: Thu Jan 28 16:38:18 2016

Das Ergebnis ist das gleiche, aber aus der Logik des Codes ist es viel klarer. Die anderen beiden Typen werden hier nicht veröffentlicht. Der größte Unterschied zwischen der Instanziierung eines Threads und dem Aufruf von thread.start_new_thread besteht darin, dass der neue Thread nicht sofort mit der Ausführung beginnt. Das heißt, in der Thread-Klasse des Threading-Moduls wird er nach dem Aufruf vereinheitlicht. start()-Funktion, die unserem Programm gute Synchronisationseigenschaften verleiht.

Das Folgende ist ein Vergleichsbeispiel zwischen Single-Thread und Multi-Thread. Zwei Sätze von Operationen werden durch Multiplikation bzw. Division abgeschlossen und zeigen so die Effizienzverbesserung durch Multi-Threading.

from time import ctime,sleep
import threading

def multi():
    num1 = 1
    print &#39;start mutiple at:&#39;,ctime()
    for i in range(1,10):
       num1 = i*num1
       sleep(0.2)
    print &#39;mutiple finished at:&#39;,ctime()
    return num1
def divide():
    num2 = 100
    print &#39;start division at:&#39;,ctime()
    for i in range(1,10):
        num2 = num2/i
        sleep(0.4)
    print &#39;division finished at:&#39;,ctime()
    return num2
def main():
    print &#39;---->single Thread&#39;
    x1 = multi()
    x2 = divide()
    print &#39;The sum is &#39;,sum([x1,x2]),&#39;\nfinished singe thread&#39;,ctime()

    print &#39;----->Multi Thread&#39;
    threads = []
    t1 = threading.Thread(target=multi,args=())
    threads.append(t1)
    t2 = threading.Thread(target=divide,args=())
    threads.append(t2)
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()

if __name__ == &#39;__main__&#39;:
    main()

结果如下:

 

---->single Thread

start mutiple at: Thu Jan 28 21:41:18 2016

mutiple finished at: Thu Jan 28 21:41:20 2016

start division at: Thu Jan 28 21:41:20 2016

division finished at: Thu Jan 28 21:41:24 2016

The sum is  362880 

finished singe thread Thu Jan 28 21:41:24 2016

----->Multi Thread

start mutiple at: Thu Jan 28 21:41:24 2016

start division at: Thu Jan 28 21:41:24 2016

mutiple finished at: Thu Jan 28 21:41:26 2016

division finished at: Thu Jan 28 21:41:27 2016

The sum is : 362880


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn