ホームページ  >  記事  >  バックエンド開発  >  Python 開発のプロセスとスレッドの概要

Python 開発のプロセスとスレッドの概要

零下一度
零下一度オリジナル
2017-06-25 10:20:291531ブラウズ

序文

プロセスとスレッドの概要:

多くの学生は、Mac OS X、UNIX、Linux、Windows などの最新のオペレーティング システムはすべて「マルチタスク」をサポートしていると聞いたことがあるでしょう。 " オペレーティング·システム。

「マルチタスク」とは何ですか?簡単に言えば、オペレーティング システムは複数のタスクを同時に実行できます。たとえば、ブラウザを使用してインターネットを閲覧したり、MP3 プレーヤーを聴いたり、Word で宿題に取り組んだりすることは、少なくとも 3 つのタスクを同時に実行していることになります。同時にバックグラウンドで多くのタスクが静かに実行されていますが、それらはデスクトップには表示されません。

最近はマルチコアCPUがとても普及していますが、昔のシングルコアCPUでもマルチタスクを実行することができます。 CPU の実行コードは順次実行されるため、シングルコア CPU はどのようにして複数のタスクを実行するのでしょうか?

答えは、オペレーティング システムが各タスクを交互に実行し、タスク 1 が 0.01 秒間実行され、タスク 2 に切り替わり、タスク 2 が 0.01 秒間実行され、次にタスク 3 に切り替わって 0.01 秒間実行されるということです。 ... 等々。表面的には、各タスクが交互に実行されていますが、CPU の実行速度が非常に速いため、すべてのタスクが同時に実行されているように感じます

マルチタスクの真の並列実行は、マルチコアCPUでのみ実現できます。ただし、タスクの数はCPUコアの数よりもはるかに多いため、オペレーティングシステムも自動的にスケジュールを設定します。多くのタスクが各コアで順番に実行されます。 オペレーティング システムの場合、タスクはプロセス (プロセス) です。たとえば、ブラウザーを開くとブラウザー プロセスが開始され、メモ帳を開くとメモ帳プロセスが開始され、2 つのメモ帳プロセスが作成されます。 Word を開くと Word プロセスが開始されます。

一部のプロセスでは、入力、スペルチェック、印刷などを同時に実行できる Word など、同時に複数の処理を実行できます。

プロセス

内で、同時に複数のことを実行したい場合は、複数の「サブタスク」を同時に実行する必要があります。これらの「サブタスク」をプロセススレッド(スレッド)と呼びます。 各プロセスは少なくとも 1 つのことを実行する必要があるため、プロセス には少なくとも

1 つのスレッド

が必要です。もちろん、Word のような複雑なプロセスには複数のスレッドが存在し、複数のスレッドを同時に実行できます。マルチスレッドの実行方法はマルチプロセスと同じであり、オペレーティング システムも複数のスレッドを素早く切り替えます。これにより、各スレッドが交互に短時間実行され、同時に実行されているように見えます。もちろん、実際に複数のスレッドを同時に実行するには、マルチコア CPUが必要です。 先ほど書いた Python プログラムはすべて、単一のタスクを実行するプロセスです。つまり、スレッドが 1 つだけあります。複数のタスクを同時に実行したい場合はどうすればよいでしょうか? 解決策は 2 つあります:

1 つは、複数のプロセスを起動することですが、各プロセスには 1 つのスレッドしかありませんが、複数のプロセスは複数のタスクを一緒に実行できます。

もう 1 つの方法は、プロセスを開始し、1 つのプロセス内で複数のスレッドを開始して、複数のスレッドが複数のタスクを一緒に実行できるようにすることです。

もちろん 3 番目の方法もあります。これは、複数のプロセスを開始し、各プロセスが複数のスレッドを開始して、より多くのタスクを同時に実行できるようにします。 もちろん、このモデルはより複雑で、めったに使用されません。実際には。 。

要約すると、マルチタスクを実装するには次の 3 つの方法があります。

より多くのプロセス + マルチスレッド モード。

