併發編程前言:python
一、網絡應用程序員
1)爬蟲 直接應用併發編程;面試
2)網絡框架 django flask tornado 源碼-併發編程django
3)socketserver 源碼-併發編程編程
二、運維領域flask
1)自動化開發-運維開發(機器的批量管理,任務的批量執行等)網絡
1、操做系統/應用程序多線程
a、硬件併發
- 硬盤框架
- CPU
- 主板
- 顯卡
- 內存
- 電源
. . . . . .
b、裝系統(軟件)
- 系統就是一個由程序員寫出來的軟件,該軟件用於控制計算機的硬件,讓他們之間進行相互配合。
c、安軟件(安裝應用程序)
- 百度雲
- pycharm
. . . . . .
2、並行與併發
併發,是僞的,因爲執行速度特別快,人感受不到停頓;
並行,是真的,建立10我的同時操做;
並行是指二者同時執行,好比賽跑,兩我的都在不停的往前跑(資源夠用,好比三個線程,四核的cpu);
併發是指資源有限的狀況下,二者交替輪流使用資源,好比一段路(單核cpu資源)同時只能過一我的,A走一段後,讓給B,B用完繼續給A,交替使用,目的是提升效率;
區別:
並行是從微觀上,也就是在一個精確的時間片刻,有不一樣的程序在執行,這就要求必須有多個處理器。
併發是從宏觀上,在一個時間段上能夠當作是同時執行的。
3、線程和進程
a、單進程、單線程的應用程序,好比:
print('666')
b、到底什麼是線程?什麼是進程?
python本身沒有這玩意,python中調用的操做系統的線程和進程。
c、單進程、多線程的應用程序,好比:
import threading print('666') def func(arg): print(arg) t = threading.Thread(target=func,args=(11,)) # 建立一個線程 t.start()
一個應用程序(軟件),能夠有多個進程(默認只有一個),一個進程中能夠建立多個線程(默認一個)。
d、故事:Alex甄嬛西遊傳
總結:
1)操做系統幫助開發者操做硬件;
2)程序員寫好代碼在操做系統上運行(依賴解釋器);
3)任務比較多時,可將之前代碼寫法做以下改進:
import requests import uuid url_list = [ 'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg', 'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg', 'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg', ] def task(url): ret = requests.get(url) file_name = str(uuid.uuid4()) + '.jpg' with open(file_name, mode='wb') as f: f.write(ret.content) for url in url_list: task() """ - 咱們寫好代碼 - 交給解釋器運行: python s1.py - 解釋器讀取代碼,再交給操做系統去執行,根據咱們的代碼去選擇建立多少個線程/進程去執行(此程序是單進程/單線程)。 - 操做系統調用硬件:硬盤、cpu、網卡.... """
import threading import requests import uuid url_list = [ 'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg', 'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg', 'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg', ] def task(url): ret = requests.get(url) file_name = str(uuid.uuid4()) + '.jpg' with open(file_name, mode='wb') as f: f.write(ret.content) for url in url_list: t = threading.Thread(target=task, args=(url,)) # 建立線程 t.start() """ - 你寫好代碼 - 交給解釋器運行: python s2.py - 解釋器讀取代碼,再交給操做系統去執行,根據你的代碼去選擇建立多少個線程/進程去執行(此程序是單進程/4線程)。 - 操做系統調用硬件:硬盤、cpu、網卡.... """
4、python中線程和進程(GIL鎖)
GIL鎖:python內置的一個全局解釋器鎖。用於限制一個進程中同一時刻只有一個線程被cpu調度。
擴展:默認GIL鎖再執行100個cpu指令(過時時間)。
import sys v = sys.getcheckinterval() print(v) # 100
問題1:爲何有這把GIL鎖?
python語言的創始人在開發這門語言時,目的是快速把語言開發出來,若是加上GIL鎖(c語言加鎖),切換時按照100條字節指令來進行線程間的切換。
問題2:進程和線程的區別?
線程,線程是cpu工做的最小單元;
進程,進程是cpu資源分配的最小單元,爲線程提供一個資源共享的空間;
一個進程中能夠有多個線程,一個進程中默認有一個主線程;
對於python來講,它的進程和線程和其餘語言有差別,有GIL鎖。它保證一個進程中同一時刻只有一個線程被cpu調度;
IO密集型操做可使用多線程,計算密集型可使用多進程;
問題3:線程建立的越多越好嗎?
不是,線程之間進行切換,要作上下文管理。
5、python線程編寫
一、線程的基本使用,以下示例:
import threading def func(arg): print(arg) t = threading.Thread(target=func,args=(11,)) # 建立一個子線程 t.start() print(123) # 11 # 123
# 程序結束
二、主線程默認等子線程執行完畢,才結束程序,以下示例:
import threading import time def func(arg): time.sleep(arg) print(arg) t1 = threading.Thread(target=func,args=(3,)) t1.start() t2 = threading.Thread(target=func,args=(6,)) t2.start() # 3 # 程序開始3秒後 # 6 # 程序開始6秒後 # 程序結束
三、主線程再也不等,當主線程終止則全部子線程也終止,使用setDaemon(True),以下示例:
import threading import time def func(arg): time.sleep(arg) print(arg) t1 = threading.Thread(target=func,args=(3,)) t1.setDaemon(False) # 主線程等待此子進程 t1.start() t2 = threading.Thread(target=func,args=(6,)) t2.setDaemon(True) # 主線程再也不等待此子進程 t2.start() print(123) # 123 # 3 # # 程序結束
四、開發者能夠控制主線程等待子線程(最多等待時間),使用join(n),以下示例:
import threading import time def func(arg): time.sleep(arg) print(arg) print('建立子線程t1') t1 = threading.Thread(target=func,args=(3,)) t1.start() t1.join(2) # 無參數,讓主線程在這裏等着,等到子線程t1執行完畢,才能夠繼續往下走。 # 有參數,讓主線程在這裏最多等待n秒,不管是否執行完畢,會繼續往下走。 print('建立子線程t2') t2 = threading.Thread(target=func,args=(1,)) t2.start() t2.join(3) # 讓主線程在這裏等着,最多等待3秒,會繼續往下走, # 若子線程t2不到3秒就執行完畢,則子線程執行完畢主線程就往下走。 print(123) # 建立子線程t1 # 建立子線程t2 # 3 # 1 # 123 # 程序結束
五、線程名稱,以下示例:
import threading def func(arg): t = threading.current_thread() # 獲取當前執行該函數的線程的對象 name = t.getName() # 根據當前線程對象獲取當前線程名稱 print(name,arg) t1 = threading.Thread(target=func,args=(11,)) t1.setName('郭德綱') # 設置線程名稱 t1.start() t2 = threading.Thread(target=func,args=(22,)) t2.setName('孫紅雷') # 設置線程名稱 t2.start() print(123) # 郭德綱 11 # 孫紅雷 22 # 123
六、線程本質,以下示例:
# 先打印:11?123? import threading def func(arg): print(arg) t1 = threading.Thread(target=func,args=(11,)) t1.start() # start 是開始運行線程嗎?不是 # start 告訴cpu,我已經準備就緒,你能夠調度我了。 print(123)
七、面向對象版本的多線程,以下示例:
import threading def func(arg): print(arg) t1 = threading.Thread(target=func,args=(11,)) t1.start()
import threading class MyThread(threading.Thread): def run(self): print(11111,self._args,self._kwargs) t1 = MyThread(args=(11,)) t1.start() # 在cpu內部,若是要調度這個線程的話會執行這個對象的run方法 t2 = MyThread(args=(22,)) t2.start() print('end') # 11111 (11,) {} # 11111 (22,) {} # end
八、多線程
1)計算密集型多線程無用,以下示例:
import threading v1 = [11,22,33] # +1 v2 = [44,55,66] # +100 def func(data,plus): for i in range(len(data)): data[i] = data[i] + plus t1 = threading.Thread(target=func,args=(v1,1)) t1.start() t2 = threading.Thread(target=func,args=(v2,100)) t2.start()
2)IO操做,多線程有用(IO操做不佔用cpu),如「3、線程和進程」中建立多個線程的代碼示例;
九、多線程的問題(加鎖+釋放鎖)
import time import threading lock = threading.RLock() n = 10 def task(i): print('這段代碼不加鎖',i) lock.acquire() # 加鎖,此區域的代碼同一時刻只能有一個線程執行 global n print('當前線程',i,'讀取到的n值爲:',n) n = i time.sleep(1) print('當前線程',i,'修改n值爲:',n) lock.release() # 釋放鎖 for i in range(10): t = threading.Thread(target=task,args=(i,)) t.start()
總結:
1. 應用程序/進程/線程的關係? *****(面試題:進程/線程/協程的區別?)
2. 爲何要建立線程?
因爲線程是cpu工做的最小單元,建立線程能夠利用多核優點實現並行操做(Java/C#)。
注意:線程是爲了工做。
3. 爲何要建立進程?
進程和進程之間作數據隔離(Java/C#)。
注意:進程是爲了提供環境讓線程工做。
4. Python
a. Python中存在一個GIL鎖 *****
- 形成:多線程沒法利用多核優點。
- 解決:開多進程處理(浪費資源)
進程和線程的使用準則:
IO密集型:多線程
計算密集型:多進程
b. 線程的建立
- Thread *****
- MyThread
c. 其餘
- join *****
- setDaemon *****
- setName *****
- threading.current_thread() *****
d. 鎖
- 得到
- 釋放