網(wǎng)站首頁 編程語言 正文
1. 并發(fā)與并行
- 所謂的并行(Parallelism),就是多個彼此獨(dú)立的任務(wù)可以同時一起執(zhí)行,彼此并不相互干擾,并行強(qiáng)調(diào)的是同時且獨(dú)立的運(yùn)行,彼此不需要協(xié)作。
- 而所謂并發(fā)(Concurrency),則是多個任務(wù)彼此交替執(zhí)行,但是同一時間只能有一個處于運(yùn)行狀態(tài),并發(fā)執(zhí)行強(qiáng)調(diào)任務(wù)之間的彼此協(xié)作。
并發(fā)通常被誤解為并行,并發(fā)實(shí)際是隱式的調(diào)度獨(dú)立的代碼,以協(xié)作的方式運(yùn)行。比如在等待IO線程完成IO操作之前,可以啟動IO線程之外的其他獨(dú)立代碼同步執(zhí)行其他操作。
關(guān)于并發(fā)、并行的圖示,如下:
由于CPython解釋器中的全局解釋器鎖(GIL,Global Interpreter Lock)的存在,所以Python中的并行實(shí)際上是一種假并行,并不是真正意義上的同時獨(dú)立運(yùn)行。
2. 線程與進(jìn)程的應(yīng)用場景
進(jìn)程(Process)是操作系統(tǒng)層面的一個抽象概念,它是運(yùn)行代碼創(chuàng)建出來的、加載到內(nèi)存中運(yùn)行的程序。電腦上通常運(yùn)行著多個進(jìn)程,這些進(jìn)程之間是彼此獨(dú)立運(yùn)行的。
線程(Thread)是操作系統(tǒng)可以調(diào)度的最小運(yùn)行程序單位,其包含在進(jìn)程中,一個進(jìn)程中通常至少包含1個線程,一些進(jìn)程中會包含多個線程。多個線程運(yùn)行的都是父進(jìn)程的相同代碼,理想情況下,這些線程是并行執(zhí)行的,但由于GIL的存在,所以它們實(shí)際上是交替執(zhí)行的,并不是真正意義上的獨(dú)立、并行的執(zhí)行的。
下表是進(jìn)程與線程的對比:
對于IO密集型的操作,更適合使用多線程編程的方式來解決問題;對于CPU密集型的操作,則更適合使用多進(jìn)程編程的方式來解決問題。
2.1. 并行/并發(fā)編程相關(guān)的技術(shù)棧
Python中提供了一些模塊用于實(shí)現(xiàn)并行/并發(fā)編程,具體如下所示:
threading
:Python中進(jìn)行多線程編程的標(biāo)準(zhǔn)庫,是一個對_thread
進(jìn)行再封裝的高級模塊。multiprocessing
:類似于threading
模塊,提供的API接口也與threading
模塊類似,不同的是它進(jìn)行多進(jìn)程編程。concurrent.futures
:標(biāo)準(zhǔn)庫中的一個模塊,在線程編程模塊的基礎(chǔ)上抽象出來的更高級實(shí)現(xiàn),且該模塊的編程為異步模式。queue
:任務(wù)隊(duì)列模塊,queue中提供的隊(duì)列是線程安全的,所以可以使用這個模塊進(jìn)行線程之間進(jìn)行安全的數(shù)據(jù)交換操作。不支持分布式。celery
:一個高級的分布式任務(wù)隊(duì)列,通過multiprocessing
模塊或者gevent
模塊可以實(shí)現(xiàn)隊(duì)列中人物的并發(fā)執(zhí)行。支持多節(jié)點(diǎn)之間的分布式計算。 2.2. 通過編碼比較多進(jìn)程與多線程的執(zhí)行效果
在下面的代碼中,定義了兩個函數(shù):only_sleep()
以及crunch_numbers()
,前者用于模擬IO密集型操作(需要頻繁中斷),后者用于模擬CPU密集型操作。
然后在串行調(diào)用、多線程的方式調(diào)用、多進(jìn)程的方式調(diào)用,三種不同的執(zhí)行環(huán)境中,比較各個函數(shù)的執(zhí)行效率情況。
具體代碼以及執(zhí)行結(jié)果如下所示:
import time import datetime import logging import threading import multiprocessing FORMAT = "%(asctime)s [%(processName)s %(process)d] %(threadName)s %(thread)d <=%(message)s=>" logging.basicConfig(format=FORMAT, level=logging.INFO, datefmt='%H:%M:%S') def only_sleep(): """ 模擬IO阻塞型操作,此時多線程優(yōu)勢明顯 :return: """ logging.info('in only_sleep function') time.sleep(1) def crunch_numbers(): """ 模擬CPU密集型操作,此時多進(jìn)程優(yōu)勢明顯 :return: """ logging.info('in crunch_numbers function') x = 0 while x < 1000000: x += 1 if __name__ == '__main__': work_repeat = 4 print('==>> only_sleep function test') count = 0 for func in (only_sleep, crunch_numbers): count += 1 # run tasks serially start1 = datetime.datetime.now() for i in range(work_repeat): func() stop1 = datetime.datetime.now() delta1 = (stop1 - start1).total_seconds() print('Serial Execution takes {} seconds~'.format(delta1)) # run tasks with multi-threads start2 = datetime.datetime.now() thread_lst = [threading.Thread(target=func, name='thread-worker' + str(i)) for i in range(work_repeat)] [thd.start() for thd in thread_lst] [thd.join() for thd in thread_lst] stop2 = datetime.datetime.now() delta2 = (stop2 - start2).total_seconds() print('Multi-Threads takes {} seconds~'.format(delta2)) # run tasks with multiprocessing start3 = datetime.datetime.now() proc_lst = [multiprocessing.Process(target=func, name='process-worker' + str(i)) for i in range(work_repeat)] [thd.start() for thd in proc_lst] [thd.join() for thd in proc_lst] stop3 = datetime.datetime.now() delta3 = (stop3 - start3).total_seconds() print('Multi-Processing takes {} seconds~'.format(delta3)) if count == 1: print('\n', '*.' * 30, end='\n\n') print('==>> crunch_numbers function test')
上述代碼的執(zhí)行結(jié)果如下:
23:55:51 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
==>> only_sleep function test
23:55:52 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
23:55:53 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
23:55:54 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
23:55:55 [MainProcess 568124] thread-worker0 553012 <=in only_sleep function=>
23:55:55 [MainProcess 568124] thread-worker1 567212 <=in only_sleep function=>
23:55:55 [MainProcess 568124] thread-worker2 547252 <=in only_sleep function=>
23:55:55 [MainProcess 568124] thread-worker3 561892 <=in only_sleep function=>
Serial Execution takes 4.022761 seconds~
Multi-Threads takes 1.01416 seconds~
23:55:56 [process-worker0 563068] MainThread 567480 <=in only_sleep function=>
23:55:56 [process-worker1 567080] MainThread 567628 <=in only_sleep function=>
23:55:56 [process-worker2 567868] MainThread 563656 <=in only_sleep function=>
23:55:56 [process-worker3 567444] MainThread 566436 <=in only_sleep function=>
23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
Multi-Processing takes 1.11466 seconds~*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.
==>> crunch_numbers function test
23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
Serial Execution takes 0.1786 seconds~
23:55:57 [MainProcess 568124] thread-worker0 567412 <=in crunch_numbers function=>
23:55:57 [MainProcess 568124] thread-worker1 566468 <=in crunch_numbers function=>
23:55:57 [MainProcess 568124] thread-worker2 565272 <=in crunch_numbers function=>
23:55:57 [MainProcess 568124] thread-worker3 568044 <=in crunch_numbers function=>
Multi-Threads takes 0.195057 seconds~
23:55:58 [process-worker0 567652] MainThread 561892 <=in crunch_numbers function=>
23:55:58 [process-worker1 553012] MainThread 547252 <=in crunch_numbers function=>
23:55:58 [process-worker2 554024] MainThread 556500 <=in crunch_numbers function=>
23:55:58 [process-worker3 565004] MainThread 566108 <=in crunch_numbers function=>
Multi-Processing takes 0.155246 seconds~Process finished with exit code 0
從上述執(zhí)行結(jié)果中可以看出:
上述代碼的執(zhí)行結(jié)果也驗(yàn)證了此前的結(jié)論:對于IO密集型操作,適合使用多線程編程的方式解決問題;而對于CPU密集型的操作,則適合使用多進(jìn)程編程的方式解決問題。
3. Python中的GIL是什么,它影響什么
GIL是CPython中實(shí)現(xiàn)的全局解釋器鎖 (Global Interpreter Lock),由于CPython是使用最廣泛的Python解釋器,所以GIL也是Python世界中最飽受爭議的一個主題。
GIL是互斥鎖,其目的是為了確保線程安全,正是因?yàn)橛辛薌IL,所以可以很方便的與外部非線程安全的模塊或者庫結(jié)合起來。但是這也是有代價的,由于有了GIL,所以導(dǎo)致Python中的并行并不是真正意義上的并行,所以也就無法同時創(chuàng)建兩個使用相同代碼段的線程,相同代碼的線程只能有一個處于執(zhí)行狀態(tài)。因?yàn)镚IL互斥鎖,相同代碼訪問的數(shù)據(jù)會被加鎖,只有當(dāng)一個線程釋放鎖之后,相同代碼的另一個線程才能訪問未被加鎖的數(shù)據(jù)。
所以Python中的多線程是相互交替執(zhí)行的,并不是真正的并行執(zhí)行的。但是在CPython之外的一些庫,是可以實(shí)現(xiàn)真正意義上的并行的,比如numpy
這個數(shù)據(jù)處理常用的庫。
原文鏈接:https://blog.csdn.net/ikkyphoenix/article/details/125852256
相關(guān)推薦
- 2022-10-31 解讀Python腳本的常見參數(shù)獲取和處理方式_python
- 2022-04-29 利用Python分析一下最近的股票市場_python
- 2022-05-26 Flutter?UI實(shí)現(xiàn)側(cè)拉抽屜菜單_Android
- 2021-12-31 使用go實(shí)現(xiàn)一個超級mini的消息隊(duì)列的示例代碼_Golang
- 2022-06-01 C語言?超詳細(xì)介紹與實(shí)現(xiàn)線性表中的無頭單向非循環(huán)鏈表_C 語言
- 2023-03-25 Rust你不認(rèn)識的所有權(quán)_Rust語言
- 2022-08-23 Python腳本提取fasta文件單序列信息實(shí)現(xiàn)_python
- 2022-11-05 Nginx配置文件nginx.conf的基本配置實(shí)例詳解_nginx
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支