面试题答案
一键面试GIL对多线程网络编程性能产生影响的主要原因
- 全局解释器锁本质:GIL是Python解释器的一个机制,它确保在任何时刻只有一个线程能执行Python字节码。这意味着即使在多核CPU环境下,多线程也不能真正利用多核并行执行Python代码。
- 网络请求虽I/O密集但GIL限制:虽然网络请求通常是I/O密集型操作,在等待网络响应时线程会释放GIL。然而,在实际执行网络请求的代码部分(如建立连接、发送数据等),仍受GIL限制,无法实现真正的并行。例如,当多个线程依次发起网络请求时,由于GIL,它们需要逐个获取GIL才能执行相关网络操作代码,这导致整体效率提升受限。
代码层面减轻影响的示例
- 使用
concurrent.futures
模块中的ThreadPoolExecutor
:
import concurrent.futures
import requests
def fetch_url(url):
response = requests.get(url)
return response.status_code
urls = [
'http://example.com',
'http://example.org',
'http://example.net'
]
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print(f'{url} generated an exception: {exc}')
else:
print(f'{url} returned status code: {data}')
在此示例中,ThreadPoolExecutor
管理线程池,每个线程在执行fetch_url
函数中的网络请求时,在I/O等待阶段释放GIL,从而一定程度上提高了并发效率。
2. 使用asyncio
库进行异步编程:
import aiohttp
import asyncio
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.status
async def main():
urls = [
'http://example.com',
'http://example.org',
'http://example.net'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, status in zip(urls, results):
print(f'{url} returned status code: {status}')
if __name__ == '__main__':
asyncio.run(main())
asyncio
库基于事件循环实现异步I/O操作,它避免了线程切换的开销以及GIL的限制,在网络I/O密集型场景下能显著提升性能。