1. 程式人生 > >進程間的數據共享、進程池的回調函數和線程初識、守護線程

進程間的數據共享、進程池的回調函數和線程初識、守護線程

arch 時空 能夠 回調 數據安全 args async 並且 放棄

一、進程的數據共享
進程間數據是獨立的,可以借助於隊列或管道實現通信,二者都是基於消息傳遞的
雖然進程間數據獨立,但可以通過Manager實現數據共享。

把所有實現了數據共享的比較便捷的類都重新又封裝了一遍,並且在原有的multiprocessing基礎上增加了新的機制 list dict等

數據共享的機制
    支持數據類型非常有限
    list dict都不是數據安全的,你需要自己加鎖來保證數據安全

Manager用法:
Manager().dict()  # 創建共享的字典
Manager().list()  # 創建共享的列表
    
用代碼看看:
簡單解釋一下with:
with可以自動關閉文件、線程鎖的自動獲取和釋放等事後清理工作。
緊跟with後面的語句會執行對象的 
__enter__() 方法,這個方法的返回值將被賦值給as後面的變量。 當with後面的代碼塊全部被執行完之後,將調用對象的 __exit__()方法。 from multiprocessing import Manager,Process,Lock def work(dic,lock): with lock: # 多個進程對數據進行修改,不加鎖的話會導致數據不安全 dic[count] -= 1 # 每個進程都對dic進行修改 if __name__ == __main__: lock = Lock() with Manager() as m: dic
= m.dict({count:100}) # 創建共享的字典 p_lst = [] for i in range(100): p = Process(target=work,args=(dic,lock)) p_lst.append(p) p.start() for p in p_lst: p.join() print(dic) 二、進程池的回調函數 場景: 子進程有大量的計算要去做,回調函數對結果做簡單處理。 我們可以把耗時間(阻塞)的任務放到進程池中,然後指定回調函數(主進程負責執行),這樣主進程在執行回調函數時就省去了I
/O的過程,直接拿到的是任務的結果。 通過例子了解一下: import os from multiprocessing import Pool def func(i): print(子進程:,os.getpid()) return i def call_back(res): print(回調函數:,os.getpid()) print(res--->,res) if __name__ == __main__: p = Pool() print(主進程:,os.getpid()) p.apply_async(func,args=(1,),callback=call_back) # callback關鍵字傳參,參數是回調函數 p.close() p.join() 結果: 主進程: 4732 子進程: 10552 回調函數: 4732 res---> 1 從結果可以看出: 子進程func執行完畢之後才去執行callback回調函數 子進程func的返回值會作為回調函數的參數 回調函數是在主進程中執行的 應用實例: url_lst = [ http://www.baidu.com, http://www.4399.com, http://www.163.com, http://www.hao123.com, http://www.sina.com ] import re from urllib.request import urlopen from multiprocessing import Pool def get_url(url): res = urlopen(url) # 打開鏈接 web_name = re.search(www\.(.*)\.com,url) # 網站名 print(%s finish %web_name.group(1)) return web_name.group(1),res.read() # 返回網站名和連接的內容 def call_back(content): # 把鏈接的內容寫入文件 web_name,con = content with open(web_name+.html,wb) as f: f.write(con) if __name__ == __main__: p = Pool() for url in url_lst: p.apply_async(get_url,args=(url,),callback=call_back) p.close() p.join() 三、線程的理論知識 1、進程概念 程序並不能單獨運行,只有將程序裝載到內存中,系統為它分配資源才能運行,而這種執行的程序就稱之為進程。 程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。 在多道編程中,我們允許多個程序同時加載到內存中,在操作系統的調度下,可以實現並發地執行。 這樣的設計,大大提高了CPU的利用率。進程的出現讓每個用戶感覺到自己獨享CPU,因此,進程就是為了在CPU上實現多道編程而提出的 2、進程的缺點 進程只能在一個時間做一件事,不能同時做兩件事或多件事 進程在執行的過程中如果阻塞,例如等待輸入,整個進程就會掛起,即使進程中有些工作不依賴於輸入的數據,也將無法執行 3、線程的概念 60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著計算機技術的發展,進程出現了很多弊端,一是由於進程是資源擁有者,創建、撤消與切換存在較大的時空開銷, 因此需要引入輕型進程;二是由於對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程並行開銷過大。 因此在80年代,出現了能獨立運行的基本單位——線程(Threads)。 註意:進程是資源分配的最小單位,線程是CPU調度的最小單位. 每一個進程中至少有一個線程。 4、進程和線程的關系 1)地址空間和其它資源(如打開文件):進程間相互獨立,同一進程的各線程間共享。某進程內的線程在其它進程不可見。 2)通信:進程間通信需要IPC(隊列,管道等),同一個進程中的所有線程的資源是共享的 3)調度和切換:線程上下文切換比進程上下文切換要快得多。 4)在多線程操作系統中,進程不是一個可執行的實體 5、線程使用場景 當某個進程肯定需要做不止一件事情的時候,比如你用QQ,你可以同時跟很多人聊天,而QQ只是一個進程,並不是說你跟一個人聊天就開一個進程這樣, 因為聊天這些任務操作的都是同一塊數據,因而不能用多進程。應該是你開了QQ這個進程,跟別人聊天的時候在這個進程裏開啟多個線程跟別人聊天。 6、線程的理解 多個線程共享同一個進程的地址空間中的資源,是對一臺計算機上多個進程的模擬,有時也稱線程為輕量級的進程。 同一臺計算機上多個進程,則共享這臺計算機的物理內存、磁盤、打印機等其他物理資源。多線程的運行跟多進程的運行類似,是cpu在多個線程之間的快速切換。 不同的進程之間是充滿敵意的,彼此是搶占、競爭cpu的關系,比如QQ和迅雷搶資源。而同一個進程是由一個程序員的程序創建,所以同一進程內的線程是合作關系,一個線程可以訪問另外一個線程的內存地址,大家都是共享的。 類似於進程,每個線程也有自己的堆棧,不同於進程,線程庫無法利用時鐘中斷強制線程讓出CPU,可以調用thread_yield運行線程自動放棄cpu,讓另外一個線程運行。 7、用戶級線程和內核級線程 用戶級線程 內核的切換由用戶態程序自己控制內核切換,不需要內核幹涉,少了進出內核態的消耗,但不能很好的利用多核Cpu。 在用戶空間模擬操作系統對進程的調度,來調用一個進程中的線程,每個進程中都會有一個運行時系統,用來調度線程。此時當該進程獲取cpu時,進程內再調度出一個線程去執行,同一時刻只有一個線程執行。 內核級線程 切換由內核控制,當線程進行切換的時候,由用戶態轉化為內核態。切換完畢要從內核態返回用戶態;可以很好的利用多核cpu,windows線程就是這樣的。 混合實現 用戶級與內核級的多路復用,內核同一調度內核線程,每個內核線程對應n個用戶線程 8、python中的線程 全局解釋器鎖GIL 一個進程中的多個線程能夠並行麽? 在java c++ c# 等語言中是可以的 但是在python中是不可以的 python是一個解釋型語言,所有的解釋型語言都不行 為什麽不行? Cpython解釋器內部有一把全局解釋器鎖 GIL 同一時刻用一個進程中的線程只有一個能被CPU執行 所以線程不能充分的利用多核 GIL鎖是為了保證數據的安全性,雖然確實是限制了你的程序效率 但實際上GIL鎖是目前能夠幫助你在線程的切換中提高效率的手段 9、總結 進程是 計算機中最小的資源分配單位 進程對於操作系統來說還是有一定負擔 創建一個進程 操作系統要分配的資源大致有 : 代碼 數據 文件 為什麽要有線程 輕量級的概念 他沒有屬於自己的進程資源: 一條線程只負責執行代碼,沒有自己獨立的代碼、變量、文件資源 什麽是線程 線程是計算機中被CPU調度的最小單位 你的計算機當中的cpu都是執行的線程中的代碼 線程和進程之間的關系 每一個進程中都有至少一條線程在工作 線程的特點 同一個進程中的線程共享這個線程的所有資源 輕量級 沒有自己的資源 進程和線程之間的區別 占用的資源 調度的效率 資源是否共享 通用的問題 一個進程中的多個線程能夠並行麽? 在java c++ c# 等語言中是可以的 python中的線程 在python中一個進程中的多個線程能夠並行麽? 不行 python是一個解釋型語言,所有的解釋型語言都不行 為什麽不行? Cpython解釋器 內部有一把全局解釋器鎖 GIL 所以線程不能充分的利用多核 同一時刻用一個進程中的線程只有一個能被CPU執行 GIL鎖是為了保證數據的安全性,雖然確實是限制了你的程序效率 但實際上GIL鎖是目前能夠幫助你在線程的切換中提高效率的手段 GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。 線程有: IO密集型線程和計算密集型線程 cpython解釋器適合IO密集型線程(web 爬蟲 金融分析) 如果要寫計算密集型的線程: 要麽換解釋器,要麽用多進程 四、threading模塊 multiprocess模塊完全模仿了threading模塊的接口,二者在使用層面,有很大的相似性 1、線程的創建方式 方式一: from threading import Thread import time def sleep_boy(name): time.sleep(1) print(%s is sleeping %name) t = Thread(target=sleep_boy,args=(xiaoming,)) # 這裏可以不需要main,因為現在只是在一個進程內操作,不需要導入進程就不會import主進程了 t.start() print(主線程) 方式二: from threading import Thread import time class Sleep_boy(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): time.sleep(1) print(%s is sleeping % self.name) t = Sleep_boy(xiaoming) t.start() print(主線程) 2、並發性(註意:在主進程下開啟多個線程,每個線程的pid都跟主進程的pid一樣) import os import time from threading import Thread def func(i): time.sleep(0.5) print(子線程:,i,os.getpid()) print(主進程:,os.getpid()) for i in range(10): t = Thread(target=func,args=(i,)) t.start() 3、線程共享進程的資源 from threading import Thread num = 100 # 全局變量 def func(): global num num -= 1 t_lst = [] for i in range(100): t = Thread(target=func) t.start() t_lst.append(t) for t in t_lst: t.join() print(num:,num) 4、其他方法 Thread實例對象的方法 isAlive(): 返回線程是否活動的。 is_alive(): 返回線程是否活動的。 getName(): 返回線程名。 setName(): 設置線程名。 from threading import Thread import time def func(): time.sleep(0.2) print(hello) t = Thread(target=func) t.start() print(t.isAlive()) # True print(t.is_alive()) # True print(t.getName()) # Thread-1 t.setName(t1) print(t.getName()) # t1 threading模塊提供的一些方法: threading.currentThread(): 返回當前線程的對象(通過這個對象可以查看線程的一些屬性,比如線程id:ident,線程的名字:getName等) threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。 threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。 from threading import currentThread,enumerate,activeCount,Thread import time def func(): print(子線程:,currentThread().ident) # 子線程: 6076 print(子線程:,currentThread().getName()) # 子線程: Thread-1 time.sleep(3) print(主線程:,currentThread().ident) # 主線程: 1156 print(主線程:,currentThread().getName()) # 主線程: MainThread t = Thread(target=func) t.start() print(enumerate()) # [<_MainThread(MainThread, started 1156)>, <Thread(Thread-1, started 6076)>] print(len(enumerate())) # 2 print(activeCount()) 5、守護線程 守護進程和守護線程的區別: 1 主進程在其代碼結束後就已經算運行完畢了(守護進程在此時就被回收),然後主進程會一直等非守護的子進程都運行完畢後回收子進程的資源(否則會產生僵屍進程),才會結束 2 主線程在其他非守護線程運行完畢後才算運行完畢(守護線程在此時就被回收)。因為主線程的結束意味著進程的結束,所以主線程結束了之後,守護線程隨著主進程的結束自然結束了 import time from threading import Thread def func1(): while True: time.sleep(0.5) print(func1) def func2(): print(func2 start) time.sleep(3) print(func2 end) t1 = Thread(target=func1) t2 = Thread(target=func2) t1.setDaemon(True) t1.start() t2.start() print(主線程代碼結束了)

進程間的數據共享、進程池的回調函數和線程初識、守護線程