Heim  >  Artikel  >  Backend-Entwicklung  >  Übersicht über Prozesse und Threads in der Python-Entwicklung

Übersicht über Prozesse und Threads in der Python-Entwicklung

零下一度
零下一度Original
2017-06-25 10:20:291531Durchsuche

Vorwort

Übersicht über Prozesse und Threads:

 Viele Studierende haben gehört, dass moderne Betriebssysteme wie Mac OS X, UNIX, Linux, Windows usw. allesamt „Multitasking“ unterstützen.

Was ist „Multitasking“? Vereinfacht ausgedrückt kann das Betriebssystem mehrere Aufgaben gleichzeitig ausführen. Sie surfen beispielsweise mit einem Browser im Internet, hören MP3-Player und erledigen Ihre Hausaufgaben. Dabei werden mindestens drei Aufgaben gleichzeitig ausgeführt. Viele Aufgaben laufen stillschweigend gleichzeitig im Hintergrund, werden aber nicht auf dem Desktop angezeigt.

Heutzutage erfreuen sich Multi-Core-CPUs großer Beliebtheit, aber auch die bisherigen Single-Core-CPUs können Multitasking ausführen. Wie führt eine Single-Core-CPU mehrere Aufgaben aus, da der CPU-Ausführungscode sequentiell ausgeführt wird?

Die Antwort ist, dass das Betriebssystem abwechselnd jede Aufgabe abwechselnd ausführen lässt , Aufgabe 1 wird 0,01 Sekunden lang ausgeführt, wechselt zu Aufgabe 2, Aufgabe 2 wird ausgeführt 0,01 Sekunden und wechselt dann zu Aufgabe 3. 0,01 Sekunden lang ausführen ... und dies wiederholt ausführen. Oberflächlich betrachtet wird jede Aufgabe abwechselnd ausgeführt, aber da die Ausführungsgeschwindigkeit der CPU so hoch ist, haben wir das Gefühl, als würden alle Aufgaben gleichzeitig ausgeführt.

Echte Parallelität zur Ausführung von Multitasking kann jedoch nur auf einer Multi-Core-CPU implementiert werden große Anzahl von Aufgaben Weit mehr als die Anzahl der CPU-Kerne, sodass das Betriebssystem automatisch nacheinander viele Aufgaben für jeden Kern einplant.

Für das Betriebssystem ist eine Aufgabe ein Prozess. Das Öffnen eines Browsers startet beispielsweise einen Browserprozess, und das Öffnen eines Notepads startet einen Notepad-Prozess, das Öffnen von zwei Notepads startet zwei Notepad Prozesse, und das Öffnen eines Words startet einen Word-Prozess.

Einige Prozesse können mehr als eine Sache gleichzeitig ausführen, z. B. Word, das gleichzeitig Eingabe, Rechtschreibprüfung, Drucken und andere Dinge ausführen kann. Wenn Sie innerhalb eines Prozesses mehrere Dinge gleichzeitig tun möchten, müssen Sie mehrere „Unteraufgaben“ gleichzeitig ausführen. Wir nennen diese „Unteraufgaben“ innerhalb des Prozesses Thread(Thread).

Da jeder Prozess mindestens eine Sache tun muss, hat ein Prozess mindestens einen Thread . Natürlich kann ein komplexer Prozess wie Word mehrere Threads haben und mehrere Threads können gleichzeitig ausgeführt werden. Die Ausführungsmethode von Multithreading ist dieselbe wie die von Multiprozessen und wird auch vom Betriebssystem gesteuert Wechseln Sie bei mehreren Prozessen schnell zwischen Threads , sodass sich jeder Thread kurzzeitig abwechselt und scheinbar gleichzeitig ausgeführt wird. Um wirklich mehrere Threads gleichzeitig ausführen zu können, ist natürlich eine Multi-Core-CPU erforderlich. Alle Python-Programme, die wir zuvor geschrieben haben, sind Prozesse, die einzelne Aufgaben ausführen, das heißt, es gibt nur einen Thread. Was ist, wenn wir mehrere Aufgaben gleichzeitig ausführen möchten?

 

Es gibt zwei Lösungen:

 Eine besteht darin, mehrere Prozesse zu starten, obwohl jeder Prozess nur einen Thread hat. aber mehrere Prozesse können mehrere Aufgaben gemeinsam ausführen.

Eine andere Methode besteht darin, einen Prozess zu starten und mehrere Threads in einem Prozess zu starten, sodass mehrere Threads mehrere Aufgaben gemeinsam ausführen können.

Natürlich gibt es eine dritte Methode, die darin besteht, mehrere Prozesse zu starten, und jeder Prozess startet mehrere Threads, sodass mehr Aufgaben gleichzeitig ausgeführt werden können. Dieses Modell ist komplexer und wird tatsächlich selten verwendet

