日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

大并发下程序出错_Python并发编程理论篇

發布時間:2024/7/23 python 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大并发下程序出错_Python并发编程理论篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

其實關于Python的并發編程是比較難寫的一章,因為涉及到的知識很復雜并且理論偏多,所以在這里我盡量的用一些非常簡明的語言來盡可能的將它描述清楚,在學習之前首先要記住一個點: 并發編程永遠的宗旨就是提高程序的運行效率,也是圍繞CPU來進行優化的一種技術手段。

像我們之前學習過的網絡編程中,我們只是基于 socketserver 模塊讓我們的Server端有了處理多任務的能力,但是我們并不了解它的底層是怎么做到的,在學習完并發編程后,嘗試自己閱讀一下 socketserver 源碼,你將會大有收獲。

并發編程很重要嗎?是的,非常重要,如果你想進入 PythonWeb 領域那么著名的框架如 Django , Tornado , Flask 等等底層都是有基于本章節的知識點,如果你想進入爬蟲領域那就更不用說了,非常強大的 scrapy 框架也是基于我們所學的這些東西累積起來的。

好了廢話不多說,讓我們開始進入并發編程的學習吧。

從任務處理角度看待操作系統發展史

這一節主要是理論知識,了解計算機任務處理方式的演變過程,能夠讓我們更快的理解和學習并發編程。

首先,我們先來回顧一下操作系統方面的一些知識。

操作系統的作用:管理硬件,目的就是讓用戶更加方便的來操控計算機底層的硬件。

可以看到操作系統對于人們操控計算機進行作業有著不可小覷的功勞,那么在早期沒有操作系統的時候你能想象是什么樣子嗎?現在我們來看一看。

無操作系統時任務的處理方式

早期的計算機并沒有操作系統 ,而是通過紙帶來進行程序的編輯,它有三臺設備分別是:輸入機,計算機,輸出機。

那個時候的程序員需要一起約定好時間,來輪流的對自己的程序進行優化,因為那個時候的計算機在同一時刻下只能由一個人去運行和掌控,我們來看一下它的計算流程:

這個時代的計算機 一次只能跑一個人的程序 ,沒有其他干擾,那么它的缺點也很明顯,一次只能一個人使用而后面想要使用的人必須得等待前一個人用完之后才行。其執行效率非常低下,最關鍵的就是 人在進行與計算機交互的時候計算機的運算器是沒有任何工作的,這就造成了資源上極大的浪費 ,那么這種浪費可以理解為 I/O阻塞 。

為了解決這個問題,批處理系統橫空出世了。

批處理系統的誕生

相比于前一代計算機處理任務的方式,批處系統的誕生讓這一代計算機有了極大的進步,并且輸入也不再使用紙帶,而是采用磁帶,批處理操作系統 可以將多個用戶的任務同時提交(但是不能同時運行)。

假設有三個程序員需要使用這臺計算機,他們將自己的程序全部交由一個程序員讓其進行人機之間的交互,那么這樣就 節省了三倍的時間 。但是這樣的缺點也很明顯, 只能等待三個人的程序全部處理完后大家才能拿到各自的結果 ,這個等待過程是十分漫長的。

在這里,出現了一種自動化的工作方式, 計算機也就是中間的7094機器能夠去區分出每個程序員自己的程序 ,那么其內部肯定是由一種代碼支持它有了這種功能,那個這個就是批處理系統。

單處理的批處理系統最大的缺點依然還是擁有 I/O阻塞 ,能不能把中間的兩個小人全部干掉讓計算機來做他們做的事兒呢?當然可以,但是....當時的人還沒那么聰明。

我們再來想一個問題,如果程序員A的程序出錯了,它第一時間拿不到,返回會一直卡在那,程序員B和程序員C也不用拿了,反正都出不來。是不是很蛋疼?

后來慢慢的經過時間的積累與技術的成熟,針對這一代的批處理系統的缺點,又出現了新一代操作系統。

多道程序設計與分時操作系統的誕生

