前言:線程和進程的關係圖
由可知,在每個應用程式執行的過程中,都會去產生一個主進程和主線程來完成工作,當我們需要並發的執行的時候,就會通過主進程去產生一系列的子進程(然後通過子進程產生一系列的子線程)來使不同的cpu調用,從而達到並發的效果。但是需要注意的是,在一般情況下每個進程之間是相互獨立的。
GIL全域解譯器鎖在Python中是專屬的,java和c#中都沒有,他的作用主要是什麼呢?我們都知道程式的執行最小單元是線程,在cpu1通過進程來調用線程的時候(只是在cpu調用的時候),只能輪詢的去調用某個進程中的線程,線程並不能進行並發的執行,也就是說一個時刻每顆cpu只能通過一個進程中的一個線程來完成某項工作。
在我們一般的程式中,如果沒有特意的建立進程和線程,那麼我們程式就是按照順序一步一步執行的,當我們建立了進程和線程之後,就會產生並發執行的效果。
進程
優點: 可同時利用多個cpu,進行多個操作
缺點: 重新開闢記憶體空間,非常耗費資源
個數: 一般和cpu顆數相同
使用場所: 一般是計算密集型
線程
優點: 共用記憶體(一個進程內),i/o操作可實現並發執行
缺點: 搶佔資源,切換上下文非常耗時
個數: 一般依情況而定
使用場所: i/o密集型
一.進程
進程的定義
進程(Process)是電腦中的程式關於某資料集合上的一次運行活動,是系統進行資源分派和調度的基本單位。其實進程就是程式執行的執行個體,程式放在那裡是不會執行的,只能通過建立進程來完成程式的操作。例如:我現在想去做飯,首先我拿起菜刀,然後我去切菜,之後開火,炒菜。做飯其實就是一個程式,我拿刀,切菜,開火,炒菜就就可以看成是一個一個的進程,他們在程式的執行流中有序的執行從而完成了一個操作。
1. 建立進程(Process類)
對於我們寫的一個程式而言,預設的都會有一個主進程和主線程來從上到小的去執行代碼,如果一但遇到要去建立進程和線程,然後主進程就會建立進程和線程,(然後建立的子進程和子線程就會自己去執行他要的執行的代碼),建立完成之後有兩種操作,一個就是等待子進程或者子線程的操作完成之後在結束程式,另一種就是當我的主進程完成之後,就立馬結束程式,無論你的子進程或者子線程有沒有完成。
# 在windows下做實驗的話,第一句必須加上if __name__ == "__main__":# 建立進程,# 參數target後面的代表的是此進程要執行的函數名稱# args後面跟的是一個元組,代表target後面函數所需要的參數p = multiprocessing.Process(target=foo, args=(1,)) p.join(5) # 當執行完此子進程之後再去執行其他的進程,參數5代表執行此子進程等待的最長時間,預設為s# daemon是指主進程是否要等待子進程完成之後再結束,預設是等待# True 代表不等待# False 代表等待p.daemon = True p.start() # 啟動子進程
1 # 下面這段代碼顯示結果為空白,因為在主進程結束之後就結束程式了 2 # 並不會去執行foo函數 3 import multiprocessing 4 import time 5 6 def foo(args): 7 # 這是個要通過子進程執行的函數 8 time.sleep(3) # 延遲三秒 9 print(args)10 11 if __name__ == "__main__":12 p = multiprocessing.Process(target=foo, args=(1,))13 p.daemon = True # 不等待子進程結束14 p.start() 15 16 17 # 下面這段代碼的執行結果為1 因為daemon的值為false,所以主進程要等待子進程執行完foo之後才會去結束程式18 import multiprocessing19 import time20 21 def foo(args):22 # 這是個要通過子進程執行的函數23 time.sleep(3) # 延遲三秒24 print(args)25 26 if __name__ == "__main__":27 p = multiprocessing.Process(target=foo, args=(1,))28 p.daemon = False # 不等待子進程結束29 p.start()
案例一daemon
# 當沒有join的時候,輸入結果為基本上是同時輸出的123456789import multiprocessingimport timedef foo(args):# 這是個要通過子進程執行的函數time.sleep(1)print(args)if __name__ == "__main__":for i in range(10): p = multiprocessing.Process(target=foo, args=(i,)) p.start()#有join的時候,他是一個一個輸出的,因為join代表的就是當這個子進程執行完之後才會去執行其他的進程import multiprocessingimport timedef foo(args):# 這是個要通過子進程執行的函數time.sleep(1)print(args)if __name__ == "__main__":for i in range(10): p = multiprocessing.Process(target=foo, args=(i,)) p.start() p.join(2)
案例二join
# 下面這個代碼不會輸出任何值,當程式執行了1s之後就會結束原因是join預設等待的時間為1s中,但是你的子進程卻需要10s的時間,所以子進程還沒有執行完主進程就結束了import multiprocessingimport timedef foo(args):# 這是個要通過子進程執行的函數time.sleep(10)print(args)if __name__ == "__main__": p = multiprocessing.Process(target=foo, args=(1,)) p.daemon = True p.start() p.join(1)
案例三join
2. 進程池(pool模組)
什麼叫做進程池呢?通俗點就是裝進程的容器,在我們寫程式的時候,我們不可能來一個程式,我們就去建立一個進程,進程是非常耗費資源的,因此我們通過事先定義一個裝進程的容器(進程的個數是固定的),當我們程式需要的時候就會自動的去進程池中區取,如果進程池中的子進程數被取完了,我們就只有等待其他的程式釋放了之後我們才能夠繼續使用。
if __name__ == "__main__":# 建立進程池proc_pool = multiprocessing.Pool(5)# 以下兩個都是使用進程池的方式# apply:他內部使用了join方法,每一個子進程進行了完了之後才會去進行下一個子進程的使用# apply_async:他內部沒有使用join方法,因此是所有的子進程並發的執行 proc_pool.apply() proc_pool.apply_async()
1 # 從結果可以看出來,每一個子進程完成了之後才會列印出最後的子進程建立完成 2 import multiprocessing 3 import time 4 5 def foo(s1): 6 time.sleep(1) 7 print(s1) 8 if __name__ == "__main__": 9 # 建立進程池,進程的個數為510 proc_pool = multiprocessing.Pool(5)11 for i in range(10):12 # 建立十個子進程,每個子進程都去執行foo函數,傳入的參數為i13 proc_pool.apply(foo, args=(i, ))14 print("子進程建立完成")15 16 輸出結果:17 018 119 220 321 422 523 624 725 826 927 子進程建立完成案例一apply
# 結果是先列印了進程建立完畢,從執行結果可以看出來,apply_async函數會使所有的子進程並發執行,後面的join函數要使主進程等待子進程完成之後在關閉程式import multiprocessingimport time# 執行的函數def foo(s1): time.sleep(1)return s1# 回呼函數def foo2(s1):print(s1)if __name__ == "__main__":# 建立進程池,進程的個數為5proc_pool = multiprocessing.Pool(5)for i in range(10):# 建立十個子進程,每個子進程都去執行foo函數,傳入的參數為i,把foo函數的傳回值當做參數給foo2,然後執行foo2函數proc_pool.apply_async(foo, args=(i, ), callback=foo2)print("子進程建立完成")# 關閉進程池 proc_pool.close()# 等待子進程執行完畢之後返回 proc_pool.join()輸出結果:子進程建立完成012345678案例2 apply_aysnc
3. 進程之間的共用
進程之間本來是獨立,互不影響的,如果實在想要在進程之間進行通訊的話有兩種方法。
<1>. 數組
<2>. manage模組建立特殊的資料類型
import multiprocessingimport multiprocessingdef f1(s1, dic): dic[s1] = s1if __name__ == "__main__":# 建立一個manage的對象manage = multiprocessing.Manager()# 通過manage建立一個特殊類型的dict,供進程之間進行使用dic = manage.dict()print("沒有修改之前的dic:",dic)for i in range(10): p = multiprocessing.Process(target=f1, args=(i, dic)) p.start() p.join()print("修改之後的dic:",dic)結果:沒有修改之前的dic: {}修改之後的dic: {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
二.線程
線程是程式最下的執行單元,他本質上也是一個進程,只不過是把進程更加的細微化的一個東西,也是用來執行程式的。
1. 建立線程
# 線程的建立和進程的建立都差不多,因為從形式上來將線程就是進程# 下面的方法和進程的方法是一樣的,就是把daemon變成了setDaemon而已if __name__ == "__main__":# 建立線程foo函數為要用子線程執行的函數,args為傳遞的參數thread = threading.Thread(target=foo, args=(1, ))# 啟動線程 thread.start()# 子線程等的最長時間thread.join(5)# 設定主進程完成之後是否要等待子線程完成,預設是不等待的thread.setDaemon(True)
1 # 下面這段代碼是沒有結果的,因為線程和進程不太一樣,線程預設是不等待子線程的 2 import threading 3 import time 4 # 執行的函數 5 def foo(s1): 6 time.sleep(1) 7 print(s1) 8 9 if __name__ == "__main__":10 thread = threading.Thread(target=foo, args=(1, ))11 thread.start()12 13 # 修改成下面這段代碼,就可以顯示結果了,14 import threading15 import time16 # 執行的函數17 def foo(s1):18 time.sleep(1)19 print(s1)20 21 if __name__ == "__main__":22 thread = threading.Thread(target=foo, args=(1, ))23 thread.setDaemon(False)24 thread.start()
案例一setDaemon
1 import threading 2 import time 3 4 # 執行的函數 5 def foo(s1): 6 time.sleep(1) 7 print(s1) 8 9 if __name__ == "__main__":10 for i in range(5):11 thread = threading.Thread(target=foo, args=(i, ))12 thread.start()13 thread.join(2)
案例二join
2. Rlock模組
Rlock模組從名字就可以看出來是一個鎖模組,我們都知道線程之間是記憶體共用的,因此當兩個線程同時修改某個值的時候,就會出現髒值(也就是我們預期不到的值),因為我們不知道到底哪個線程修改的有效,因此這個模組就應運而生了,當我們想去修改某個值的時候,就可以用到鎖模組,把值鎖定起來
1 # 其實這個例子看不出來資料的混亂。。。。 2 # 只是簡單的說了一下rlock模組的使用方法 3 import threading 4 import time 5 6 # 建立一個全域變數,要運用線程對其進行修改 7 num = [] 8 # 建立一個鎖對象 9 lock = threading.RLock()10 # 執行的函數11 def foo(s1):12 # 加鎖13 # lock.acquire()14 global num15 num.append(s1)16 print(num)17 # 釋放鎖18 # lock.release()19 if __name__ == "__main__":20 for i in range(40):21 thread = threading.Thread(target=foo, args=(i, ))22 thread.start()23 print(num)
rlock
3. event模組
event模組其實就是暫停意思,當我們使用了此模組之後,線程就會停在此處,當我們設定了相應的值之後,就會繼續執行。
1 import threading 2 import time 3 4 # 建立一個全域變數,要運用線程對其進行修改 5 num = [] 6 # 建立一個鎖對象 7 lock = threading.RLock() 8 event = threading.Event() 9 # 執行的函數10 def foo(s1):11 # 加鎖12 lock.acquire()13 # 線程在此暫停(紅燈)14 event.wait()15 global num16 num.append(s1)17 print(num)18 # 釋放鎖19 lock.release()20 if __name__ == "__main__":21 for i in range(5):22 thread = threading.Thread(target=foo, args=(i, ))23 thread.start()24 event.clear() # 設定為紅燈25 inp = input("輸入q繼續:")26 if inp == 'q':27 # 如果輸入的為q,就把event的等待狀態改變,繼續執行28 event.set() 29 30 31 結果輸出32 輸入True繼續:q33 [0]34 [0, 1]35 [0, 1, 2]36 [0, 1, 2, 3]37 [0, 1, 2, 3, 4]event+lock之後的狀態
1 import threading 2 import time 3 4 # 建立一個全域變數,要運用線程對其進行修改 5 num = [] 6 # 建立一個鎖對象 7 lock = threading.RLock() 8 event = threading.Event() 9 # 執行的函數10 def foo(s1):11 # 加鎖12 # lock.acquire()13 # 線程在此暫停(紅燈)14 event.wait()15 global num16 num.append(s1)17 print(num)18 # 釋放鎖19 # lock.release()20 if __name__ == "__main__":21 for i in range(5):22 thread = threading.Thread(target=foo, args=(i, ))23 thread.start()24 event.clear() # 設定為紅燈25 inp = input("輸入q繼續:")26 if inp == 'q':27 # 如果輸入的為q,就把event的等待狀態改變,繼續執行28 event.set()29 30 輸出結果:31 輸入q繼續:q32 [0]33 [0, 2]34 [0, 2, 1]35 [0, 2, 1, 4]36 [0, 2, 1, 4, 3]event模型
4. 生產者消費者模型(queue模組)
生產者消費者模型其實說的就是隊列,隊列我們只需要記住先進先出就可以了。
# 匯入隊列的模組import queue# 建立一個隊列,隊列的長度最多為5obj = queue.Queue(5)# 從隊列中擷取值,如果隊列為空白,則等待obj.get()# 從隊列中擷取值,如果隊列為空白,則放棄取值(不等待)obj.get_nowait()# 給隊列中上傳一個值obj.put("value")
5. 線程池
在Python中預設沒有建立線程池的方法,因此在此處總結了wupeiqi老師的兩個方法,方法的地址如下
這段代碼的有些地方是比較難懂的,主要的原因是之前寫的代碼都是順序執行的,而對於線程和進程而言,都是可以並發執行的,因此對於執行流還是需要注意的。
1 import queue 2 import threading 3 import time 4 5 class ThreadPool: 6 def __init__(self, max_num): 7 self.ThreadQueue = queue.Queue(max_num) 8 for i in range(max_num): 9 self.ThreadQueue.put(threading.Thread)10 def get_Thread(self):11 return self.ThreadQueue.get()12 13 def add_Thread(self):14 self.ThreadQueue.put(threading.Thread)15 16 def func(pool, args):17 time.sleep(2)18 print(args)19 pool.add_Thread()
線程池實現--簡單的方法
1 # -*- coding:utf-8 -*- 2 # zhou 3 # 2017/7/5 4 5 import threading 6 import queue 7 import time 8 9 # 列表退出標誌位 10 StopEvent = object() 11 12 class ThreadPool: 13 def __init__(self, max_num): 14 # 建立一個空的隊列用來存放任務而不是線程 15 self.q = queue.Queue() 16 # 設定閒置線程數為0 17 self.free_list = [] 18 # 已經建立的線程數 19 self.generate_list = [] 20 # 建立線程的最大個數 21 self.max_num = max_num 22 # 建立工作清單為空白 23 self.task = [] 24 self.terminal_flag = False 25 26 def apply(self, target, args, callback=None): 27 # 得到工作清單 28 task = (target, args, callback, ) 29 # print('***', args) 30 # 把工作清單排入佇列中 31 self.q.put(task) 32 # 去執行 33 if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: 34 # 如果沒有閒置線程並且建立的線程數小於最大線程數,就建立一個線程 35 self.generate_thread() 36 37 def generate_thread(self): 38 t = threading.Thread(target=self.run) 39 t.start() 40 41 def run(self): 42 current_thread = threading.currentThread 43 self.generate_list.append(current_thread) 44 event = self.q.get() 45 while event != StopEvent: 46 # 是任務,解開任務包,執行任務 47 func1, argument, func2 = event 48 # print("++",argument) 49 try: 50 ret = func1(*argument) 51 state = True 52 except Exception as e: 53 state = False 54 ret = e 55 if func2 is not None: 56 try: 57 func2(state, ret) 58 except Exception as e: 59 pass 60 if not self.terminal_flag: 61 self.free_list.append(current_thread) 62 event = self.q.get() 63 self.free_list.remove(current_thread) 64 else: 65 event = StopEvent 66 else: 67 # 不是任務,就移除 68 self.generate_list.remove(current_thread) 69 70 def close(self): 71 # StopEvent作為迴圈結束的標誌,有多少個線程就會給他建立多少個標誌位 72 num = len(self.generate_list) 73 while num: 74 self.q.put(StopEvent) 75 num -= 1 76 77 def terminal(self): 78 self.terminal_flag = True 79 while self.generate_list: 80 self.q.put(StopEvent) 81 # self.close() 82 self.q.empty() 83 # 執行函數 84 def foo(s1): 85 # time.sleep(0.5) 86 print(s1) 87 # 回呼函數 88 def f2(state, s2): 89 print(s2) 90 91 if __name__ == "__main__": 92 # 建立一個線程池 93 pool = ThreadPool(5) 94 for i in range(40): 95 # 應用線程池 96 # print('___',i) 97 pool.apply(target=foo, args=(i, )) 98 time.sleep(4) 99 pool.terminal()100 101線程池實現--複雜的方法
三.協程
協程是什麼呢?協程其實就是微線程,如,協程一般用在web頁面請求上面,使用協程要匯入模組gevent,下面貼一個簡單的使用例子
1 # -*- coding:utf-8 -*- 2 # zhou 3 # 2017/7/5 4 import gevent 5 import requests 6 7 def f1(url): 8 requests.get(url) 9 10 gevent.joinall([11 gevent.spawn(f1, ""),12 gevent.spawn(f1, ""),13 ]14 )
協程使用方法
四. 環境切換(contextlib)
其實這個環境切換和裝飾器有點類似,也是在一個操作的前後在去加上一點操作。
下面代碼執行流程
import contextlib@contextlib.contextmanagerdef file_open(file_name, mode): f = open(file_name, mode)try:yield ffinally: f.close()with file_open('te', 'r') as obj_f:print(obj_f.read())