複数のタスクを同時に実行します。通常、タスクは無関係ではありませんが、タスク 1 を一時停止し、タスク 2 が完了するまで実行を続行する必要がある場合があります。場合によっては、タスク 3 とタスク 4 を同時に実行できないため、マルチプロセスおよびマルチスレッド プログラムの複雑さは、前に作成した単一プロセスおよび単一スレッドのプログラムよりもはるかに高くなります。

非常に複雑でデバッグが難しいため、必要がない限りマルチタスクを書きたくありません。ただし、マルチタスクなしでは実行できない場合も多くあります。コンピューターで映画を視聴する場合を考えてみましょう。1 つのスレッドでビデオを再生し、別のスレッドでオーディオを再生する必要があります。それ以外の場合は、最初にビデオを再生してからオーディオを再生するか、またはオーディオを最初に再生する必要があります。そしてビデオは明らかに不可能です。

Pythonはマルチプロセッシングとマルチスレッドの両方をサポートしており、両方のマルチタスクプログラムを記述する方法について説明します。

Process

初対面:

Pythonプログラムでマルチプロセッシング(多重処理)を実装するには、まずオペレーティングシステムの関連知識を理解します。 Unix/Linux オペレーティング システムは、非常に特殊な fork()fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

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.

由于Windows没有fork调用,上面的代码在Windows上无法运行。由于Mac系统是基于BSD(Unix的一种)内核,所以,在Mac下运行是没有问题的,推荐大家用Mac学Python!有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。

 

multiprocessing模块:

  如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process システム コールを提供します。通常の関数呼び出しは 1 回呼び出して 1 回戻りますが、fork() は 1 回呼び出して

2 回返します
。これは、オペレーティング システムが自動的に (親プロセスと呼ばれます) 行うためです。 ) コピー (子プロセスと呼ばれます) を作成し、それぞれ親プロセスと子プロセスに戻ります。
子プロセスは常に 0
を返しますが、親プロセスは子プロセスの
IDを返します。その理由は、親プロセスが多くの子プロセスをフォークアウトできるため、親プロセスは各子プロセスの ID を記録する必要があり、子プロセスは
getppid()

親プロセスのIDを取得できます。

Python の osmodule

は、Python プログラムで子プロセスを簡単に作成できる

fork を含む一般的なシステム コールをカプセル化します。 Windows には fork 呼び出しがないため、上記のコードは Windows 上で実行できません。 Mac システムは BSD (Unix の一種) カーネルに基づいているため、Mac 上で問題なく実行できます。Python を学習するには Mac を使用することをお勧めします。 fork 呼び出しを使用すると、プロセスが新しいタスクを受け取ると、新しいタスクが発生するたびに、子プロセスをコピーしてそのポートを処理できます。 http リクエスト 新しい http リクエストを処理するために子プロセスがフォークされるとき。

🎜マルチプロセッシングモジュール:🎜🎜🎜🎜 マルチプロセスサービスプログラムを作成する予定がある場合、Unix/Linux が間違いなく正しい選択です。 Windows には 🎜fork🎜 呼び出しがないため、Windows 上で Python でマルチプロセス プログラムを作成することは不可能ですか? Python はクロスプラットフォームであるため、当然、クロスプラットフォームのマルチプロセス サポートを提供する必要があります。 🎜multiprocessing🎜 このモジュールは、マルチプロセス モジュールのクロスプラットフォーム バージョンです。 🎜multiprocessing🎜 モジュールは、プロセス オブジェクトを表す 🎜Process🎜 クラスを提供します。次の例は、子プロセスの開始と終了の待機を示しています。
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>
🎜🎜プロセス コード🎜🎜🎜🎜🎜🎜🎜プール プロセス プール: 🎜🎜🎜🎜🎜 多数の子プロセスを開始したい場合は、プロセス プールを使用して子プロセスをバッチで作成できます。 : 🎜🎜 🎜🎜 🎜りー 🎜

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

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
子子进程的心路历程

 

以上がPython 開発のプロセスとスレッドの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。