在這一代操作系統中最先出現了一種技術,名叫 SPOOLING 技術,這個技術的出現讓上圖的兩個小人下崗了。 SPOOLING 技術的出現極大的減少了 I/O阻塞 的時間,除此之外,該代操作系統還提出了一個非常重要的思想,即 多道程序設計 的思想,這個技術思想目前在我們的進程中依然存在,它的主要功能就是解決了順序執行(串行)的問題。

盡管這樣做的確讓程序效率提高了,但是我們還有一個問題。計算機中依然是批處理系統,還是要等A,B,C的叅櫊程序同時出結果才能拿到最終結果,這個時間太長了,就想上面說的如果程序員A的程序出錯了卡住了程序員B和C的正常程序也取不出來。

有的人開始懷念最早的無操作系統時代的計算機了,太懷念了,我一個人的程序十分鐘我就出來了,三個人的我要等三十分鐘,如果有一個出錯了我的等在久也出不來,我太難了...

為了解決這種問題,出現了極為牛逼的 分時操作系統

分時很形象的一個比喻就是一臺電腦給A,B,C每個程序員一個鍵盤鼠標和顯示器,大家共有一個主機各玩各的互不影響,都認為自己的程序是獨享的并且馬上就能看到自己程序的運行結果,你說牛不牛逼?大家都很開心,但是實際上大家還是共用的同一個CPU...( 多用戶多任務 )。

分時操作系統到現在依然存在,并且還十分常見,比如許多人去操作同一臺服務器。

這時候大家就在考慮,你丫鍵盤鼠標顯示器啥都給我了,為啥不再給我一個主機呢?這其實還是受限于當時的成本條件,但是到了如今計算機的成本以及體積都下來后,這些都不是問題了。

個人操作系統的誕生

現在咱們大家都是用的個人操作系統,已經挺熟練了吧,這個玩意兒每個人都在玩,但是雖然大家不共有一個CPU了,其實在系統內部依然存在著切換,它就是進程或者線程之間的切換。

應用程序與系統之間的關系

現在咱們聊一聊應用程序與系統之間的關系,其實對于開發者而已,我們與操作系統之間是隔了很多層的。如圖所示:

所以,我們自己寫的程序要想運行,必須從上至下的依次經過這些關卡。

為什么要聊這個,因為聊完這個之后我們才能接著往下看。

并發并行阻塞非阻塞同步異步

這幾個概念將貫穿接下來的所有學習。

并發和并行是指操作系統處理任務的能力:( 一個一個處理?一次處理多個? )

并發:操作系統具有處理多個任務的能力。

并行:操作系統具有 同時 處理多個任務的能力。

PS:并發包含并行。這里再提一個偽并行,就是看起來像是同時處理,但是實際上并不是同時處理。

同步和異步是指任務的提交方式:( 任務提交完后等你結果我再進行下一步操作?或者不等你的結果我接著干我的其他事? )

同步:任務提交之后,原地等待任務的返回結果,等待的過程中不做任何事。(干等),程序上面表現出來的感覺就是卡住了。

異步:任務提交之后,不原地等待任務的返回結果,直接去做其他事情,等待任務的返回結果自動提交給調用者。

Ps: 對于異步來說,那么我們提交任務后的返回結果如何獲取?

提交任務后的返回結果會有一個異步回調機制自動處理,可以理解為當該任務有結果就會自動返回回來。給你打電話告訴你一聲我這邊完成了,你別忙了,看我一眼。

阻塞和非阻塞是指程序的運行狀態:( 程序現在卡住了嗎?卡住了就是阻塞,沒卡就是非阻塞 )

阻塞:是指調用某個函數的時候被卡住不動了,比如 input() 函數會導致阻塞

非阻塞:是指調用某個函數的時候不會卡住,而是立即返回的一種形式

進程理論

進程的定義

大白話版本:

進程你可以把它當做一件屋子,里面放了很多物件(資源), 所以進程就是最小的資源單位。 另外我們要注意一點,程序只有在運行狀態時才會產生進程,而不運行的時候就是一堆死代碼。

程序是一堆躺在硬盤上的代碼,是"死"的

進程則是表示程序正在執行的過程,是"活的"

所以說,進程這玩意兒就是在程序執行過程中產生的,它會有一些資源狀態放在這個屋子里。

