4.從linux到python:線程和進程

linux進程和線程:https://www.cnblogs.com/cxuanBlog/p/13277369.html

一.Linux進程和線程

1.進程和線程的區(qū)別

  • 進程是系統(tǒng)資源分配的最小單位,線程是系統(tǒng)調(diào)度的最小單位
  • 進程在初始化的時候,就會擁有一個獨立的控制線程

https://blog.csdn.net/weixin_44602505/article/details/110893949
創(chuàng)建線程使用的底層函數(shù)和進程一樣,都是clone。從內(nèi)核里看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內(nèi)存資源的三級頁表是相同的。進程可以蛻變成線程。線程可看做寄存器和棧的集合。
實際上,無論是創(chuàng)建進程的fork,還是創(chuàng)建線程的pthread_create,底層實現(xiàn)都是調(diào)用同一個內(nèi)核函數(shù)clone。如果復制對方的地址空間,那么就產(chǎn)出一個“進程”;如果共享對方的地址空間,就產(chǎn)生一個“線程”。
因此:Linux內(nèi)核是不區(qū)分進程和線程的。只在用戶層面上進行區(qū)分。所以,線程所有操作函數(shù) pthread_* 是庫函數(shù),而非系統(tǒng)調(diào)用。

2.進程間通信方式

進程間通信通常被稱為:IPC(Internel-Process communication)
(個人猜測:Internel內(nèi)部是:相比于網(wǎng)絡通信,IPC是內(nèi)部的進程通信,走的是系統(tǒng)調(diào)用)
主要有6種:

image.png

這6種方式:都不會走網(wǎng)絡通信(TCP/IP那一套,直接走內(nèi)核的內(nèi)存等進行通信)
網(wǎng)絡socket(不同機器的不同進程)和命名socket的區(qū)別(同一臺機器的不同進程)參考:
https://blog.csdn.net/weixin_45121946/article/details/105045387
(個人猜測:結(jié)合python實現(xiàn)猜測)

  • 命名Socket是最底層的實現(xiàn)方式
  • Pipe:基于memoryview+Socket實現(xiàn)
  • Queue:基于Pipe進一步封裝實現(xiàn)等

3.進程管理系統(tǒng)調(diào)用

操作系統(tǒng)可以分為兩種模式:

  • 內(nèi)核態(tài):操作系統(tǒng)內(nèi)核使用的模式
  • 用戶態(tài):用戶應用程序使用的模式

系統(tǒng)調(diào)用(函數(shù)):是引起內(nèi)核態(tài)和用戶態(tài)切換的一種方式。
與進程相關的主要的系統(tǒng)調(diào)用包括:
1.fork
fork用于創(chuàng)建一個與父進程相同的子進程,創(chuàng)建完進程后的子進程擁有和父進程一樣的程序計數(shù)器、相同的CPU寄存器、相同的打開文件等
2.exec
exec 系統(tǒng)調(diào)用用于執(zhí)行駐留在活動進程中的文件,調(diào)用 exec 后,新的可執(zhí)行文件會替換先前的可執(zhí)行文件并獲得執(zhí)行。也就是說,調(diào)用 exec 后,會將舊文件或程序替換為新文件或執(zhí)行,然后執(zhí)行文件或程序。新的執(zhí)行程序被加載到相同的執(zhí)行空間中,因此進程的 PID不會修改,因為我們沒有創(chuàng)建新進程,只是替換舊進程。但是進程的數(shù)據(jù)、代碼、堆棧都已經(jīng)被修改。如果當前要被替換的進程包含多個線程,那么所有的線程將被終止,新的進程映像被加載執(zhí)行。
備注:
進程映像(Process image)的概念
進程映象是執(zhí)行程序時:所需要的可執(zhí)行文件(進程啟動后,程序加載到內(nèi)存,內(nèi)存的分配的映像)。通常包括下面這些東西

  • 代碼段(codesegment/textsegment):又稱文本段,用倆存放指令,運行代碼的一塊內(nèi)存空間。此空間大小在代碼運行前就已經(jīng)確定。內(nèi)存空間一般屬于只讀,某些架構(gòu)的代碼也允許可寫。代碼段中:也有可能包含一些只讀的常數(shù)變量,例如字符串常量等
  • 數(shù)據(jù)段(datasegment):可讀可寫,存儲初始化全局變量和初始化的static變量,數(shù)據(jù)段中的數(shù)據(jù)的生命周期是隨程序持續(xù)性(隨進程持續(xù)性)。隨進程持續(xù)性指的是:進程創(chuàng)建就存在,進程死亡就消失。
  • bss段(bss segement):可讀可寫。存儲未初始化的全局變量和未初始化的static變量。bss段中的數(shù)據(jù)一般默認為0
  • 棧(stack):可讀可寫。存儲的是函數(shù)或代碼中國呢的局部變量(非static變量),棧的生存期隨代碼塊持續(xù)性,代碼塊運行就給你分配空間,代碼塊結(jié)束,就自動回收空間。
  • 堆(heap):可讀可寫。存儲的是程序運行期間動態(tài)分配的malloc/relloc的空間,堆的生存期隨進程持續(xù)性,從malloc/relloc到free一直存在。


    image.png

    3.waitpid
    等待子進程結(jié)束或終止
    4.exit
    在許多計算機操作系統(tǒng)上,計算機進程的終止是通過執(zhí)行exit系統(tǒng)調(diào)用命令執(zhí)行的。

