Python全局解释器锁GIL与多线程
Python中如果是 I/O密集型的操作,用多線程(協(xié)程Asyncio、線程Threading),如果I/O操作很慢,需要很多任務(wù)/線程協(xié)同操作,用Asyncio,如果需要有限數(shù)量的任務(wù)/線程,那么使用多線程。
如果是CPU密集型操作,用多進(jìn)程(multeprocessing)。
一、GIL
GIL(Global Interpreter Lock,即全局解釋器鎖),Python實(shí)質(zhì)上并不存在真正的多線程,只有一個(gè)主線程在調(diào)度,由于GIL采用輪流運(yùn)行線程的機(jī)制,GIL需要在線程之間不斷輪流進(jìn)行切換,線程如果較多或運(yùn)行時(shí)間較長(zhǎng),切換帶來的性能損失可能會(huì)超過單線程。
二、CPython引進(jìn)GIL的主要原因是:
- 設(shè)計(jì)者為了規(guī)避類似內(nèi)存管理這樣的復(fù)雜競(jìng)爭(zhēng)風(fēng)險(xiǎn)問題(race condition);
- CPython大量使用C語言庫,但大部分C語言庫都不是線程安全的(線程安全會(huì)降低性能和增加復(fù)雜度)。
三、繞過GIL的兩種思路:
- 繞過CPython,使用JPython等別的實(shí)現(xiàn);
- 把關(guān)鍵性能代碼放到其他語言中實(shí)現(xiàn),比如C++
四、線程、進(jìn)程、協(xié)程
- 線程、進(jìn)程——把內(nèi)部資源、任務(wù)調(diào)度交給底層的CPU后者內(nèi)核來處理。
- 協(xié)程 類似線程,資源、任務(wù)的調(diào)度更靈活,完全由人工處理,對(duì)開發(fā)人員及使用的人來說要求更高。其切換資源等要比線程更高效。
五、協(xié)程、線程、進(jìn)程代碼示例
asyncio包含有協(xié)程需要的所有魔法
Asyncio 和其他 Python 程序一樣,是單線程的,它只有一個(gè)主線程event loop,但是可以進(jìn)行多個(gè)不同的任務(wù)(task),這里的任務(wù),就是特殊的 future 對(duì)象。這些不同的任務(wù),被一個(gè)叫做 event loop 的對(duì)象所控制。
當(dāng)在 jupyter 中運(yùn)行: %time asyncio.run(main([‘url_1’, ‘url_2’, ‘url_3’,
‘url_4’]))
出現(xiàn)報(bào)錯(cuò): 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. 協(xié)程
# 協(xié)程 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'])
結(jié)果如下圖:
2.線程
threading
threadingpool
# 異步編程from threading import Thread
import time
import functools# 裝飾器實(shí)現(xiàn)耗時(shí)記錄
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()
等待運(yùn)行結(jié)束并返回結(jié)果
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. 進(jìn)程
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")
總結(jié)
以上是生活随笔為你收集整理的Python全局解释器锁GIL与多线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python的神奇功能——函数装饰器Me
- 下一篇: 使用Python,OpenCV确定对象的