并且一定要注意,進程這玩意兒是一個系統級別的概念,進程是由操作系統創建出來的。程序執行的時候我們就會有一個進程,當然一個程序運行中也可以產生多個進程。

專業版本

詳細定義:

進程就是一個程序在一個數據集上的一次動態執行過程。

進程一般由程序、數據集、進程控制塊三部分組成。

我們編寫的程序用來描述進程要完成哪些功能以及如何完成;

數據集則是程序在執行過程中所需要使用的資源;

進程控制塊用來記錄進程的外部特征,描述進程的執行變化過程,系統可以利用它來控制和管理進程,它是系統感知進程存在的唯一標志。

數據集提供所有程序運行時需要的資源,進程控制塊用來記錄程序的狀態,比如說掛起被切換狀態還是運行狀態等等...

進程間的數據交互

進程之間按理說是不應該允許彼此之間數據交互的, 因為每個進程都是一間獨立的小房子,每個小房子的資源都是自己獨享的。 但是我們之前學過 socket 模塊,這玩意兒最早就是用來解決進程間數據交互(進程間通信)問題的。

所以,進程之間雖然默認不支持數據交互,但是我們可以使用某些特殊手段讓兩個進程之間支持數據交互,但是這不是很容易就能完成的,需要付出一些代價。

進程切換

一個CPU核心同一時刻最多只能運行一個進程,而多個CPU核心同一時刻可以運行多個進程,這個就是并發的體現。 我們說過,多道技術的產生解決了程序串行的問題,那么就必然涉及到進程切換。 進程切換實際上是由操作系統說了算,除了我們的I/O操作切換外,它還有以下控制進程切換的手段,PS:進程的切換代價也是比較巨大的,因為一旦切換就要保證當前進程中的資源數據,而切換回來時又要將進程的狀態復原 :

1.先來先服務算法

誰先開辟了一個小屋子,那么就先運行你。這個說白了對一個存活時間很短的進程是相當不利的,如果一個存活時間很長的進程占用了一個CPU核心,那么恰巧這個CPU又是單核的,其他存活時間短的進程永遠也得不到CPU的眷顧了。所以單一的這種策略不行。

2.短作業優先調度算法

誰的進程作業時間短(即存活時間短)就先運行誰,顯然,單一的這種算法會讓長作業進程得不到CPU眷顧,故也不能一直采取這種策略。

3.時間片輪轉(時間輪詢)

什么意思呢?就是說假如有多個進程,我每個進程讓你運行個三五秒就切換到另一個進程運行,如此來回切換就是時間片輪轉。即將時間切成一段。

4.多級反饋隊列

這個其實是基于時間片輪轉做的,它會將當前所有的活動進程送入一個隊列中,根據存活時間來為其分配到不同的隊列中,進程存活時間越久,其得到CPU眷顧的次數越低。如圖:

其實在Linux系統中,我們可以為一個進程分配更多的時間片與更高的優先級,這里暫且先不提。

線程理論

線程的定義

大白話版:

每個進程存在的時候都默認會有一個線程,如果把進程比喻做房子,那么線程就是房子里的人(可以有一個也可以有多個,默認一個)。線程才是真正干活的單位, 因此線程是最小的執行單位,線程共享進程中所有數據(進程資源集)。

進程和線程是一個包含關系:必須有進程才有線程,就像線程這個人必須住在進程的房子里。

專業版本:

線程詳細定義:

1 一個程序至少有一個進程,一個進程至少有一個線程.(進程可以理解成線程的容器)

2 進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。

3 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

4 進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位. 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧)但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源. 一個線程可以創建和撤銷另一個線程;同一個進程中的多個線程之間可以并發執行。

線程間的數據交互

線程必須存在于進程中,我們上面說過一個進程可以有多個線程,那么想當然的該進程里的所有資源都可以被位于該進程中的線程所拿到。

而跨進程之間的線程就是屬于進程間的數據交互了。

但是我們一定要注意一點,就是線程安全。這句話怎么說呢?就是這個房子里有一顆糖,被一個小人吃了,那么其他的小人也應該知道這顆糖沒了才行。雖然聽起來很符合邏輯,但是別忘了線程不是真正的人,它是傻的,所以當我們進行線程之間數據交互的時候一定要注意線程安全 。