二.python進程和線程

1.基本概念

1.進程間通信(IPC)

進程是孤立的,但是可以彼此通信。進程間通信(IPC)通常有兩種方式:
1.基于消息傳遞
一條消息:就是一塊原始字節(jié)的緩存。
基于消息的IPC通常有兩種:

  • 管道
  • 隊列

2.共享內(nèi)存(mmap模塊):內(nèi)存映射區(qū)域
不太常見

2.共享數(shù)據(jù)的同步和訪問

當多進程或者多線程需要共享數(shù)據(jù)時,就會出現(xiàn)數(shù)據(jù)同步和訪問的問題。這也是并發(fā)編程常見的比較復雜的地方。

3.并發(fā)編程與python

python線程收到的限制比較多,主要原因是:python解釋器內(nèi)部使用了GIL(Global Interpreter Lock, 全局解釋器鎖)
GIL:使得在任意時刻只允許單個python線程執(zhí)行,無論系統(tǒng)上存在多少個可用的CPU核。
GIL說明:
Python解釋器別一個鎖保護,只允許一次執(zhí)行一個線程,即使存在多核。

  • 在計算密集型程序中:這嚴重限制了多線程的作用。事實上,在計算密集型程序中使用線程,經(jīng)常比僅僅按照順序執(zhí)行同樣的工作慢的多。通常用multiprocessing等模塊替代。
  • 在I/O密集型程序中:可能比較適合。比如:網(wǎng)絡服務器中使用線程。

2.multiprocessing

1.進程process類

用于創(chuàng)建和啟動一個進程
使用subprocess中的Popen類進行實現(xiàn)。
底層還是調(diào)用os相關的接口,去創(chuàng)建進程等

1)創(chuàng)建子進程時:會對當前一份進程鏡像的拷貝。所以:傳遞給子進程的函數(shù)的參數(shù)等,都會在子進程中有一份一模一樣的拷貝。子進程中對參數(shù)等對象的修改,完全不會影響到主進程。
2)通過多進程通信(IPC)發(fā)送消息的方式:隊列/管道中放入的項,在子進程中也是一個新的拷貝,修改其,不會影響到主進程中的該項。

2.進程間通信

1.Pipe類
單向/雙向都支持
Pipe類使用:Connection類實現(xiàn),Connection內(nèi)部使用:memory+命名socket通信實現(xiàn)。
管道內(nèi)部使用:pickle模塊作為序列化
關于IPC通信命名socket(同一臺機器不同進程)和網(wǎng)絡通信socket的區(qū)別(不同機器之間的網(wǎng)絡通信)詳見:
https://blog.csdn.net/weixin_45121946/article/details/105045387
2.Queue類
單向:更高級封裝
創(chuàng)建共享的進程隊列。底層隊列使用:管道和鎖實現(xiàn)。另外,還需要運行支持線程以便將隊列中的數(shù)據(jù)傳輸?shù)降讓庸艿乐小?br> 3.共享數(shù)據(jù)與同步(一般不建議使用)
其內(nèi)部是基于mmap模塊實現(xiàn)。

3.threading

由于GIL的存在,python的多線程可能更適用于IO密集型任務,而不太適合計算密集型任務。

1.線程Thread類

用于創(chuàng)建和啟動一個現(xiàn)成
(個人猜測)
底層是通過:gevent(select、poll、epoll)等方式創(chuàng)建的線程。
線程使用有兩種方式:

  • 創(chuàng)建Thread對象,傳遞可調(diào)用對象等
  • 繼承Thread類,重寫run方法。之后新的類也是線程類,創(chuàng)建對象,執(zhí)行方法等
2.Timer類

