コンカレントプログラミング(スレッドおよびプロセス)

並行プロセスとスレッド

同時、シリアルは何ですか、平行:同時実行の話をする前に、我々はいくつかの概念を理解する必要がありますか?

  • シリアル:完全なプログラムを実行した後、その後、次へ
  • 同時:プログラム間で同時に実行されているように見えます
  • パラレル:本当に実行します

また、マルチチャネル技術は、二つのことは、我々は(カッコ拡張コンテンツに)を見る必要があります。

  • 空間多重化:共有メモリマルチプロセス(ブロック - >ページ - > +タブセグメント)が、それ自身の独立したメモリ空間(ベースレジスタ、動的アドレスリロケーション)、非干渉、物理的な分離を有します。
  • 時分割多重化:共通のCPU(タイムシェアリングシステム)

スタンバイ知識:

プロセスとスレッドは5つの状態があります:新(新)、レディ(準備)、実行中の(ランニング)、ブロックされた(ブロック)

そして死(死)


プロセス

私たちは、呼ばれるプログラム実行しているプロセスを、我々は正式にそれの並行処理を学ぶために、コードを使用することができます!

子プロセスをオープンするには、2つの方法:

  1. ターゲットとしての機能を指定することによって、新しいプロセスを生成するためにプロセスオブジェクトを作成します

    from multiprocessing import Process
    import time
    
    def task():
        print('进程 start')
        time.sleep(2)
        print('进程 end')
    
    
    if __name__ == '__main__':
        p = Process(target=task)
        p.start() # 向操作系统发送开子进程请求,具体什么时候开,开多长时间只和操作系统有关。
        time.sleep(5)
        print('主进程/父进程')
  2. 継承Processクラス、およびProcessクラスを作成するには、run()メソッドをオーバーライドします

    from multiprocessing import Process
    import time
    
    class Test(Process):
        def __init__(self,sex):
            super().__init__()
            self.sex = sex
    
        def run(self):
            '''run()方法为进程的执行体,执行start()后就会自动执行,不要直接调用!!!'''
    
            print(f'子进程的性别是{self.sex} start')
            time.sleep(2)
            print('子进程 end')
    if __name__ == '__main__':
        p = Test('女')
        p.start() # 向操作系统 发送开启子进程的请求
        print('主进程')

