2021年1月19日 星期二

[python] 並行(Concurrency)-使用muti-threading

 

並行(Concurrency)-使用muti-threading
這3篇可以一起閱讀
非同步(async)-使用asyncio

  • Multi-processing (多處理程序/多進程):
    1. 資料在彼此間傳遞變得更加複雜及花時間,因為一個 process 在作業系統的管理下是無法去存取別的 process 的 memory
    2. 適合需要 CPU 密集,像是迴圈計算,即瓶頸在於計算等情況,且有多核可用時,就可以考慮用多進程提高效率
    3. 可多顆CPU運行
  • Multi-threading (多執行緒/多線程):
    1. 資料彼此傳遞簡單,因為多執行緒的 memory 之間是共用的,最直接的辦法就是設置一個全域變數,多個線程共享這個全域變數即可,但也因此要避免會有 Race Condition 問題。
    2. 適合需要 I/O 密集,像是爬蟲需要時間等待 request 回覆
    3. 如果你的程序有大量與數據交互/網絡交互,可以使用多線程,因為程序時間瓶頸不在於GIL而是在I/O,這時多線程的小開銷就比多進程更實用
    4. 如果你的程序有圖形界面GUI,使用多線程,GIL鎖會幫助你讓你的UI線程不會產生死鎖等問題
    5. 同一顆CPU運行, 但Python本身對於執行緒排程進行優化

並行

使用muti-threading

範例1-加速download
import threading
from urllib.request import urlopen

def download(url: str, file: str):
    with urlopen(url) as u, open(file, 'wb') as f:
        f.write(u.read())

urls = [
    'https://openhome.cc/Gossip/Encoding/',
    'https://openhome.cc/Gossip/Scala/',
    'https://openhome.cc/Gossip/JavaScript/',
    'https://openhome.cc/Gossip/Python/'
]
        
filenames = [
    'Encoding.html',
    'Scala.html',
    'JavaScript.html',
    'Python.html'
]

for url, filename in zip(urls, filenames):
    t = threading.Thread(target = download, args = (url, filename))
    t.start()

範例2-優先執行Thread B 做完才回還給main Thread

.join(n)表示最多直行n秒

import threading

def demo():
    print('Thread B 開始...')
    for i in range(5):
        print('Thread B 執行...')
    print('Thread B 將結束...')
 
print('Main thread 開始...')
tb = threading.Thread(target = demo)
tb.start()
tb.join(); # Thread B 加入 Main thread 流程

print('Main thread 將結束...')
h.start()

範例3-使用Lock, 避免race condition

thread使用同一資源或變更同一變數

from typing import Dict
from threading import Thread, Lock

def setTo1(data: Dict[str, int], lock: Lock):
    while True:
        with lock: # 取代lock.aquire() lock.release()
            data['Justin'] = 1
            if data['Justin'] != 1:
                raise ValueError(f'setTo1 資料不一致:{data}')

def setTo2(data: Dict[str, int], lock: Lock):
    while True:
        with lock:
            data['Justin'] = 2
            if data['Justin'] != 2:
                raise ValueError(f'setTo2 資料不一致:{data}')

lock = Lock()
data: Dict[str, int] = {}

t1 = Thread(target = setTo1, args = (data, lock))
t2 = Thread(target = setTo2, args = (data, lock))

t1.start()
t2.start()

範例4-使用Queue, 避免dead lock

不正確使用lock會導致dead lock

from queue import Queue
import threading

def producer(clerk: Queue):
    for product in range(10):
        clerk.put(product)
        print(f'店員進貨 ({product})')

def consumer(clerk: Queue):
    for product in range(10):
        print(f'店員賣出 ({clerk.get()})')

clerk: Queue = Queue(1);
threading.Thread(target = producer, args = (clerk, )).start()
threading.Thread(target = consumer, args = (clerk, )).start()


Ref:


 

沒有留言:

張貼留言