Python全局解释器锁GIL与多线程
Python中如果是 I/O密集型的操作,用多線程(協程Asyncio、線程Threading),如果I/O操作很慢,需要很多任務/線程協同操作,用Asyncio,如果需要有限數量的任務/線程,那么使用多線程。
如果是CPU密集型操作,用多進程(multeprocessing)。
一、GIL
GIL(Global Interpreter Lock,即全局解釋器鎖),Python實質上并不存在真正的多線程,只有一個主線程在調度,由于GIL采用輪流運行線程的機制,GIL需要在線程之間不斷輪流進行切換,線程如果較多或運行時間較長,切換帶來的性能損失可能會超過單線程。
二、CPython引進GIL的主要原因是:
- 設計者為了規避類似內存管理這樣的復雜競爭風險問題(race condition);
- CPython大量使用C語言庫,但大部分C語言庫都不是線程安全的(線程安全會降低性能和增加復雜度)。
三、繞過GIL的兩種思路:
- 繞過CPython,使用JPython等別的實現;
- 把關鍵性能代碼放到其他語言中實現,比如C++
四、線程、進程、協程
- 線程、進程——把內部資源、任務調度交給底層的CPU后者內核來處理。
- 協程 類似線程,資源、任務的調度更靈活,完全由人工處理,對開發人員及使用的人來說要求更高。其切換資源等要比線程更高效。
五、協程、線程、進程代碼示例
asyncio包含有協程需要的所有魔法
Asyncio 和其他 Python 程序一樣,是單線程的,它只有一個主線程event loop,但是可以進行多個不同的任務(task),這里的任務,就是特殊的 future 對象。這些不同的任務,被一個叫做 event loop 的對象所控制。
當在 jupyter 中運行: %time asyncio.run(main([‘url_1’, ‘url_2’, ‘url_3’,
‘url_4’]))
出現報錯: RuntimeError: asyncio.run() cannot be called from a running event loop 原因是: The problem in your case is that jupyter (IPython)
is already running an event loop (for IPython ≥ 7.0) 解決是: 將 %time
asyncio.run(main([‘url_1’, ‘url_2’, ‘url_3’, ‘url_4’])) 換成 await
main([‘url_1’, ‘url_2’, ‘url_3’, ‘url_4’])
1. 協程
# 協程 import asyncioasync def crawl_page(url):print('crawling {}'.format(url))sleep_time = int(url.split('_')[-1])await asyncio.sleep(sleep_time)print('OK {}'.format(url))async def main(urls):tasks = [asyncio.create_task(crawl_page(url)) for url in urls]for task in tasks:await task# %time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
await main(['url_1', 'url_2', 'url_3', 'url_4'])
結果如下圖:
2.線程
threading
threadingpool
# 異步編程from threading import Thread
import time
import functools# 裝飾器實現耗時記錄
def log_execution_time(func):@functools.wraps(func)def wrapper(*args, **kwargs):start = time.perf_counter()res = func(*args, **kwargs)end = time.perf_counter()print('{} took {} s'.format(func.__name__, (end - start)))return resreturn wrapper@log_execution_time
def CountDown(n):while n > 0:n -= 1n = 100000000t1 = Thread(target=CountDown, args=[n // 2])
t2 = Thread(target=CountDown, args=[n // 2])
t1.start()
t2.start()
t1.join()
t2.join()
等待運行結束并返回結果
import concurrent.futures
import requests
import timedef download_one(url):resp = requests.get(url)# print('Read {} from {}'.format(len(resp.content), url))return('Read {} from {}'.format(len(resp.content), url))def download_all(sites):with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:# executor.map(download_one,sites)to_do = []for site in sites:future = executor.submit(download_one, site)to_do.append(future)for future in to_do:future.add_done_callback(lambda future: print(future.result()))def main():sites = ['https://news.qq.com/','http://www.ifeng.com/','http://www.ce.cn/','https://news.baidu.com/','http://www.people.com.cn/','http://www.ce.cn/','https://news.163.com/','http://news.sohu.com/']start_time = time.perf_counter()download_all(sites)end_time = time.perf_counter()print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))if __name__ == '__main__':main()
3. 進程
import multiprocessing
import timedef cpu_bound(number):print(number)return sum(i * i for i in range(number))def find_sums(numbers):with multiprocessing.Pool() as pool:pool.map(cpu_bound, numbers)if __name__ == "__main__":numbers = [1000000 + x for x in range(20)]start_time = time.time()find_sums(numbers)duration = time.time() - start_timeprint(f"Duration {duration} seconds")
總結
以上是生活随笔為你收集整理的Python全局解释器锁GIL与多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python的神奇功能——函数装饰器Me
- 下一篇: 使用Python,OpenCV确定对象的