Timer類繼承Thread類,支持在給定時間后開始執(zhí)行線程。

4.線程同步相關

并發(fā)編程(主要是多線程,當然多進程也要考慮,有其他方式解決)涉及到共享數(shù)據(jù),就會有數(shù)據(jù)的同步的問題。為了解決同步,通常是加鎖方式。

1.Lock對象(原語鎖,是最底層的鎖)

原語鎖(或互斥鎖):是一個同步原語
有兩個狀態(tài):

  • 已鎖定
  • 未鎖定

方法:

  • Lock():創(chuàng)建新的Lock對象,初始狀態(tài)為未鎖定
  • lock.acquire([blocking]):獲取鎖,如果有必要,需要阻塞到鎖釋放為止。如果設置blocking=False,當無法獲取鎖時,將立即返回False,如果成功獲取鎖則返回為True。
  • lock.release():釋放一個鎖。當鎖處于未鎖定狀態(tài)時,或者從原本調(diào)用acquire()方法的線程不同的線程調(diào)用此方法,將會出現(xiàn)錯誤。(即:只能由獲取到鎖的線程,進行鎖的釋放)

備注:
如果有多個線程等待鎖,當鎖被釋放時,只有一個線程能獲得到它。等待縣城獲得鎖的順序沒有定義。

2.Rlock對象

可重入鎖(reentrant lock):是一個同步原語
它允許擁有鎖的線程執(zhí)行嵌套的acquire()和release操作。在這種情況下,只有最外面的release()操作,才能將鎖置為未鎖定狀態(tài)

3.信號量與有邊界的信號量(用的比較少)

信號量是一個基于計數(shù)器的同步原語??梢酝ㄟ^設置value值,指定內(nèi)部有多少信號量,可以用于線程的獲取和釋放。

4.Condition(條件變量,對原語鎖進行封裝)

1.condition用法介紹
條件變量是構(gòu)建在鎖上的同步原語,當需要線程關注特定的狀態(tài)變化或事件的發(fā)生時將使用這個鎖。
方法:

  • Condition([lock]):創(chuàng)建新的條件變量。lock是可選的Lock或Rlock實例。如果未提供lock參數(shù),就會創(chuàng)建新的Rlock實例供條件變量使用。
  • cv.acquire(args):獲取底層鎖。此方法將調(diào)用底層鎖上對應的acquire(args)方法
  • cv.release():釋放底層鎖。此方法將調(diào)用底層鎖上對應的release()方法
  • cv.wait([timeout]):一直等待直到被喚醒,或者出現(xiàn)超時狀態(tài)。
    此方法在調(diào)用線程已經(jīng)獲取鎖之后調(diào)用。調(diào)用后:將釋放底層鎖,而且線程將進入睡眠狀態(tài),知道另一個線程在該條件變量上執(zhí)行notify()或者notify_all()方法將其喚醒為止(通過內(nèi)部鎖實現(xiàn))。在線程被喚醒后,線程將重新獲取鎖(重新獲取底層鎖,當然如果有多個線程在wait,同時被喚醒,這些線程都會去爭取獲得底層鎖,但只有一個線程會獲取到該底層鎖,其他線程雖然被喚醒,但是會阻塞在獲取外部鎖的地方),方法也會返回。timeout是浮點數(shù),單位為s。如果這單時間耗盡,線程將被喚醒,重新獲取鎖,而控制將被返回。
  • cv.notify([n]):喚醒一個或多個等待此條件變量的線程。此方法只會在帶哦用線程已經(jīng)獲取鎖之后調(diào)用。如果沒有正在等待的線程,它就什么都不做。被喚醒的線程在他們重新獲取底層鎖之前不會從wait()返回
  • cv.notify_all():喚醒所有等待在此條件的線程。

2.condition的實現(xiàn)原理(源碼解析)
http://timd.cn/python/threading/condition/
http://darr-en1.top/2020/07/20/1/
總結(jié):
condition實現(xiàn)主要依靠兩層鎖:

  • condition初始化時創(chuàng)建一把鎖(外部鎖,或者叫底層鎖),使用時需要先對外部鎖上鎖;
  • 每次調(diào)用wait時,會先生成一個lock鎖(內(nèi)部鎖),將內(nèi)部鎖放到算雙端隊列waiters中,
    然后上鎖,再將外部鎖釋放。并再次獲取內(nèi)部鎖block(備注:第二次再調(diào)用acquire會阻塞當前線程),等待其他線程調(diào)用notify釋放該內(nèi)部鎖


    image.png

    備注:
    其中finally:是一定會走的流程。


    image.png
