iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Software Development

selenium爬蟲應用至discord bot系列 第 22

[DAY22]補充: MultiThreading 多線程 & MultiProcessing 多進程

  • 分享至 

  • xImage
  •  

今天來簡單講講多線程(MultiThreading)和多進程(MultiProcessing)。

首先我們要先來理解一下兩者的差異。

線程(Threading)和進程(Processing)的區別:

  1. 定義區別:
  • 線程是第二小的執行單元,一個進程可以包含多個線程。線程屬於進程。而協程屬於線程。

  • 進程有自身的地址空間,每個進程都擁有獨立的代碼段、數據段等資源。線程不能脫離進程單獨存在。

  1. 調度區別:
  • 線程調度由程序自行控制,進程調度由操作系統Kernel控制。

  • 線程切換消耗資源較小,進程切換需要內核態和用戶態之間的切換,消耗更大。

  1. 通信區別:
  • 同一進程內的線程可以共享進程資源,使用簡單的方法互相通信。進程間通信需要IPC。

  • 線程更適合頻繁的通信交互,進程適合較為獨立的子任務。

  1. 多核利用:
  • 一個進程內的多個線程可以利用多核,但同一進程最多只能用佔用一個核。

  • 多個進程可以分配給多個內核,有利於提高多核CPU的利用率。

總體來說,線程與進程的選擇需要根據具體場景需求來確定。對於頻繁交互的任務,線程更有優勢。而對於獨立分散的任務,則應優先考慮進程。


接著來簡單說一下使用方法。

Python中的多線程是通過threading模塊實現的。要使用多線程,需要先導入該模塊:

import threading

然後可以通過以下兩種方式創建線程:

  1. 直接創建Thread對象,傳入目標函數:
t = threading.Thread(target=函數名)
  1. 從Thread類繼承,重寫run方法:
class MyThread(threading.Thread):
  def run(self):
    # 線程要執行的程式碼

創建線程後,調用start()方法啟動線程:

t = threading.Thread(target=函數名) 
t.start()

如果要等待線程結束後再繼續執行,可以調用join()方法。

t.join()

Python中的每啟動一個線程就是啟動一個獨立執行單元。多線程能提高程序運行效率,在一個線程等待時,可以讓其他線程繼續運行。

需要注意的是,Python中的多線程由於全局解釋鎖的存在,不能真正利用多核CPU。如果要發揮多核效能,需要使用多進程。


Python如果要發揮多核CPU的效能,可以通過multiprocessing模塊實現多進程。

使用multiprocessing模塊的主要步驟是:

  1. 導入multiprocessing模塊:
import multiprocessing 
  1. 定義目標函數:
def 函數名(args):
  // 要執行的程式碼
  1. 使用Process類創建進程:
p = multiprocessing.Process(target=函數名, args=(args,))
  1. 啟動進程:
p.start()
  1. 等待進程結束:
p.join()

與threading不同,multiprocessing會創建實際的子進程,可以真正利用多核。

multiprocessing提供了多種類型的進程,如Pool可以創建進程池並發送任務,Queue可以用於進程間通信。

通過合理配置多進程,指派給不同CPU核,可以大大提高Python程序的執行效率。


而使用線程與進程時也有需要注意的地方。

使用線程時,主要注意以下幾點:

  1. 線程安全

多個線程同時對一個對象進行操作時,需要確保代碼是線程安全的,通常可以使用互斥鎖來保證。

  1. 線程同步

需要線程之間協調運行順序,可以使用Event、Condition等方式實現線程同步。

  1. 死鎖

線程間互相持有資源而造成的死鎖問題。可以設計好資源訪問順序來避免。

  1. 共享資源

線程間共享資源的時候一定要確保對資源的訪問是互斥的。

  1. 線程間通信

線程間一般通過Queue、Pipe等方式通信傳遞數據。

  1. 線程停止

需要正確處理線程的退出,避免資源洩露等問題。

  1. 多線程編程 bugs

多線程程序極易出現Heisenbug,需要充分的測試。

  1. 資源限制

每個進程可創建的線程數是有限制的,需要合理規劃。

  1. 並發性能

避免不必要的線程切換影響性能。可以根據需求適當使用線程池。

總之,多線程編程一定要全面考慮各方面因素,謹慎設計和測試,以獲得一個健壯的多線程程序。


進程需要注意的地方也有些類似。

使用進程時,主要需要注意以下幾點:

  1. 進程間通信

進程間存在獨立的地址空間,需要使用IPC機制進行通信和數據共享,如管道、信號、Socket等。

  1. 數據共享

進程間數據共享可以使用共享內存,但需要處理好同步問題。也可以使用Manager。

  1. 進程同步

需要進程間協調運行順序,可以使用多種同步原語如互斥鎖、條件變量等。

  1. 死鎖

進程間也存在資源競爭導致死鎖的問題,需要小心避免。

  1. 進程調度

進程執行由操作系統調度,對進程優先級和時間片長度等要有充分考慮。

  1. 競爭條件

多個進程共享訪問資源時可能出現競爭條件,需要使用互斥、同步等機制。

  1. 進程安全

關鍵區域的代碼要考慮重入問題,保證進程安全。

  1. 資源限制

進程也有最大數量等資源限制,需要合理分配資源。

  1. 進程性能

追求最大性能時,需要考慮進程創建、上下文切換、IPC通信等開銷。

多進程編程也是非常複雜的,需要全面考慮各方面問題,以實現健壯的多進程協作。


以下是一些解決範例。

在Python的threading模塊中,Lock是一種同步鎖,可以實現對共享資源的互斥訪問,避免數據競爭。 threading.Lock的基本使用方法如下:

  1. 創建Lock對象:
lock = threading.Lock()
  1. 在需要鎖定的區域加鎖:
lock.acquire() # 上鎖
# 訪問共享資源
...
lock.release() # 釋放鎖
  1. 也可以使用with語句自動上鎖和釋放鎖:
with lock:
  # 訪問共享資源
  ...

下面是一個使用Lock的示例:

import threading
import time

lock = threading.Lock()
value = 0

def increment():
  global value
  with lock:
    temp = value + 1
    time.sleep(0.1) 
    value = temp

threads = []
for i in range(10):
  t = threading.Thread(target=increment)
  t.start()
  threads.append(t)

for t in threads:
  t.join()

print(value)

上面的代碼會正確打印出10。

需要注意的是,Lock不可以重入,同一線程不能多次獲取同一Lock。
如果需要可重入鎖,可以使用可重入鎖RLock(Reentrant Lock)。

在 Python 的 threading 模組中,lock = threading.Lock()會建立一個新的鎖物件。
lock.acquire() 會嘗試獲取鎖。
如果鎖已經被鎖定,則 acquire() 會阻塞,直到另一個線程調用 release() 將其解鎖。
然後 acquire() 會將其重置為鎖定並返回。
release() 方法應僅在鎖定狀態下調用,它將狀態更改為解鎖並立即返回。
這些方法用於確保多個線程之間的同步,並防止多個線程同時訪問共享資源。


上一篇
[DAY21]Discord bot非同步處理
下一篇
[DAY23]Discord bot斜槓指令(slash command)
系列文
selenium爬蟲應用至discord bot30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言