プロセス・クラス・メソッド(概要)

  • 実行可能ファイルの上書きこの方法は、プロセスを達成することができます。run()
  • 開始():プロセスを開始するために使用
  • 参加する([タイムアウト]):現在のプロセスがダウンして実行を完了するために実行されるのを待っているのプロセスに参加する必要があります
  • 設定やプロセスにアクセスするために使用されるプロパティの名前:名前
  • is_alive():プロセスがまだ生きているかどうかを判断します
  • デーモン:この属性は、決定するために設定されているかどうか、デーモン(バックグラウンド状態)
  • PID:プロセスIDを返します。
  • AuthKey:ライセンスキーの返却処理
  • )(終了:(要求)プロセスを中断し、割り込みは、オペレーティングシステムによって割り当てられた正確場合

Processクラスの方法(詳細)

  • 参加する([タイムアウト]):現在のプロセスがダウンして実行を完了するために実行されるのを待っているのプロセスに参加する必要があります

    あなたはタイムアウトを指定しない場合、タイムアウトが割り当てられている場合は、プロセスが終わっていない参加する場合、プロセスが無期限に待機プロセスの最後に参加します後、それは、もはや待って、最大秒数を待ちません。

    from multiprocessing import Process
    import time
    def foo(x):
        print('进程  start ')
        time.sleep(x)
        print('进程  end ')
    
    if __name__ == '__main__':
        p1 = Process(target=foo,args=(1,))
        p2 = Process(target=foo,args=(2,))
        p3 = Process(target=foo,args=(3,))
        start = time.time()
        p1.start() #
        p2.start() #
        p3.start() #
        # 核心需求就是
        p3.join() #1s
        p1.join() #1s
        p2.join() #1s
        # 总时长:按照最长的时间计算多一点。
        end = time.time()
        print(end-start) #3s多 or 6s多  ?  正解:3s多
        print('主')
  • デーモン:この属性は、決定するために設定されているかどうか、デーモン(バックグラウンド状態)

    特性化デーモンはこれです:フォアグラウンドプロセスが死んでいる場合は、バックグラウンドスレッドを自動的に死亡ます

    from multiprocessing import Process
    import time
    def foo():
        print('守护进程 start')
        time.sleep(3)
        print('守护进程 end')
    
    def task():
        print('子进程 start')
        time.sleep(5)
        print('子进程 end')
    
    if __name__ == '__main__':
        p = Process(target=foo)
        p2 = Process(target=task)
        p.daemon = True # 把这个子进程定义为了守护进程
        p.start()
        p2.start()
        time.sleep(1)
        print('主')
  • PID:プロセスIDを返します。

    • (各プロセスは、通常の状況下では、それに属するIDを持っていますし、すべての子プロセスが実行され、親プロセスは回復統一されます)待機を()すべての子プロセスをのpid、今回死んだ子プロセスが呼び出されますこれは、あるゾンビプロセス(ゾンビ)ゾンビプロセスは、システムリソースの多くを取る廃棄物が得られますが、最も簡単な方法は、ゾンビは、その後、ゾンビプロセスの全てとなり犯人を殺す生産することである孤立したことにより、initプロセス init()メソッドが待機する、引き継ぐことを孤児このプロセスは、彼らはすでに死んで孤児が残された和平プロセスで休むことができるようになり、システムのプロセステーブル占有リソースを解放し、そう。

    • いくつかの方法の表示PID:

      from multiprocessing import Process,current_process
      import time,os
      
      def task():
          print('子进程 start')
          print('在子进程中查看自己的pid',current_process().pid) # 在子进程中查看自己的pid
          print('在子进程中查看父进程的pid',os.getppid()) #
          time.sleep(2)
          print('子进程 end')
      
      if __name__ == '__main__':
          p = Process(target=task)
          p.start()
          print('在主进程查看子进程的pid',p.pid) # 一定要写在 start()之后
          print('主进程的pid',os.getpid())
          print('主进程的父进程pid',os.getppid())
          print('主')
      
      '''
      # 记住这些就ok了 这个是要掌握的
      # 角度 站在当前进程的角度
      os.getpid()#获取当前进程的pid
      os.getppid() #获取当前进程的父进程的pid
      子进程对象.pid #获取当前进程的子进程pid
      '''

プロセスの安全性と同期ロック(ロック)

同時に実行されている複数のプロセスが、そのようなグラブ票として、エラー状況を取得し、より多くの可能性が高い同じ検索をしてチケットをつかむために簡単である場合には、我々はそれが起こることを望んでいない、我々はロックする必要があります、multiprocessingモジュールを提供Lockし、RLockロック、ロックを解除するための次の二つの方法を提供する2つのクラス:

  • 取得(=ブロッキングトゥーレ、タイムアウト= -1): 前記ロックまたはRLOCK、のロック要求timeoutパラメータは秒数ロック(解決さデッドロック)を指定します。

  • リリース():ロックを解除

    だから、ロックとRLOCKの違いは次のとおりです。

  • ロック:ミューテックス(また、ミューテックスとして知られているミューテックスの基本的なロックオブジェクトである)、唯一のたびにロックすることができ、ロック要求の残りの部分を、あなたは解放されたロックを取得するまで待機する必要があります。

  • RLOCK:リエントラントロック(再入可能ロックも再帰的ロック(として知られている)、再帰ミューテックス)。リエントラントロックの場合、それはまた、いくつかの回を解放することができ、同じプロセスで複数回ロックすることができます。しかし、(取得)およびリリース()メソッドを組み合わせる必要があり、それが呼び出さn回取得()ロックされ、あなたがロックを解除するために)(n番目のリリースを呼び出す必要があります。RLOCKオブジェクトはを追跡するカウンタを維持します。

その後、我々は、コードについて書いて票をつかむしようとしました。

from  multiprocessing import Process,Lock
import json,time,os

def search():
    time.sleep(1) # 模拟网络io
    with open('db.txt',mode='rt',encoding='utf-8') as f:
        res = json.load(f)
        print(f'还剩{res["count"]}')

def get():
    with open('db.txt',mode='rt',encoding='utf-8') as f:
        res = json.load(f)
        # print(f'还剩{res["count"]}')
    time.sleep(1) # 模拟网络io
    if res['count'] > 0:
        res['count'] -= 1
        with open('db.txt',mode='wt',encoding='utf-8') as f:
            json.dump(res,f)
            print(f'进程{os.getpid()} 抢票成功')
        time.sleep(1.5) # 模拟网络io
    else:
        print('票已经售空啦!!!!!!!!!!!')

def task(lock):
    search()
   
    lock.acquire() # 锁住
    get()
    lock.release() # 释放锁头
    
if __name__ == '__main__':
    lock = Lock() # 写在主进程是为了让子进程拿到同一把锁.
    for i in range(15):
        p = Process(target=task,args=(lock,))
        p.start()
    #  进程锁 是把锁住的代码变成了串行

補足(理解)セマフォ(セマフォ):

from multiprocessing import Process, current_process, Semaphore
import time

def task(sm):
    sm.acquire()
    print(f'{current_process().name} 在执行')
    time.sleep(3)
    sm.release()

if __name__ == '__main__':
    sm = Semaphore(5)  # 指定同时最多有多少个进程在执行
    for i in range(15):
        t = Process(target=task,args=(sm,))
        t.start()

補足(理解)GILロック:

ロックGIL(グローバルインタプリタロック)CPythonのインタプリタであり、GILロックは、本質的にミューテックスです。

同じプロセスの下で導かれ、一つだけのスレッドを実行することができ、同時に、あなたはマルチコアの優位性を活用することはできません。

同じプロセスの下では、複数の並行スレッドは並行して達成することができない達成することができます。

理由: CPythonの自身のガベージコレクションがスレッドセーフではないので、そのGILロックすること。

デッドロック

我々は2つのロックが互いをロックしながら理解することは簡単で、古典的なデッドロックを言及しなければならないロックの話、リリースに他のを待っています。

ここで少し話を共有します:

面试官:你给我简单明了的说下死锁问题,我就把offer发给你。

应聘者:你把offer给我我就给你讲死锁问题

デッドロックは、デッドロックを回避しようとしてくださいプログラムの作成には、プログラムに表示されません。ここではいくつかの一般的な方法があります

デッドロックを解決するには:

  • 使用RLOCK:リエントラントロックを使用します
  • 複数のロックを避ける:同じプロセスをロックする複数のロックを避けるようにしてください。
  • 同じ順序でプロセス間ロックをしてみましょう:同じ順序をロックします。
  • タイミングを使用してロック:取得()メソッドを呼び出すときにtimeout引数を指定します

待ち行列(キュー)制御処理通信を使用して

キュー・ブロックの数に設けられたキューモジュールは、キューの三つの主要なクラスがあり、主な違いは、入口は、異なるキューにキューイングすることです。簡単に言うと次のように:

  • queue.Queue(MAXSIZE = 0):代表的なFIFO(ファーストインファーストアウト)キュールーチン、MAXSIZEは、キューのキューサイズが上限に達した場合、それは要素に再びロックされ、キューのサイズを制限することがブロックされ、キューの要素が消費されるまで。MAXSIZEが0または負に設定されている場合は、キューサイズは無制限です。
  • queue.LifoQueue(MAXSIZE = 0):代表LIFO(ラストには出進ん)キュー
  • queue.PriorityQueue(MAXSIZE = 0):代表的な優先度キュー、先入れ先出しキュー内の最小要素の優先、通常デジタル、ファーストアウトの数。

これらの3つのキューは、実質的に同一のプロパティとメソッドは、彼らは以下の属性とメソッドを提供します。

  • 置く(項目、ブロック=真、タイムアウト=なし):キューに入れ要素、キューがいっぱいになると、ブロックパラメータが(ブロッキング)が真である場合、タイムアウトがNoneに設定されている場合、現在のプロセスは、ブロッキング時間指定されたタイムアウトを阻止されますキューの要素が消費されるまで、それはブロックを表し、キューがいっぱいになると、ブロックパラメータは(ブロックされていない)が偽である場合、例外をスロー向けます。
  • 取得(アイテム、ブロック= Trueの場合、タイムアウト=なし):キューから要素(要素の消費量)を取り外します。そして、同様の議論を置きます。
  • put_nowait(アイテム):ブロックに対応するがFalseに設定されています。
  • get_nowait(アイテム):ブロックに対応するがFalseに設定されています。
  • 空の():キューが空であるかどうか。
  • フル():キューが満杯であるかどうか。
  • QSIZEは():(いくつかの要素を含む)キューの実際のサイズを返します。

例:

# 我们也可以导入multiprocessing的Queue
from multiprocessing import Queue
q = Queue(2)
q.put('1')
q.put('2')

# q.put('3', timeout=1)    # 等1秒,还放不进去就报错
q.put('4', block=False)  # 报错 queue.Full

プロセスプール

プロセスプールは、基本クラスであるconcurrent.futures内部のモジュールExecutorExecutor即ち、二つのサブカテゴリーを提供し、ProcessPoolExecutorそしてThreadPoolExecutorそのProcessPoolExecutorプロセス・プールを作成するために使用され、ThreadPoolExecutorスレッドプールを作成するために使用されます。

プロセスプール/スレッドプールの目的は:同時タスクの数は、コンピュータを買う余裕ができ、それが仕事のあまりに多くのワンタイム番号を開くことができないよりもはるかに大きい場合、我々は保証から、プロセスやスレッドの私の数を制限しようとして検討すべきですサーバーは崩壊しません。

我々は並行プログラミングを管理するためにプールを使用する場合は、限り、プール/スレッドプールを処理するために、対応するタスク機能として、物事の残りの部分は、プールサイドで解決されます。

エグゼキュータは、以下の一般的な方法を提供します。

  • (FN、* argsを、** kwargsから)を提出:FN機能は、プロセスプールに提出されます。後者は、FNに位置パラメータとキーワード引数です。

  • シャットダウン(待機= TRUE):クローズドプロセスプール

  • マップ(FUNC、イテラブル、タイムアウト=なし、チャンク= 1)関数は、グローバル関数マップ(FUNC、同様である)を理解:(イテラブル)が、関数は非同期に実行処理イテラブルをマッピングする複数のプロセスを開始します。

    プログラムは、プールタスク機能を処理する(提出)に提出された後、方法は、将来のオブジェクト、関数の戻り値を取得するために使用されるプロセスの未来の主要タスクを返し提出。非同期タスク実行プロセス。機能実行プロセスは、「将来の完璧な」仕事に相当しますので今後として表現します。

    今後の主な方法:

  • 結果(=なしタイムアウト):将来の結果を返すために、プロセスの最後のタスクを表し取得します。もし。タスクの代わりに今後のプロセスが完了していない場合は、ブロッキングタイムアウトパラメータが最大秒数を指定するには、現在のプロセスを、ブロックされます。

  • ADD_ done_コールバック(FN):タスクが正常に完了している未来の代理として、上の「コールバック」スレッドのタスクを登録し、プログラムが自動的にFN機能をトリガします。

    あなたは、プロセスのプールを使用したら、プールは、シーケンス処理プールをオフに開始します)方法、(シャットダウンのプロセスを呼び出す必要があります。シャットダウンを呼び出した後()メソッドは、プールは、もはや新しいタスクを受け取り処理していないが、すべては以前にタスクの実行を完了するために提出されているでしょう。すべてのタスクがプールを処理するために実行された場合、死のプロセスのすべてのプロセスをプール。

例:

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time


def task(i):
    # print(f'{currentThread().name} 在执行任务 {i}')
    print(f'进程 {current_process().name} 在执行任务 {i}')
    time.sleep(1)
    return i * 2


if __name__ == '__main__':
    # pool = ThreadPoolExecutor(4) # 池子里只有4个线程
    pool = ProcessPoolExecutor(4)  # 池子里只有4个线程
    fu_list = []
    for i in range(20):
        future = pool.submit(task, i)  # task任务要做20次,4个进程负责做这个事
        # print(future.result()) # 如果没有结果一直等待拿到结果,导致了所有的任务都在串行
        fu_list.append(future)
    pool.shutdown()  # 关闭了池的入口,会等待所有的任务执行完,结束阻塞.
    for fu in fu_list:
        print(fu.result())

前のプログラムでは、スレッドのタスクの戻り値を取得するには、結果フューチャー()メソッドを呼び出しますが、このメソッドは、メソッドが解放された()タスクが完了するまで、プロセスは唯一、ブロッキングが発生します現在のプロセスをブロックします。

我々は結果を直接呼び出す()メソッドは、プロセスをブロックしたくない場合は、ADD_ done_コールバック()メソッド、フォームは、fn(未来)のコールバック関数の将来のことでコールバック関数を追加することができます。タスクは、プロセスが完了すると、プログラムが自動的にコールバック関数とコールバック関数に渡されたパラメータとして対応する未来のオブジェクトをトリガします。

次のプログラムは、スレッドのタスクの戻り値を取得するためにADD_ done_コールバック()メソッドを使用しています。

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time


def task(i):
    # print(f'{currentThread().name} 在执行任务 {i}')
    print(f'进程 {current_process().name} 在执行任务 {i}')
    time.sleep(1)
    return i ** 2


def parse(future):
    # 处理拿到的结果
    print(future.result())


if __name__ == '__main__':
    # with ThreadPoolExecutor(4) as pool: # 池子里只有4个线程
    with ProcessPoolExecutor(4) as pool:  # 池子里只有4个线程
        fu_list = []
        for i in range(20):
            future = pool.submit(task, i)  # task任务要做20次,4个进程负责做这个事
            future.add_done_callback(parse)
            # 为当前任务绑定了一个函数,在当前任务执行结束的时候会触发这个函数,
            # 会把future对象作为参数传给函数
            # 这个称之为回调函数,处理完了回来就调用这个函数.

ヒント:コンテキスト管理プロトコルを達成するためのプロセスプール/スレッドプール、あなたがプロセスプールをシャットダウンし、手動で避けることができるので、したがって、プロセスのプールを管理するためのステートメントを使用することができます。

追加:我々はまたできるfrom multiprocessing.pool import Poolプロセスプールを表現すること。


スレッド

基本的に同様の方法スレッドおよびプロセスは、その後、スレッドとプロセスとの間の主な違いは次のとおりです。

 操作系统可以同时执行多个任务,每个任务就是一个进程;进程可以同时执行多个任务,每一个任务就是一个线程。

我々はまた、二つのことを知っている必要がありますスレッド:

  • タイマ(タイマ)
  • コルーチンは:シングルスレッドの同時実行を実現するために、コルーチンが実際にプログラマ抽象化して、コルーチンを知らないオペレーティングシステムが存在し、それはスレッドがこのオペレーティングシステムと他のタスクに直接カット雌ねじを、遭遇したioの言いましたシングルスレッドで最大効率を達成するために、見つけることができません。
    • 長所:高速化スイッチを切り替える制御よりも、独自のオペレーティングシステム。
    • 短所:私は閉塞全体があるたびに、すべてのIOを検出する必要があるが、障害物を踏襲している、そしてあなたは、マルチコア利点を使用することはできません。

おすすめ

転載: www.cnblogs.com/Du704/p/11569569.html