5.event事件(最外層封裝,對Condition做進一步封裝,建議直接用Condition)

event用于線程之間通信。其底層是依賴Condition+flag實現(xiàn)。
一個線程發(fā)出"事件"信號,一個或多個其他線程等待線程信號。
flag含義:

  • True:表示某個線程發(fā)出了信號,將flag設置為True。
  • False:表示當前的flag是False

1.用法:

  • Event():創(chuàng)建新的Event實例,并將內(nèi)部標志設為False。
  • e.is_set():只有當內(nèi)部標志為True時才返回True
  • e.set():將內(nèi)部標志設置為True。等待它變?yōu)門rue的所有線程都將被喚醒。(注意:雖然被喚醒,底層調(diào)用的是condition的wait方法,意味著:如果有多個線程等待,多個線程被喚醒后,會去競爭底層鎖,只有一個線程能真正的獲取到該鎖,往下執(zhí)行,其他線程依然阻塞在獲取底層鎖這里,等待下一次機會獲取)
  • e.clear():將內(nèi)部標志重制為False
  • e.wait([timeout]):線程阻塞在此event上,直到event玳標志為True。當然,如果進入時內(nèi)部標志就為True,此方法將立即返回。否則,它將阻塞,直到另一個線程調(diào)用set()方法。

5.concurrent包

concurrent中目前只有一個模塊:concurrent.futures
該模塊通過對多線程或者多進程的進一步封裝,提供異步執(zhí)行可調(diào)用對象的更高層接口。(更高的封裝意味著易用性更好,靈活性更差)
參考:
https://docs.python.org/zh-cn/3/library/concurrent.futures.html
貼上源碼:

image.png

Future的實現(xiàn)原理簡單分析(以ThreadPoolExecutor為例):
1.submit函數(shù)中:

  • 創(chuàng)建并返回future對象。
    ProcessPoolExector的submit


    image.png

    ThreadPoolExecutor的submit


    image.png

    2.啟動線程并提交執(zhí)行任務
    image.png

    3.執(zhí)行workItem的run方法
    image.png

    4.執(zhí)行函數(shù)fn,并將結(jié)果設置到future中


    image.png

5.Future的源碼


image.png

image.png

image.png

6.總結(jié)

1.多線程和多進程

  • 多進程:進程資源是相互隔離的,所以一般不會有共享數(shù)據(jù)的問題。主要關注是:進程之間通信的問題。
  • 多線程:多線程是在同一個進程內(nèi),共享同一個進程資源。不會有通信問題,主要關注是:對共享數(shù)據(jù)的同步問題。

2.進程內(nèi)通信(IPC)和網(wǎng)絡通信

  • 進程內(nèi)通信:主要是發(fā)送消息(對象序列化成字節(jié)的一塊緩存),不走網(wǎng)絡通信,其本質(zhì)是:內(nèi)存的偏移、拷貝等相關內(nèi)存操作來完成。
  • 網(wǎng)絡通信:需要走TCP/IP等網(wǎng)絡,需要網(wǎng)卡支持。其也是將對象序列話成字節(jié),然后通過網(wǎng)絡發(fā)送、接受等。

7.其他

1.關于進程之間的參數(shù)都是拷貝,那么future在ProcessPoolExecutor中是如何實現(xiàn)異步的呢?

參考:Pool的apply


image.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 1.進程和線程 1.1系統(tǒng)多任務機制 多任務操作機制的引入主要是在相同的硬件資源下怎么提高任務處理效率的!多任務的...
    _寧采臣閱讀 1,132評論 0 6
  • 前言 拖了好久,不過還是得堅持。喜歡本文的話可以加下公眾號【于你供讀】。 目錄 線程與進程 線程與進程是操作系統(tǒng)里...
    GitHubClub閱讀 985評論 0 4
  • python之進程、線程與協(xié)程 有這么個例子說他們的區(qū)別,幫助理解很有用。 有一個老板想開一個工廠生產(chǎn)手機。 他需...
    道無虛閱讀 3,322評論 0 3
  • 線程與進程 每一個應用程序都有一個自己的進程,操作系統(tǒng)會為這些進程分配一些執(zhí)行資源,例如內(nèi)存空間等。 在一個進程內(nèi)...
    Day_cun閱讀 208評論 0 2
  • Python多線程多進程 QUICK START 1.[endif]進程和線程 1.1系統(tǒng)多任務機制 多任務操作的...
    進化的程序猿閱讀 619評論 0 0

友情鏈接更多精彩內(nèi)容