Python多程式之Process、Pool、Lock、Queue、Event、Semaphore、Pipe

ckxllf發表於2021-03-03

  1. Python建立程式類Process

  python的multiprocessing模組提供了一個建立程式的類Precess,其建立有以下兩種方法:

  建立Process類的例項,並指向目標函式和傳遞引數

  自定義一個類並繼承Process類,重寫__init__()和run()方法

  Process類的建構函式如下:

  class multiprocessing.Process(group, target, name, kwargs)

  def __init__(self,

  group: Any = ...,

  target: Optional[Callable] = ...,

  name: Optional[str] = ...,

  args: Iterable[Any] = ...,

  kwargs: Mapping[Any, Any] = ...,

  *,

  daemon: Optional[bool] = ...) -> None: ...

  引數說明:

  target 表示呼叫物件,一般為任務函式,也可以為類;

  args 給呼叫物件target傳遞的引數,為元組

  kwargs 表示呼叫物件的字典

  name 為程式的別名

  group 引數不使用,可以忽略

  Process類常用的方法和屬性如下

  方法

  is_alive(): 返回程式是否啟用

  join([timeout]): 阻塞程式,直到程式執行完成或超時或程式被終止

  run(): 代表程式執行任務的函式,可被重寫

  start(): 啟用啟動程式

  terminate(): 終止程式

  屬性

  authkey(): 位元組碼,程式的準秘鑰

  daemon(): 為True時,父程式終止後所有子程式自動終止,且不能產生新的程式,必須在start()方法前設定

  exitcode:退出碼,程式在執行時為None,如果為-N,就表示被訊號N結束

  name:獲取程式名稱

  pid:程式id

  1.1 我們首先使用第一種方法建立兩個程式,並與單程式執行的時間做比較

  from multiprocessing import Process

  import time

  def task_process(delay):

  num = 0

  for i in range(delay * 100000000): # 1億次資料累加計算

  num += i

  if __name__ == '__main__':

  t0 = time.time()

  task_process(3)

  task_process(3)

  t1 = time.time()

  print(f"單程式順序執行耗時 {t1 - t0} ")

  p0 = Process(target=task_process, args=(3,))

  p1 = Process(target=task_process, args=(3,))

  t2 = time.time()

  p0.start()

  p1.start()

  p0.join()

  p1.join()

  t3 = time.time()

  print(f"多程式併發執行耗時 {t3 - t2}")

  輸出結果:多程式執行相同的操作消耗的時間更少!

  單程式順序執行耗時 32.359516859054565

  多程式併發執行耗時 17.804959535598755

  1.2 使用第二種方法,自定義一個類並繼承Process類

  from multiprocessing import Process

  import time

  class MyProcess(Process):

  def __init__(self, delay):

  super().__init__()

  self.delay = delay

  # 子程式要執行的程式碼

  def run(self):

  num = 0

  for i in range(self.delay * 100000000):

  num += i

  if __name__ == "__main__":

  p0 = MyProcess(3)

  p1 = MyProcess(3)

  t0 = time.time()

  p0.start()

  p1.start()

  p0.join()

  p1.join()

  t1 = time.time()

  print(f"多程式併發執行耗時 {t1 - t0}")

  輸出結果

  多程式併發執行耗時 16.68904447555542

  2. 程式池Pool

  程式池Pool可以提供指定數量的程式給使用者使用,當有新的請求程式時,若Pool池沒有滿,就會建立一個新的程式用於執行該請求,如果Pool池中的程式數量已經達到最大值,則請求會等待,直到池中有程式結束才會建立新的程式。

  示例:

  # coding: utf-8

  import multiprocessing

  import time

  def task(name):

  print(f"{time.strftime('%H:%M:%S')}: {name} 開始執行")

  time.sleep(3)

  if __name__ == "__main__":

  pool = multiprocessing.Pool(processes=3)

  for i in range(10):

  # 維持執行的程式總數為processes,當一個程式執行完畢後會新增新的程式進去

  pool.apply_async(func=task, args=(i,))

  pool.close()

  pool.join()

  print("hello")

  輸出:

  19:43:22: 0 開始執行

  19:43:22: 1 開始執行

  19:43:22: 2 開始執行

  19:43:25: 3 開始執行

  19:43:25: 4 開始執行

  19:43:25: 5 開始執行

  19:43:28: 6 開始執行

  19:43:28: 7 開始執行

  19:43:28: 8 開始執行

  19:43:31: 9 開始執行

  hello

  3.同步機制之Lock鎖

  多程式的目的是併發執行程式,提高程式執行效率,但有時候我們想要在某一時間,或者滿足某一條件時,只有一個程式在執行,就需要使用Lock鎖機制。

  示例:

  import multiprocessing

  import time

  def task1(lock):

  with lock: # with上下文語句使用鎖,會自動釋放鎖

  n = 5

  while n > 1:

  print(f"{time.strftime('%H:%M:%S')} task1 輸出資訊")

  time.sleep(1)

  n -= 1

  def task2(lock):

  lock.acquire()

  n = 5

  while n > 1:

  print(f"{time.strftime('%H:%M:%S')} task2 輸出資訊")

  time.sleep(1)

  n -= 1

  lock.release()

  def task3(lock):

  lock.acquire()

  n = 5

  while n > 1:

  print(f"{time.strftime('%H:%M:%S')} task3 輸出資訊")

  time.sleep(1)

  n -= 1

  lock.release()

  if __name__ == "__main__":

  lock = multiprocessing.Lock()

  p1 = multiprocessing.Process(target=task1, args=(lock,))

  p2 = multiprocessing.Process(target=task2, args=(lock,))

  p3 = multiprocessing.Process(target=task3, args=(lock,))

  p1.start()

  p2.start()

  p3.start()

  輸出:

  20:13:22 task1 輸出資訊

  20:13:23 task1 輸出資訊

  20:13:24 task1 輸出資訊

  20:13:25 task1 輸出資訊

  20:13:26 task2 輸出資訊

  20:13:27 task2 輸出資訊

  20:13:28 task2 輸出資訊

  20:13:29 task2 輸出資訊

  20:13:30 task3 輸出資訊

  20:13:31 task3 輸出資訊

  20:13:32 task3 輸出資訊

  20:13:33 task3 輸出資訊

  說明:從結果上看,由於鎖的機制,同一時刻只有一個程式在執行。

  使用lock = multiprocess.Lock()可以得到一個鎖的例項,在上面的程式碼中,可以使用with語句來使用鎖,也可以在要執行的程式碼塊前使用lock.acquire()方法,在執行完後再釋放lock.release()鎖,需要注意的是lock.acquire()後面的語句不能阻塞,否則會發生死鎖的情況

  4.程式佇列Queue

  Queue是python多程式的安全佇列,可以使用Queue實現多程式之間的資料傳遞。

  Queue.put()方法說明:

  Queue.put(self, obj, block=True, timeout=None) # 用於插入資料到佇列中

  當block為True,timeout為正值時,該方法會阻塞timeout指定的時間,直到該佇列有剩餘的空閒時間。如果超時,會丟擲Queue.Full異常

  當block為False,但佇列已滿,會丟擲Queue.Full異常

  Queue.get()方法說明:

  Queue.get(self, block=True, timeout=None) # 從佇列中取出資料並刪除

  當block為True,timeout為正值時,在timeout時間內沒用取到資料就會丟擲Queue.Empty異常

  當block為False時,有兩種情況:若佇列中有資料則取出;若佇列為空則丟擲Queue.Empty異常。

  示例:

  from multiprocessing import Process, Queue

  import time

  def ProducerA(q):

  count = 1

  while True:

  q.put(f"冷飲 {count}")

  print(f"{time.strftime('%H:%M:%S')} A 放入:[冷飲 {count}]")

  count += 1

  time.sleep(1)

  def ConsumerB(q):

  while True:

  print(f"{time.strftime('%H:%M:%S')} B 取出 [{q.get()}]")

  time.sleep(5)

  if __name__ == '__main__':

  q = Queue(maxsize=5)

  p = Process(target=ProducerA, args=(q,))

  c = Process(target=ConsumerB, args=(q,))

  c.start()

  p.start()

  c.join()

  p.join()

  輸出:

  19:07:09 A 放入:[冷飲 1]

  19:07:09 B 取出 [冷飲 1]

  19:07:10 A 放入:[冷飲 2]

  19:07:11 A 放入:[冷飲 3]

  19:07:12 A 放入:[冷飲 4]

  19:07:13 A 放入:[冷飲 5]

  19:07:14 B 取出 [冷飲 2]

  19:07:14 A 放入:[冷飲 6]

  19:07:15 A 放入:[冷飲 7]

  19:07:19 B 取出 [冷飲 3]

  19:07:19 A 放入:[冷飲 8]

  19:07:24 B 取出 [冷飲 4]

  19:07:24 A 放入:[冷飲 9]

  19:07:29 B 取出 [冷飲 5]

  19:07:29 A 放入:[冷飲 10]

  說明:上述程式碼定義了生產者和消費者函式,設定佇列最大容量為5,生產者呼叫Queue.put()方法放入資料,消費呼叫Queue.get()方法取出資料,當佇列滿時,生產者等待,當佇列空時,消費者等待。生產的速度和消費的速度可能不一致,但是佇列能讓生產和消費有條不紊地進行。

  5.同步機制Event

  Event用來實現多程式之間的同步通訊

  示例:

  import multiprocessing

  import time

  def wait_for_event(e):

  e.wait()

  time.sleep(1)

  # 喚醒後清除Event狀態,為後續繼續等待

  e.clear()

  print(f"{time.strftime('%H:%M:%S')} 程式 A: 我們是兄弟,我等你...")

  e.wait()

  print(f"{time.strftime('%H:%M:%S')} 程式 A: 好的,是兄弟一起走")

  def wait_for_event_timeout(e, t):

  e.wait()

  time.sleep(1)

  # 喚醒後清除Event狀態,為後續繼續等待

  e.clear()

  print(f"{time.strftime('%H:%M:%S')} 程式 B: 好吧,最多等你 {t} 秒")

  e.wait(t)

  print(f"{time.strftime('%H:%M:%S')} 程式 B: 我繼續往前走了")

  if __name__ == "__main__":

  e = multiprocessing.Event()

  w1 = multiprocessing.Process(target=wait_for_event, args=(e,))

  w2 = multiprocessing.Process(target=wait_for_event_timeout, args=(e, 5))

  w1.start()

  w2.start()

  # 主程式發話

  print(f"{time.strftime('%H:%M:%S')} 主程式: 誰等我下,我需要 8 s 時間")

  # 喚醒等待的程式

  e.set()

  time.sleep(8)

  print(f"{time.strftime('%H:%M:%S')} 主程式: 好了,我趕上了")

  # 再次喚醒等待的程式

  e.set()

  w1.join()

  w2.join()

  print(f"{time.strftime('%H:%M:%S')} 主程式:退出")

  輸出:

  20:28:25 主程式: 誰等我下,我需要 8 s 時間

  20:28:26 程式 A: 我們是兄弟,我等你...

  20:28:26 程式 B: 好吧,最多等你 5 秒

  20:28:31 程式 B: 我繼續往前走了

  20:28:33 主程式: 好了,我趕上了

  20:28:33 程式 A: 好的,是兄弟一起走

  20:28:33 主程式:退出

  說明:上述程式碼定義了兩個程式函式,一個是等待事件發生,一個是設定超時時間後等待事件發生,主程式呼叫事件的set()方法喚醒等待事件的程式,事件被喚醒後呼叫clear()方法清除事件的狀態並呼叫wait()重新等待,以此達到程式同步的控制。

  6.併發控制之Semaphore

  Semaphore是用來控制對共享資源的訪問量,可以控制同一時刻程式的併發數量。

  示例:

  import multiprocessing

  import time

  def worker(s, i):

  s.acquire() # 獲得鎖

  print(time.strftime('%H:%M:%S'), multiprocessing.current_process().name + " 獲得鎖執行");

  time.sleep(i)

  print(time.strftime('%H:%M:%S'), multiprocessing.current_process().name + " 釋放鎖結束");

  s.release() # 釋放鎖

  if __name__ == "__main__":

  s = multiprocessing.Semaphore(2)

  for i in range(6):

  p = multiprocessing.Process(target=worker, args=(s, 2))

  p.start()

  輸出: 山東棗莊東方婦科醫院

  20:07:17 Process-1 獲得鎖執行

  20:07:17 Process-2 獲得鎖執行

  20:07:19 Process-1 釋放鎖結束

  20:07:19 Process-3 獲得鎖執行

  20:07:19 Process-2 釋放鎖結束

  20:07:19 Process-4 獲得鎖執行

  20:07:21 Process-3 釋放鎖結束

  20:07:21 Process-5 獲得鎖執行

  20:07:21 Process-4 釋放鎖結束

  20:07:21 Process-6 獲得鎖執行

  20:07:23 Process-5 釋放鎖結束

  20:07:23 Process-6 釋放鎖結束

  說明:multiprocessing.Semaphore(2)定義了同一時刻只能有2個程式在執行

  7.程式間資料傳遞Pipe

  multiprocessing.Pipe()方法會返回一個管道(列表的形式)的兩個埠,一個埠作為輸入端,一個埠作為輸出端,如程式A的輸出可以作為程式B的輸入,程式B的輸出可以作為程式A的輸入,預設是全雙工模式。

  Pipe()方法返回的物件具有傳送訊息send()方法和接收訊息recv()方法。呼叫接收recv()方法時,如果管道中沒用訊息會一直阻塞,如果管道關閉,則會丟擲EOFError異常。

  示例:

  import multiprocessing

  import time

  def task1(pipe):

  for i in range(5):

  str = f"task1-{i}"

  print(f"{time.strftime('%H:%M:%S')} task1 傳送:{str}")

  pipe.send(str)

  time.sleep(2)

  for i in range(5):

  print(f"{time.strftime('%H:%M:%S')} task1 接收: { pipe.recv() }")

  def task2(pipe):

  for i in range(5):

  print(f"{time.strftime('%H:%M:%S')} task2 接收: { pipe.recv() }")

  time.sleep(1)

  for i in range(5):

  str = f"task2-{i}"

  print(f"{time.strftime('%H:%M:%S')} task2 傳送:{str}")

  pipe.send(str)

  if __name__ == "__main__":

  pipe = multiprocessing.Pipe()

  p1 = multiprocessing.Process(target=task1, args=(pipe[0],)) # pipe[0]管道傳送訊息的埠

  p2 = multiprocessing.Process(target=task2, args=(pipe[1],)) # pipe[1]管道接收訊息的埠

  p1.start()

  p2.start()

  p1.join()

  p2.join()

  輸出:

  17:23:53 task1 傳送:task1-0

  17:23:53 task1 傳送:task1-1

  17:23:53 task1 傳送:task1-2

  17:23:53 task1 傳送:task1-3

  17:23:53 task1 傳送:task1-4

  17:23:53 task2 接收: task1-0

  17:23:53 task2 接收: task1-1

  17:23:53 task2 接收: task1-2

  17:23:53 task2 接收: task1-3

  17:23:53 task2 接收: task1-4

  17:23:54 task2 傳送:task2-0

  17:23:54 task2 傳送:task2-1

  17:23:54 task2 傳送:task2-2

  17:23:54 task2 傳送:task2-3

  17:23:54 task2 傳送:task2-4

  17:23:55 task1 接收: task2-0

  17:23:55 task1 接收: task2-1

  17:23:55 task1 接收: task2-2

  17:23:55 task1 接收: task2-3

  17:23:55 task1 接收: task2-4

  說明:定義了兩個任務函式,task1先發5條訊息,再接收訊息,task2先接收訊息,再傳送訊息。呼叫time.sleep()只是讓輸出更好看點,不會影響管道的接收和傳送。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2760926/,如需轉載,請註明出處,否則將追究法律責任。

相關文章