線程安全的問題還是由于線程切換導致的,比如這個房間一共有10顆糖,一個小人吃了3顆糖被CPU通知歇息一會,那么他會認為還剩下7顆糖,另一個干活的小人又吃了3顆糖后去休息了,那么現在第一個休息的小人上線了,但是真正的糖果數量只剩下了4顆,他還傻乎乎的任務是7顆。

線程切換

線程切換與進程切換如出一轍,看上面的就行了。

Python中的GIL鎖

終于聊到這個話題了,GIL鎖被稱為全局解釋器鎖。這玩意兒直接讓Python的多線程殘了,我們用圖來解釋這個鎖是干嘛用的:

我們再來看一眼諸如C#或者Java中的多線程運行是怎么樣的。

所以!Python中的多線程沒有并行操作!同時處理多個事對于Python里的單進程下的多線程來說是做不到的,那么我們可以怎么辦呢?

自己在學GIL鎖時作的筆記:

Python中的一個線程對應于C語言中的一個線程( 基于CPython ),而 CPython前面也已經說過了。會將函數轉變為可執行的字節碼,而多個線程同時運行一段字節碼是很有可能出錯的,為了避免這個錯誤所以Python使用了GIL鎖限制了多線程技術。 具體如下:

GIL 使得同一個時刻只能有一個線程在 CPU 上執行字節碼( 一般情況下 ),無法將多個線程映射到多個CPU 上去執行。 ? 因此 Python多線程的GIL鎖 注定了其在多線程任務處理并沒有太大優勢

當GIL 鎖死一個線程之后,并不是非要等這個線程運行完后才會釋放。而是會在適當的時候就進行釋放 :

1:時間輪詢機制

2: I/O操作

所以Python中線程的并行操作是不被支持的(Cpython),Python并不適合做多線程的大量計算。這樣的時間遠不如串行來的簡單,因為在線程切換之中會導致運行速度的減緩。

Python中的線程不能并行,但是進程是存在并行的。所以,Python的線程更加適用于密集型I/O操作比如網絡爬蟲方面,Python的GIL鎖在某種程度上來說是保護了線程安全,但是更多的被人詬病。開發團隊曾經嘗試過去GIL鎖但是發現去掉GIL鎖之后實現線程并行的這種方式讓運行速度更加慢了下來,具體原因是因為CPython中的大量模塊第三方庫在設計之初都是在有GIL鎖的情況下設計的,所以一旦改版后果不能被人預料。

但是也不用悲觀,Python的GIL鎖只是解釋器層面和語言本身并無關系,比如PYPY就是沒有GIL鎖的一種解釋器。

擴展:進程切換與程序計數器

不同的進程之間能進行切換那么不同的線程之間也必定能進行切換,既然線程是最小的執行單位那么同一進程中的線程切換的代價必然是少于進程間的切換的。

進程切換

為了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,并恢復以前掛起的某個進程的執行。這種行為被稱為進程切換,這種切換是由操作系統來完成的。因此可以說,任何進程都是在操作系統內核的支持下運行的,是與內核緊密相關的。

從一個進程的運行轉到另一個進程上運行,這個過程中經過下面這些變化:

1. 保存處理機上下文,包括程序計數器和其他寄存器。

2. 更新PCB信息。

3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。

4. 選擇另一個進程執行,并更新其PCB。

5. 更新內存管理的數據結構。

6.恢復處理機上下文。

注:總而言之就是很耗資源的

程序計數器

我們都知道軟件的數據是存儲在硬盤上的,這個調用的過程十分緩慢,但是在內存中就會快很多。同時,一個線程或者進程的切換掛起狀態如果是存放在內存中那么是肯定不行的,這個速度對于切換毫秒級別的線程或者進程來說速度依舊不夠快。所以在CPU旁邊有了一個程序計數器的存在,由于距離CPU比較近傳輸狀態的時間也會相應縮短。它的大小并不是很大只有小小的 1 到 2kb ,主要功能就是存儲了這些進程或者線程切換狀態的數據。存儲的其實都是--->內存地址。

總結

以上是生活随笔為你收集整理的大并发下程序出错_Python并发编程理论篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。