python之7進程線程和協程的詳解

來源:互聯網
上載者:User

前言:線程和進程的關係圖

  由可知,在每個應用程式執行的過程中,都會去產生一個主進程和主線程來完成工作,當我們需要並發的執行的時候,就會通過主進程去產生一系列的子進程(然後通過子進程產生一系列的子線程)來使不同的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())

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.