
併發與並行
併發和並行都用於與多執行緒程式相關聯,但它們之間的相似性和差異卻存在很多混淆。這方面的一個大問題是:併發是否就是並行?雖然這兩個術語看起來非常相似,但上述問題的答案是否定的,併發和並行不是一回事。現在,如果它們不是一回事,那麼它們之間最根本的區別是什麼呢?
簡單來說,併發處理的是從不同執行緒管理對共享狀態的訪問,而並行處理的是利用多個 CPU 或其核心來提高硬體效能。
併發詳解
併發是指兩個任務在執行上重疊。它可能是一種情況,應用程式在同一時間處理多個任務。我們可以透過圖表來理解它;多個任務同時取得進展,如下所示:

併發級別
在本節中,我們將討論程式設計方面三個重要的併發級別:
低階併發
在這個併發級別中,顯式地使用了原子操作。我們不能使用這種併發來構建應用程式,因為它很容易出錯並且難以除錯。即使 Python 也不支援這種併發。
中級併發
在這種併發中,沒有使用顯式的原子操作。它使用顯式鎖。Python 和其他程式語言支援這種併發。大多數應用程式程式設計師使用這種併發。
高階併發
在這種併發中,既不使用顯式的原子操作也不使用顯式的鎖。Python 擁有concurrent.futures模組來支援這種併發。
併發系統的特性
為了使程式或併發系統正確,它必須滿足某些屬性。與系統終止相關的屬性如下:
正確性屬性
正確性屬性意味著程式或系統必須提供所需的正確答案。為了簡單起見,我們可以說系統必須正確地將起始程式狀態對映到最終狀態。
安全性屬性
安全性屬性意味著程式或系統必須保持在“良好”或“安全”狀態,並且永遠不會做任何“壞事”。
活躍性屬性
此屬性意味著程式或系統必須“取得進展”,並且它將到達某個期望的狀態。
併發系統的參與者
這是併發系統的一個常見屬性,其中可以有多個程序和執行緒,它們同時執行以在其自己的任務上取得進展。這些程序和執行緒稱為併發系統的參與者。
併發系統的資源
參與者必須利用記憶體、磁碟、印表機等資源來執行其任務。
某些規則集
每個併發系統都必須具有一套規則來定義參與者要執行的任務型別和每個任務的時機。任務可以是獲取鎖、共享記憶體、修改狀態等。
併發系統的障礙
在實現併發系統時,程式設計師必須考慮以下兩個重要問題,它們可能是併發系統的障礙:資料共享
在實現併發系統時,一個重要問題是在多個執行緒或程序之間共享資料。實際上,程式設計師必須確保鎖保護共享資料,以便對其的所有訪問都進行序列化,並且一次只有一個執行緒或程序可以訪問共享資料。如果多個執行緒或程序都試圖訪問相同的共享資料,那麼並非所有執行緒或程序都能訪問,至少其中一個會被阻塞並保持空閒狀態。換句話說,當鎖生效時,我們一次只能使用一個程序或執行緒。有一些簡單的解決方案可以消除上述障礙:
資料共享限制
最簡單的解決方案是不共享任何可變資料。在這種情況下,我們不需要使用顯式鎖定,並且由於互斥資料而導致的併發障礙將得到解決。
資料結構輔助
很多時候,併發程序需要同時訪問相同的資料。除了使用顯式鎖之外,另一個解決方案是使用支援併發訪問的資料結構。例如,我們可以使用queue模組,它提供執行緒安全的佇列。我們還可以使用multiprocessing.JoinableQueue類進行基於多處理的併發。
不可變資料傳輸
有時,我們使用的資料結構(例如併發佇列)不合適,那麼我們可以傳遞不可變資料而無需鎖定它。
可變資料傳輸
作為上述解決方案的延續,假設如果需要傳遞僅可變資料而不是不可變資料,那麼我們可以傳遞只讀的可變資料。
I/O 資源共享
在實現併發系統中的另一個重要問題是執行緒或程序使用 I/O 資源。當一個執行緒或程序長時間使用 I/O 而其他執行緒或程序處於空閒狀態時,就會出現問題。在處理 I/O 密集型應用程式時,我們可以看到這種障礙。它可以透過一個例子來理解,例如從 Web 瀏覽器請求頁面。這是一個重量級的應用程式。在這裡,如果資料請求速率低於資料使用速率,那麼我們的併發系統中就會存在 I/O 障礙。
以下 Python 指令碼用於請求網頁並獲取網路獲取請求頁面的時間:
import urllib.request import time ts = time.time() req = urllib.request.urlopen('https://tutorialspoint.tw') pageHtml = req.read() te = time.time() print("Page Fetching Time : {} Seconds".format (te-ts))
執行上述指令碼後,我們可以獲得如下所示的頁面獲取時間。
輸出
Page Fetching Time: 1.0991398811340332 Seconds
我們可以看到,獲取頁面的時間超過一秒。現在如果我們要獲取數千個不同的網頁,您可以理解我們的網路將花費多長時間。
什麼是並行?
並行可以定義為將任務分解成可以同時處理的子任務的藝術。它與上面討論的併發相反,併發是指兩個或多個事件同時發生。我們可以透過圖表來理解它;一個任務被分解成許多可以並行處理的子任務,如下所示:

為了更好地理解併發和並行之間的區別,請考慮以下幾點:
併發但不併行
應用程式可以是併發但不是並行的,這意味著它同時處理多個任務,但這些任務沒有被分解成子任務。
並行但不併發
應用程式可以是並行但不是併發的,這意味著它一次只處理一個任務,並且可以並行處理將任務分解成的子任務。
既不併行也不併發
應用程式可以既不併行也不併發。這意味著它一次只處理一個任務,並且該任務從未被分解成子任務。
既並行又併發
應用程式可以既並行又併發,這意味著它既可以同時處理多個任務,又可以將任務分解成子任務以並行執行它們。
並行的必要性
我們可以透過將子任務分佈在單個 CPU 的不同核心之間或網路中連線的多臺計算機之間來實現並行。
請考慮以下要點以瞭解為什麼需要實現並行:
高效的程式碼執行
藉助並行,我們可以高效地執行程式碼。它將節省我們的時間,因為程式碼的各個部分將並行執行。
比順序計算快
順序計算受物理和實際因素的限制,因此無法獲得更快的計算結果。另一方面,平行計算解決了這個問題,並且與順序計算相比,它可以提供更快的計算結果。
執行時間更短
並行處理減少了程式程式碼的執行時間。
如果我們談論並行的現實生活例子,我們計算機的顯示卡就是一個突顯並行處理真正能力的例子,因為它擁有數百個獨立工作的獨立處理核心,並且可以同時執行。由於這個原因,我們能夠執行高階應用程式和遊戲。
瞭解處理器以進行實施
我們瞭解了併發、並行以及它們之間的區別,但對於要實現的系統呢?瞭解我們將要實現的系統非常必要,因為它使我們能夠在設計軟體時做出明智的決策。我們有以下兩種型別的處理器:
單核處理器
單核處理器能夠在任何給定時間執行一個執行緒。這些處理器使用上下文切換在特定時間儲存執行緒的所有必要資訊,然後稍後恢復這些資訊。上下文切換機制幫助我們在給定的秒內在一系列執行緒上取得進展,並且看起來系統正在處理多件事。
單核處理器有很多優點。這些處理器需要更少的功耗,並且多個核心之間沒有複雜的通訊協議。另一方面,單核處理器的速度有限,不適合大型應用程式。
多核處理器
多核處理器具有多個獨立的處理單元,也稱為核心。
此類處理器不需要上下文切換機制,因為每個核心都包含執行儲存指令序列所需的一切。
取指令-譯碼-執行週期
多核處理器的核心遵循一個執行週期。此週期稱為取指令-譯碼-執行週期。它包含以下步驟:
取指令
這是週期的第一步,它涉及從程式記憶體中獲取指令。
譯碼
最近獲取的指令將被轉換為一系列訊號,這些訊號將觸發 CPU 的其他部分。
執行
這是最後一步,其中獲取和解碼的指令將被執行。執行的結果將儲存在 CPU 暫存器中。
這裡的一個優勢是,多核處理器中的執行速度比單核處理器快。它適用於較大的應用程式。另一方面,多個核心之間複雜的通訊協議是一個問題。多個核心比單核處理器需要更多的功率。