Python GIL(全局解釋器鎖)的使用小結(jié)
我們通常所說的GIL,指的是全局解釋器鎖(Global Interpreter Lock),是計算機(jī)程序設(shè)計語言解釋器用于同步線程的一種機(jī)制,它使得任何時刻僅有一個線程在執(zhí)行。即使在多核處理器上,使用 GIL 的解釋器也只允許同一時間執(zhí)行一個線程。
在Python中,GIL的存在主要是為了簡化CPython解釋器的實現(xiàn),因為CPython的內(nèi)存管理不是線程安全的。GIL可以防止并發(fā)訪問Python對象,從而避免多個線程同時修改同一個對象導(dǎo)致的數(shù)據(jù)不一致問題。
但是,GIL也導(dǎo)致了一個問題:在多核CPU上,使用多線程的Python程序并不能真正地并行執(zhí)行,而是通過交替執(zhí)行來模擬并發(fā)。因此,對于CPU密集型的任務(wù),使用多線程并不能提高性能,甚至可能因為線程切換的開銷而降低性能。
然而,對于I/O密集型的任務(wù)(如網(wǎng)絡(luò)請求、文件讀寫等),由于線程在等待I/O時會被阻塞,此時GIL會被釋放,從而允許其他線程運行,因此多線程在I/O密集型任務(wù)中仍然可以提升性能。
為了克服GIL的限制,可以采用多進(jìn)程(使用multiprocessing模塊)來利用多核CPU,因為每個進(jìn)程有自己獨立的Python解釋器和內(nèi)存空間,因此每個進(jìn)程都有自己的GIL,從而可以實現(xiàn)真正的并行。
什么是 GIL?
GIL(Global Interpreter Lock) 是 CPython 解釋器中的一個互斥鎖,它確保在任何時刻只有一個線程在執(zhí)行 Python 字節(jié)碼。這意味著即使在多核 CPU 上,CPython 也無法實現(xiàn)真正的并行線程執(zhí)行。
GIL 的工作原理
+-----------------------------------------------+ | Python 進(jìn)程 (單個進(jìn)程) | | | | +-----------------------------------------+ | | | 全局解釋器鎖 (GIL) | | | | | | | | ?? 一把鎖,控制 Python 字節(jié)碼執(zhí)行 | | | +-----------------------------------------+ | | ↑ | | | (獲取/釋放) | | | | | +------------+ +------------+ +------------+| | | 線程 1 | | 線程 2 | | 線程 3 || | | | | | | || | | Python代碼 | | Python代碼 | | Python代碼 || | | 執(zhí)行中 | | 等待中 | | 等待中 || | +------------+ +------------+ +------------+| | | +-----------------------------------------------+
關(guān)鍵點:
- ?? GIL 是進(jìn)程級別的鎖,不是線程級別的
- ?? 同一時間只有一個線程能持有 GIL 并執(zhí)行 Python 字節(jié)碼
- ? 其他線程必須等待 GIL 被釋放
import threading
import time
def count_down(n):
while n > 0:
n -= 1
# 單線程執(zhí)行
start = time.time()
count_down(100000000)
single_time = time.time() - start
# 多線程執(zhí)行
start = time.time()
t1 = threading.Thread(target=count_down, args=(50000000,))
t2 = threading.Thread(target=count_down, args=(50000000,))
t1.start()
t2.start()
t1.join()
t2.join()
multi_time = time.time() - start
print(f"單線程執(zhí)行時間: {single_time:.2f}秒")
print(f"雙線程執(zhí)行時間: {multi_time:.2f}秒")
# 你會發(fā)現(xiàn)多線程可能比單線程更慢!
為什么需要 GIL?
1. 簡化內(nèi)存管理
Python 使用引用計數(shù)進(jìn)行內(nèi)存管理:
import sys a = [] print(sys.getrefcount(a)) # 查看對象的引用計數(shù) b = a print(sys.getrefcount(a)) # 引用計數(shù)增加
沒有 GIL 時,多個線程同時修改引用計數(shù)會導(dǎo)致競爭條件:
# 偽代碼演示競爭條件 # 線程1: obj.ref_count += 1 # 線程2: obj.ref_count -= 1 # 如果沒有同步機(jī)制,ref_count 可能出錯
2. 保護(hù)內(nèi)部數(shù)據(jù)結(jié)構(gòu)
Python 的很多內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如 list、dict)不是線程安全的。
GIL 的影響
CPU 密集型任務(wù)
import threading
import time
def cpu_intensive_task():
result = 0
for i in range(10**7):
result += i * i
return result
# 測試多線程性能
def test_multithreading():
threads = []
start_time = time.time()
for _ in range(4):
t = threading.Thread(target=cpu_intensive_task)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"多線程執(zhí)行時間: {time.time() - start_time:.2f}秒")
# 對比多進(jìn)程
import multiprocessing
def test_multiprocessing():
processes = []
start_time = time.time()
for _ in range(4):
p = multiprocessing.Process(target=cpu_intensive_task)
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"多進(jìn)程執(zhí)行時間: {time.time() - start_time:.2f}秒")
# 運行測試
if __name__ == "__main__":
test_multithreading() # 可能比單線程還慢
test_multiprocessing() # 真正的并行,速度更快
I/O 密集型任務(wù)
import threading
import time
import requests
def download_site(url, session):
with session.get(url) as response:
print(f"Read {len(response.content)} from {url}")
def download_all_sites(sites):
with requests.Session() as session:
# 單線程
start_time = time.time()
for url in sites:
download_site(url, session)
print(f"單線程下載時間: {time.time() - start_time:.2f}秒")
# 多線程
start_time = time.time()
threads = []
for url in sites:
thread = threading.Thread(target=download_site, args=(url, session))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print(f"多線程下載時間: {time.time() - start_time:.2f}秒")
# I/O 密集型任務(wù)中,多線程有明顯優(yōu)勢
如何繞過 GIL 的限制
1. 使用多進(jìn)程
from multiprocessing import Pool, cpu_count
import math
def is_prime(n):
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
def find_primes_parallel(numbers):
with Pool(processes=cpu_count()) as pool:
results = pool.map(is_prime, numbers)
return results
numbers = range(1000000, 1010000)
primes = find_primes_parallel(numbers)
+------------------------+
| 主進(jìn)程 (協(xié)調(diào)者) |
+------------------------+
|
+-----+-----+
| |
v v
+-------+ +-------+
| 進(jìn)程1 | | 進(jìn)程2 |
| | | |
| ??GIL | | ??GIL | ← 每個進(jìn)程有獨立的GIL
+-------+ +-------+
2. 使用 C 擴(kuò)展
// primes.c
#include <Python.h>
static PyObject* find_primes_c(PyObject* self, PyObject* args) {
Py_BEGIN_ALLOW_THREADS // 釋放 GIL
// 執(zhí)行計算密集型任務(wù)
Py_END_ALLOW_THREADS // 重新獲取 GIL
return Py_BuildValue("i", result);
}
+-----------------------+
| Python 線程 |
+-----------------------+
|
v
+-----------------------+
| 釋放 GIL 的 C 擴(kuò)展 | ← 在C代碼中手動釋放GIL
+-----------------------+
|
v
+-----------------------+
| 并行計算 (無GIL限制) |
+-----------------------+
3. 使用其他 Python 實現(xiàn)
- Jython: 基于 JVM,沒有 GIL
- IronPython: 基于 .NET,沒有 GIL
- PyPy: 有 GIL,但性能更好
+----------------+----------------+----------------+ | CPython | Jython | IronPython | | (有GIL) | (無GIL) | (無GIL) | +----------------+----------------+----------------+
4. 使用異步編程
import asyncio
import aiohttp
async def download_site_async(session, url):
async with session.get(url) as response:
content = await response.read()
print(f"Read {len(content)} from {url}")
async def download_all_sites_async(sites):
async with aiohttp.ClientSession() as session:
tasks = []
for url in sites:
task = asyncio.create_task(download_site_async(session, url))
tasks.append(task)
await asyncio.gather(*tasks)
# 運行異步任務(wù)
asyncio.run(download_all_sites_async(sites))
GIL 的優(yōu)缺點
優(yōu)點
- 簡化 CPython 實現(xiàn)
- 使單線程程序更快(無鎖開銷)
- 更容易集成非線程安全的 C 擴(kuò)展
缺點
- 限制多核 CPU 的利用率
- 對 CPU 密集型多線程程序不友好
- 可能造成性能誤解
實際開發(fā)建議
# 根據(jù)任務(wù)類型選擇方案:
def choose_concurrency_method(task_type, data):
if task_type == "cpu_intensive":
# 使用多進(jìn)程
with multiprocessing.Pool() as pool:
return pool.map(process_data, data)
elif task_type == "io_intensive":
# 使用多線程或異步
with ThreadPoolExecutor() as executor:
return list(executor.map(process_data, data))
elif task_type == "mixed":
# 混合方案:進(jìn)程池 + 線程池
pass
未來展望
Python 社區(qū)正在探索移除 GIL 的方案:
- nogil 分支:嘗試移除 GIL 的實驗性版本
- subinterpreters:通過多個解釋器實例實現(xiàn)真正的并行
總結(jié)
GIL 是 CPython 的歷史遺留問題,它:
- 主要影響 CPU 密集型多線程程序
- 對 I/O 密集型任務(wù)影響較小
- 可以通過多進(jìn)程、C 擴(kuò)展、異步編程等方式繞過
到此這篇關(guān)于Python GIL(全局解釋器鎖)的使用小結(jié)的文章就介紹到這了,更多相關(guān)Python GIL全局解釋器鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python突破多線程限制GIL問題的4種實戰(zhàn)解法
- Python多進(jìn)程開發(fā)之如何輕松突破GIL瓶頸
- python全局解釋器GIL鎖機(jī)制詳解
- Python學(xué)習(xí)之線程池與GIL全局鎖詳解
- Python中GIL的使用詳解
- 詳解Python中的GIL(全局解釋器鎖)詳解及解決GIL的幾種方案
- 淺談Python中的全局鎖(GIL)問題
- python 深入了解GIL鎖詳細(xì)
- 一篇文章快速了解Python的GIL
- 深入理解python虛擬機(jī)GIL詳解
- Python Threading 線程/互斥鎖/死鎖/GIL鎖
- Python全局解釋器鎖(GIL):提高多線程性能的最佳實踐
相關(guān)文章
pytorch 計算ConvTranspose1d輸出特征大小方式
這篇文章主要介紹了pytorch 計算ConvTranspose1d輸出特征大小方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
Python使用tarfile模塊實現(xiàn)免費壓縮解壓
Python自帶的tarfile模塊可以方便讀取tar歸檔文件,厲害的是可以處理使用gzip和bz2壓縮歸檔文件tar.gz和tar.bz2,這篇文章主要介紹了Python使用tarfile模塊實現(xiàn)免費壓縮解壓,需要的朋友可以參考下2024-03-03
Python 相對路徑報錯:"No such file or 
如果你取相對路徑不是在主文件里,可能就會有相對路徑問題:"No such file or directory",由于python 的相對路徑,相對的都是主文件所以會出現(xiàn)Python 相對路徑報錯,今天小編給大家?guī)砹送昝澜鉀Q方案,感興趣的朋友一起看看吧2023-02-02
Python 中使用 Selenium 單擊網(wǎng)頁按鈕功能
Selenium是一個用于測試網(wǎng)站的自動化測試工具,支持各種瀏覽器包括Chrome、Firefox、Safari等主流界面瀏覽器,同時也支持phantomJS無界面瀏覽器,本篇文章將介紹如何在 Python 中使用 selenium 單擊網(wǎng)頁上的按鈕,感興趣的朋友一起看看吧2023-11-11