.

Zusammenfassend gibt es drei Möglichkeiten, Multitasking zu erreichen:


  • Multiprozess-Modus;
    • Multi-Prozess + Multi-Thread Modus.

Die gleichzeitige Ausführung mehrerer Aufgaben ist nicht unabhängig, sondern muss miteinander kommunizieren und koordiniert werden. Manchmal muss Aufgabe 1 pausieren und auf die Aufgabe warten 2. Um die Ausführung fortzusetzen, können Aufgabe 3 und Aufgabe 4 nicht gleichzeitig ausgeführt werden. Daher ist die Komplexität von Multiprozess- und Multithread-Programmen viel höher als die von uns schrieb vorhin.

Aufgrund der hohen Komplexität und Schwierigkeit beim Debuggen möchten wir kein Multitasking schreiben, es sei denn, es muss sein. Allerdings kommt es oft vor, dass Multitasking nicht mehr wegzudenken ist. Denken Sie daran, einen Film auf einem Computer anzusehen. Ein Thread muss das Video und ein anderer Thread den Ton abspielen. Andernfalls kann bei der Implementierung in einem einzelnen Thread nur das Video und dann der Ton abgespielt werden, oder der Ton muss zuerst abgespielt werden und dann das Video. Das ist offensichtlich nicht möglich.

Python unterstützt sowohl Multi-Processing als auch Multi-Threading. Wir werden besprechen, wie man diese beiden Arten von Multitasking-Programmen schreibt.

Prozess

Erstes Kennenlernen:

 Um Multi-Processing in einem Python-Programm zu implementieren, müssen wir zunächst die relevanten Kenntnisse des Betriebssystems verstehen. Das Unix/Linux-Betriebssystem bietet einen Systemaufruf fork(), was etwas ganz Besonderes ist. Gewöhnliche Funktionsaufrufe rufen einmal auf und geben einmal zurück, aber fork() ruft einmal auf und gibt zweimal zurück , weil das Betriebssystem automatisch den aktuellen Prozess (den sogenannten übergeordneten Prozess) kopiert (sogenannte untergeordnete Prozesse). ) und kehren dann im übergeordneten bzw. untergeordneten Prozess zurück. Der untergeordnete Prozess gibt immer 0 zurück, während der übergeordnete Prozess die ID des untergeordneten Prozesses zurückgibt. Der Grund dafür ist, dass ein übergeordneter Prozess viele untergeordnete Prozesse austeilen kann. Daher muss der übergeordnete Prozess die ID jedes untergeordneten Prozesses aufzeichnen und der untergeordnete Prozess muss nur getppid() Sie können die ID des übergeordneten Prozesses abrufen. Pythons os-Modul kapselt allgemeine Systemaufrufe, einschließlich fork, die problemlos in Python-Programmen verwendet werden können. Erstellen Sie einen untergeordneten Prozess :

import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
    print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
    
# Process (44587) start...
# I (44587) just created a child process (44588).
# I am child process (44588) and my parent is 44587.

Da Windows keinen -Aufruf hat, wird der obige Code unter Windows nicht ausgeführt. Da das Mac-System auf dem BSD-Kernel (einer Art Unix-Kernel) basiert, ist die Ausführung auf einem Mac kein Problem. Es wird empfohlen, zum Erlernen von Python einen Mac zu verwenden! Mit dem fork-Aufruf kann ein Prozess einen untergeordneten Prozess kopieren, um die neue Aufgabe zu verarbeiten. Ein gewöhnlicher Apache-Server lässt den übergeordneten Prozess auf dem Port lauschen aus. Untergeordneter Prozess zur Verarbeitung neuer http-Anfragen. fork

Multiprocessing-Modul:

Wenn Sie planen, ein Multiprozess-Dienstprogramm zu schreiben, Unix/ Linux ist zweifellos die beste Wahl und die richtige Wahl. Ist es unmöglich, Multiprozessprogramme in Python unter Windows zu schreiben, da Windows keine

fork-Aufrufe hat? Da Python plattformübergreifend ist, sollte es natürlich plattformübergreifende Multiprozessunterstützung bieten. Das Modul multiprocessing ist eine plattformübergreifende Version des Multiprozessmoduls. Das Modul multiprocessing stellt eine Klasse Process zur Darstellung eines Prozessobjekts bereit. Das folgende Beispiel zeigt das Starten eines untergeordneten Prozesses und das Warten auf dessen Ende:

import os
import time

# 子进程要执行的代码
def run_proc(name):
    time.sleep(1)
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))    # args里面为何要用,隔开?
    p.start()                                        # 子进程启动,不加这个子进程不执行
    p.join()             # 等待子进程p的执行完毕后再向下执行,不加此项,主程序执行完毕,子进程依然会继续执行不受影响
    print('Child process end.'),

