網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
什么是進(jìn)程
進(jìn)程就是操作系統(tǒng)中執(zhí)行的一個(gè)程序,操作系統(tǒng)以進(jìn)程為單位分配存儲(chǔ)空間,每個(gè)進(jìn)程都有自己的地址空間、數(shù)據(jù)棧以及其他用于跟蹤進(jìn)程執(zhí)行的輔助數(shù)據(jù),操作系統(tǒng)管理所有進(jìn)程的執(zhí)行,為它們合理的分配資源。
每個(gè)進(jìn)程都有自己的獨(dú)立內(nèi)存空間,不同進(jìn)程通過進(jìn)程間通信來通信。由于進(jìn)程比較重量,占據(jù)獨(dú)立的內(nèi)存,所以上下文進(jìn)程間的切換開銷(棧、寄存器、虛擬內(nèi)存、文件句柄等)比較大,但相對(duì)比較穩(wěn)定安全。
什么是線程
一個(gè)進(jìn)程還可以擁有多個(gè)并發(fā)的執(zhí)行線索,簡(jiǎn)單的說就是擁有多個(gè)可以獲得CPU調(diào)度的執(zhí)行單元,這就是所謂的線程。
CPU調(diào)度和分派的基本單位線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧)。
由于線程在同一個(gè)進(jìn)程下,它們可以共享相同的上下文,因此相對(duì)于進(jìn)程而言,線程間的信息共享和通信更加容易,上下文切換很快,資源開銷較少,但相比進(jìn)程不夠穩(wěn)定容易丟失數(shù)據(jù)。
注:當(dāng)然在單核CPU系統(tǒng)中,真正的并發(fā)是不可能的,因?yàn)樵谀硞€(gè)時(shí)刻能夠獲得CPU的只有唯一的一個(gè)線程,多個(gè)線程共享了CPU的執(zhí)行時(shí)間。
線程缺點(diǎn):
多線程也并不是沒有壞處,站在其他進(jìn)程的角度,多線程的程序?qū)ζ渌绦虿⒉挥押?,因?yàn)樗加昧烁嗟腃PU執(zhí)行時(shí)間,導(dǎo)致其他程序無法獲得足夠的CPU執(zhí)行時(shí)間;另一方面,站在開發(fā)者的角度,編寫和調(diào)試多線程的程序都對(duì)開發(fā)者有較高的要求,對(duì)于初學(xué)者來說更加困難。
線程與進(jìn)程的區(qū)別
- 地址空間和其他資源:進(jìn)程間相互獨(dú)立,同一進(jìn)程的各線程間共享。某線程內(nèi)的想愛你城咋其他進(jìn)程不可見。
- 通信:進(jìn)程間通信IPC,線程間可以直接讀寫進(jìn)程數(shù)據(jù)段來進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
- 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
- 在多線程操作系統(tǒng)中,進(jìn)程不是一個(gè)可執(zhí)行的實(shí)體
并行與并發(fā)
并行(Parallelism)
并行:指兩個(gè)或兩個(gè)以上事件(或線程)在同一時(shí)刻發(fā)生,是真正意義上的不同事件或線程在同一時(shí)刻,在不同CPU資源呢上(多核),同時(shí)執(zhí)行。
特點(diǎn)
- 同一時(shí)刻發(fā)生,同時(shí)執(zhí)行。
- 不存在像并發(fā)那樣競(jìng)爭(zhēng),等待的概念。
并發(fā)(Concurrency)
指一個(gè)物理CPU(也可以多個(gè)物理CPU) 在若干道程序(或線程)之間多路復(fù)用,并發(fā)性是對(duì)有限物理資源強(qiáng)制行使多用戶共享以提高效率。
特點(diǎn)
- 微觀角度:所有的并發(fā)處理都有排隊(duì)等候,喚醒,執(zhí)行等這樣的步驟,在微觀上他們都是序列被處理的,如果是同一時(shí)刻到達(dá)的請(qǐng)求(或線程)也會(huì)根據(jù)優(yōu)先級(jí)的不同,而先后進(jìn)入隊(duì)列排隊(duì)等候執(zhí)行。
- 宏觀角度:多個(gè)幾乎同時(shí)到達(dá)的請(qǐng)求(或線程)在宏觀上看就像是同時(shí)在被處理。
Python中的多進(jìn)程
Python中進(jìn)程操作
process模塊是一個(gè)創(chuàng)建進(jìn)程的模塊,借助這個(gè)模塊,就可以完成進(jìn)程的創(chuàng)建。
法:Process([group [, target [, name [, args [, kwargs]]]]])
由該類實(shí)例化得到的對(duì)象,表示一個(gè)子進(jìn)程中的任務(wù)(尚未啟動(dòng))。
注意:
- 必須使用關(guān)鍵字方式來指定參數(shù);
- args指定的為傳給target函數(shù)的位置參數(shù),是一個(gè)元祖形式,必須有逗號(hào)。
參數(shù)介紹:
- group:參數(shù)未使用,默認(rèn)值為None。
- target:表示調(diào)用對(duì)象,即子進(jìn)程要執(zhí)行的任務(wù)。
- args:表示調(diào)用的位置參數(shù)元祖。
- kwargs:表示調(diào)用對(duì)象的字典。如kwargs = {'name':Jack, 'age':18}。
- name:子進(jìn)程名稱。
代碼展示:
import os from multiprocessing import Process def func_one(): print("第一個(gè)子進(jìn)程") print("子進(jìn)程(一)大兒子:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid())) def func_two(): print("第二個(gè)子進(jìn)程") print("子進(jìn)程(二)二兒子:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid())) if __name__ == '__main__': p_one = Process(target=func_one) P_two = Process(target=func_two) p_one.start() P_two.start() print("子進(jìn)程:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid()))
繼承Process的方式開啟進(jìn)程的方式:
import os from multiprocessing import Process def func_one(): print("第一個(gè)子進(jìn)程") print("子進(jìn)程(一)大兒子:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid())) def func_two(): print("第二個(gè)子進(jìn)程") print("子進(jìn)程(二)二兒子:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid())) if __name__ == '__main__': p_one = Process(target=func_one) P_two = Process(target=func_two) p_one.start() P_two.start() print("子進(jìn)程:%s 父進(jìn)程:%s" % (os.getpid(), os.getppid()))
線程
Python的threading模塊
在Python早期的版本中就引入了thread模塊(現(xiàn)在名為_thread)來實(shí)現(xiàn)多線程編程,然而該模塊過于底層,而且很多功能都沒有提供,因此目前的多線程開發(fā)我們推薦使用threading模塊,該模塊對(duì)多線程編程提供了更好的面向?qū)ο蟮姆庋b。
from random import randint from threading import Thread from time import time, sleep def download(filename): print('開始下載%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費(fèi)了%d秒' % (filename, time_to_download)) def main(): start = time() t1 = Thread(target=download, args=('Python從入門到住院.pdf',)) t1.start() t2 = Thread(target=download, args=('Peking Hot.avi',)) t2.start() #join阻塞完成任務(wù) t1.join() t2.join() end = time() print('總共耗費(fèi)了%.3f秒' % (end - start)) if __name__ == '__main__': main()
我們可以直接使用threading模塊的Thread
類來創(chuàng)建線程,但是我們之前講過一個(gè)非常重要的概念叫“繼承”,我們可以從已有的類創(chuàng)建新類,因此也可以通過繼承Thread
類的方式來創(chuàng)建自定義的線程類,然后再創(chuàng)建線程對(duì)象并啟動(dòng)線程。
代碼如下所示:
from random import randint from threading import Thread from time import time, sleep class DownloadTask(Thread): def __init__(self, filename): super().__init__() self._filename = filename def run(self): print('開始下載%s...' % self._filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費(fèi)了%d秒' % (self._filename, time_to_download)) def main(): start = time() t1 = DownloadTask('Python從入門到住院.pdf') t1.start() t2 = DownloadTask('Peking Hot.avi') t2.start() t1.join() t2.join() end = time() print('總共耗費(fèi)了%.2f秒.' % (end - start)) if __name__ == '__main__': main()
鎖Lock:
模擬場(chǎng)景:
演示了100個(gè)線程向同一個(gè)銀行賬戶轉(zhuǎn)賬(轉(zhuǎn)入1元錢)的場(chǎng)景,在這個(gè)例子中,銀行賬戶就是一個(gè)臨界資源,在沒有保護(hù)的情況下我們很有可能會(huì)得到錯(cuò)誤的結(jié)果。
from time import sleep from threading import Thread class Account(object): #初始化賬戶余額為0元 def __init__(self): self._balance = 0 # 存款函數(shù) def deposit(self, money): # 計(jì)算存款后的余額 new_balance = self._balance + money # 模擬受理存款業(yè)務(wù)需要0.01秒的時(shí)間 sleep(0.01) # 修改賬戶余額 self._balance = new_balance # set和get @property def balance(self): return self._balance class AddMoneyThread(Thread): def __init__(self, account, money): super().__init__() self._account = account self._money = money def run(self): self._account.deposit(self._money) def main(): # 創(chuàng)建對(duì)象 account = Account() threads = [] # 創(chuàng)建100個(gè)存款的線程向同一個(gè)賬戶中存錢 for _ in range(100): t = AddMoneyThread(account, 1) threads.append(t) t.start() # 等所有存款的線程都執(zhí)行完畢 for t in threads: t.join() print('賬戶余額為: ¥%d元' % account.balance) if __name__ == '__main__': main()
運(yùn)行結(jié)果:
100個(gè)線程分別向賬戶中轉(zhuǎn)入1元錢,結(jié)果居然遠(yuǎn)遠(yuǎn)小于100元。
之所以出現(xiàn)這種情況是因?yàn)槲覀儧]有對(duì)銀行賬戶這個(gè)“臨界資源”加以保護(hù),多個(gè)線程同時(shí)向賬戶中存錢時(shí),會(huì)一起執(zhí)行到new_balance = self._balance + money
這行代碼,多個(gè)線程得到的賬戶余額都是初始狀態(tài)下的0
,所以都是0
上面做了+1的操作,因此得到了錯(cuò)誤的結(jié)果。
在這種情況下,“鎖”就可以派上用場(chǎng)了。我們可以通過“鎖”來保護(hù)“臨界資源”,只有獲得“鎖”的線程才能訪問“臨界資源”,而其他沒有得到“鎖”的線程只能被阻塞起來,直到獲得“鎖”的線程釋放了“鎖”,其他線程才有機(jī)會(huì)獲得“鎖”,進(jìn)而訪問被保護(hù)的“臨界資源”。
加鎖:
from time import sleep from threading import Thread, Lock class Account(object): def __init__(self): self._balance = 0 self._lock = Lock() def deposit(self, money): # 先獲取鎖才能執(zhí)行后續(xù)的代碼 self._lock.acquire() try: new_balance = self._balance + money sleep(0.01) self._balance = new_balance finally: # 在finally中執(zhí)行釋放鎖的操作保證正常異常鎖都能釋放 self._lock.release() @property def balance(self): return self._balance class AddMoneyThread(Thread): def __init__(self, account, money): super().__init__() self._account = account self._money = money def run(self): # 運(yùn)行存錢業(yè)務(wù),只有獲取鎖的才能執(zhí)行 self._account.deposit(self._money) def main(): account = Account() threads = [] #創(chuàng)建100個(gè)線程 for _ in range(100): # 線程加錢 t = AddMoneyThread(account, 1) threads.append(t) t.start() for t in threads: t.join() print('賬戶余額為: ¥%d元' % account.balance) if __name__ == '__main__': main()
結(jié)果:賬戶余額為: ¥100元
比較遺憾的一件事情是Python的多線程并不能發(fā)揮CPU的多核特性,因?yàn)镻ython的解釋器有一個(gè)“全局解釋器鎖”(GIL)的東西,任何線程執(zhí)行前必須先獲得GIL鎖,然后每執(zhí)行100條字節(jié)碼,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行。
全局解釋器鎖(GIL)
GIL是一個(gè)互斥鎖,它防止多個(gè)線程同時(shí)執(zhí)行Python字節(jié)碼。這個(gè)鎖是必要的,主要是因?yàn)镃Python的內(nèi)存管理不是線程安全的 盡管Python完全支持多線程編程, 但是解釋器的C語(yǔ)言實(shí)現(xiàn)部分在完全并行執(zhí)行時(shí)并不是線程安全的。
因此,解釋器實(shí)際上被一個(gè)全局解釋器鎖保護(hù)著,它確保任何時(shí)候都只有一個(gè)Python線程執(zhí)行。在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:
- 設(shè)置GIL
- 切換到一個(gè)線程去執(zhí)行
- 運(yùn)行
由于GIL的存在,Python的多線程不能稱之為嚴(yán)格的多線程。因?yàn)槎嗑€程下每個(gè)線程在執(zhí)行的過程中都需要先獲取GIL,保證同一時(shí)刻只有一個(gè)線程在運(yùn)行。
由于GIL的存在,即使是多線程,事實(shí)上同一時(shí)刻只能保證一個(gè)線程在運(yùn)行,既然這樣多線程的運(yùn)行效率不就和單線程一樣了嗎,那為什么還要使用多線程呢?
由于以前的電腦基本都是單核CPU,多線程和單線程幾乎看不出差別,可是由于計(jì)算機(jī)的迅速發(fā)展,現(xiàn)在的電腦幾乎都是多核CPU了,最少也是兩個(gè)核心數(shù)的,這時(shí)差別就出來了:通過之前的案例我們已經(jīng)知道,即使在多核CPU中,多線程同一時(shí)刻也只有一個(gè)線程在運(yùn)行,這樣不僅不能利用多核CPU的優(yōu)勢(shì),反而由于每個(gè)線程在多個(gè)CPU上是交替執(zhí)行的,導(dǎo)致在不同CPU上切換時(shí)造成資源的浪費(fèi),反而會(huì)更慢。即原因是一個(gè)進(jìn)程只存在一把gil鎖,當(dāng)在執(zhí)行多個(gè)線程時(shí),內(nèi)部會(huì)爭(zhēng)搶gil鎖,這會(huì)造成當(dāng)某一個(gè)線程沒有搶到鎖的時(shí)候會(huì)讓cpu等待,進(jìn)而不能合理利用多核cpu資源。
但是在使用多線程抓取網(wǎng)頁(yè)內(nèi)容時(shí),遇到IO阻塞時(shí),正在執(zhí)行的線程會(huì)暫時(shí)釋放GIL鎖,這時(shí)其它線程會(huì)利用這個(gè)空隙時(shí)間,執(zhí)行自己的代碼,因此多線程抓取比單線程抓取性能要好,所以我們還是要使用多線程的。
參考文章:
深度解析Python線程和進(jìn)程 - wyh草樣 - 博客園
Python-100-Days/13.進(jìn)程和線程.md at master · jackfrued/Python-100-Days · GitHub
原文鏈接:https://blog.csdn.net/xp_lx1/article/details/124421627
相關(guān)推薦
- 2022-12-15 深入了解Golang中的格式化輸出_Golang
- 2022-05-03 詳解Python實(shí)現(xiàn)圖像分割增強(qiáng)的兩種方法_python
- 2022-04-16 C語(yǔ)言實(shí)現(xiàn)順序循環(huán)隊(duì)列實(shí)例_C 語(yǔ)言
- 2022-11-10 Android開發(fā)Jetpack組件ViewModel與LiveData使用講解_Android
- 2022-01-29 win server 2008 web IIS部署asp.net程序后,CSS樣式錯(cuò)亂不顯示問題
- 2023-04-01 Unreal學(xué)習(xí)之簡(jiǎn)單三角形的繪制詳解_C 語(yǔ)言
- 2022-09-02 基于Python實(shí)現(xiàn)配置熱加載的方法詳解_python
- 2022-09-30 詳解OpenCV執(zhí)行連通分量標(biāo)記的方法和分析_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- 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錯(cuò)誤: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)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支