# Parent process 8428.
# Run child process test (9392)...
# Child process end.
<span style="font-size: 13px; color: #800000">Process实例化时执行self._args = tuple(args)操作,如果不用,隔开生成的slef._args就是一个个字母了,传入两个参数以上是就不用加,号了,如下:<br></span>
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},                 *, daemon=None):assert group is None, 'group argument must be None for now'count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._popen = None
        self._target = target
        self._args = tuple(args)

a =('ers')
b = tuple(a)print(b)# ('e', 'r', 's')a1 =('ers','gte')
b1 = tuple(a1)print(b1)# ('ers', 'gte')
Prozesscode

Pool-Prozesspool:

Wenn Sie eine große Anzahl untergeordneter Prozesse starten möchten, können Sie den Prozesspool verwenden, um untergeordnete Prozesse stapelweise zu erstellen:

from multiprocessing import Pool,cpu_count
import os, time, random

def long_time_task(name):
    print(&#39;Run task %s (%s)...&#39; % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print(&#39;Task %s runs %0.2f seconds.&#39; % (name, (end - start)))

def Bar(arg):
    print(&#39;-->exec done:&#39;,arg,os.getpid())

if __name__==&#39;__main__&#39;:
    print(&#39;Parent process %s.&#39; % os.getpid())
    p = Pool(cpu_count())               # 获取当前cpu核数,多核cpu的情况下多进程才能实现真正的并发
    for i in range(5):
        # p.apply_async(func=long_time_task, args=(i,), callback=Bar) #callback回调 执行完func后再执行callback 用主程序执行
        p.apply_async(long_time_task, args=(i,))
    print(&#39;Waiting for all subprocesses done...&#39;)
    p.close()
    p.join()              # !等待进程池执行完毕,不然主进程执行完毕后,进程池直接关闭
    print(&#39;All subprocesses done.&#39;)

# Parent process 4492.
# Waiting for all subprocesses done...
# Run task 0 (3108)...
# Run task 1 (7936)...
# Run task 2 (11236)...
# Run task 3 (8284)...
# Task 2 runs 0.86 seconds.
# Run task 4 (11236)...
# Task 0 runs 1.34 seconds.
# Task 1 runs 1.49 seconds.
# Task 3 runs 2.62 seconds.
# Task 4 runs 1.90 seconds.
# All subprocesses done.

重点:另进程池里的进程执行完毕后,进程关闭自动销毁,不再占用内存,同理,非进程池创建的子进程,执行完毕后也是自动销毁,具体测试如下:

from multiprocessing import Pool,cpu_countimport os, time, randomdef long_time_task(name):print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()print('Task %s runs %0.2f seconds.' % (name, (end - start)))def count_process():import psutil
    pids = psutil.pids()
    process_name = []for pid in pids:
        p = psutil.Process(pid)
        process_name.append(p.name())       # 获取进程名# process_name.append(p.num_threads())       # 获取进程的线程数# print process_nameprint len(process_name)if __name__=='__main__':print('Parent process %s.' % os.getpid())
    p = Pool(4)for i in range(5):
        p.apply_async(long_time_task, args=(i,))print('Waiting for all subprocesses done...')
    count_process()        # 进程池开始时进程数(包含系统其他应用进程)    p.close()
    p.join()             
    count_process()        # 进程池关闭时进程数print('All subprocesses done.')# Parent process 8860.# Waiting for all subprocesses done...# Run task 0 (2156)...# Run task 1 (1992)...# Run task 2 (10680)...# Run task 3 (11216)...# 109           开始# Task 2 runs 0.93 seconds.# Run task 4 (10680)...# Task 1 runs 1.71 seconds.# Task 3 runs 2.01 seconds.# Task 0 runs 2.31 seconds.# Task 4 runs 2.79 seconds.# 105           结束# All subprocesses done.
进程池子进程执行完毕后销毁

代码解读:

Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

请注意输出的结果,task 0123是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:

p = Pool(5)

就可以同时跑5个进程。

由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

 

进程间通信:

  Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    print(&#39;Process to write: %s&#39; % os.getpid())
    for value in [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]:
        print(&#39;Put %s to queue...&#39; % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print(&#39;Process to read: %s&#39; % os.getpid())
    while True:
        value = q.get(True)
        print(&#39;Get %s from queue.&#39; % value)

if __name__==&#39;__main__&#39;:
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()      # 强制关闭子进程

# Process to write: 9472
# Put A to queue...
# Process to read: 3948
# Get A from queue.
# Put B to queue...
# Get B from queue.
# Put C to queue...
# Get C from queue.

在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所有,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了。

 

进程间共享数据:

有时候我们不仅仅需要进程间数据传输,也需要多进程间进行数据共享,即可以使用同一全局变量;如:为何下面程序的列表输出为空?

from multiprocessing import Process, Manager
import os

# manager = Manager()
vip_list = []
#vip_list = manager.list()

def testFunc(cc):
    vip_list.append(cc)
    print &#39;process id:&#39;, os.getpid()

if __name__ == &#39;__main__&#39;:
    threads = []

    for ll in range(10):
        t = Process(target=testFunc, args=(ll,))
        t.daemon = True
        threads.append(t)

    for i in range(len(threads)):
        threads[i].start()

    for j in range(len(threads)):
        threads[j].join()

    print "------------------------"
    print &#39;process id:&#39;, os.getpid()
    print vip_list

# process id: 9436
# process id: 11120
# process id: 10636
# process id: 1380
# process id: 10976
# process id: 10708
# process id: 2524
# process id: 9392
# process id: 10060
# process id: 8516
# ------------------------
# process id: 9836
# []

如果你了解 python 的多线程模型,GIL 问题,然后了解多线程、多进程原理,上述问题不难回答,不过如果你不知道也没关系,跑一下上面的代码你就知道是什么问题了。因为进程间内存是独立的

正如上面提到的,在进行并发编程时,最好尽可能避免使用共享状态。在使用多个进程时尤其如此。但是,如果您确实需要使用一些共享数据,那么多处理提供了两种方法。

共享内存:

数据可以使用值或数组存储在共享内存映射中。例如,下面的代码:

from multiprocessing import Process, Value, Array

def f(n, a):
    n.value = 3.1415927
    for i in range(len(a)):
        a[i] = -a[i]

if __name__ == &#39;__main__&#39;:
    num = Value(&#39;d&#39;, 0.0)
    arr = Array(&#39;i&#39;, range(10))

    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()

    print num.value
    print arr[:]
    
# 3.1415927
# [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

在创建num和arr时使用的“i”和“i”参数是数组模块使用的类型的类型:“表示双精度浮点数”,“i”表示一个已签名的整数。这些共享对象将是进程和线程安全的。为了更灵活地使用共享内存,您可以使用多处理。sharedctypes模块支持创建从共享内存中分配的任意类型的ctypes对象。

服务进程:

manager()返回的manager对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。manager()返回的管理器将支持类型列<span class="pre">list</span><span class="pre">dict</span><span class="pre">Namespace</span><span class="pre">Lock</span><span class="pre">RLock</span><span class="pre">Semaphore</span><span class="pre">BoundedSemaphore</span><span class="pre">Condition</span><span class="pre">Event</span><span class="pre">Queue</span><span class="pre">Value</span> and <span class="pre">Array</span>。如下:

from multiprocessing import Process, Manager

def f(d, l):
    d[1] = &#39;1&#39;
    d[&#39;2&#39;] = 2
    d[0.25] = None
    l.reverse()

if __name__ == &#39;__main__&#39;:
    manager = Manager()

    d = manager.dict()
    l = manager.list(range(10))

    p = Process(target=f, args=(d, l))
    p.start()
    p.join()

    print d
    print l

# {0.25: None, 1: &#39;1&#39;, &#39;2&#39;: 2}
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

服务器进程管理器比使用共享内存对象更灵活,因为它们可以用来支持任意对象类型。另外,单个管理器可以通过网络上不同计算机上的进程共享。但是,它们比使用共享内存要慢。

更多-》》点击

 

 

小结

在Unix/Linux下,可以使用fork()调用实现多进程。

要实现跨平台的多进程,可以使用multiprocessing模块。

进程间通信是通过Queue(多进程间)Pipes(两个进程间)等实现的。

 

补充小知识点-》父进程开辟子进程,子进程开辟子子进程,如果把子进程杀掉,子子进程会被杀死吗?

import timefrom multiprocessing import Processimport osdef count_process():import psutil
    pids = psutil.pids()print len(pids)def test3():
    count_process()for i in range(10):print "test3 %s"%os.getpid()
        time.sleep(0.5)def test1():print "test1 %s"%os.getpid()
    p2 = Process(target=test3, name="protest2")
    p2.start()
    p2.join()if __name__ == '__main__':
    count_process()
    p1 = Process(target=test1, name="protest1")
    p1.start()
    time.sleep(2)
    p1.terminate()
    time.sleep(2)
    count_process()for i in range(10):print(i)
        time.sleep(1)# 86# test1 9500# 88# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# 87# 0# test3 3964# test3 3964# 1# 2# 3# 4# 5# 6# 7# 8# 9
子子进程的心路历程

 

Das obige ist der detaillierte Inhalt vonÜbersicht über Prozesse und Threads in der Python-Entwicklung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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