進程與線程的曆史
我們都知道電腦是由硬體和軟體組成的。硬體中的CPU是電腦的核心,它承擔電腦的所有任務。 作業系統是運行在硬體之上的軟體,是電腦的管理者,它負責資源的管理和分配、任務的調度。 程式是運行在系統上的具有某種功能的軟體,比如說瀏覽器,音樂播放器等。 每次執行程式的時候,都會完成一定的功能,比如說瀏覽器幫我們開啟網頁,為了保證其獨立性,就需要一個專門的管理和控制執行程式的資料結構——進程式控制制塊。 進程就是一個程式在一個資料集上的一次動態執行過程。 進程一般由程式、資料集、進程式控制制塊三部分組成。我們編寫的程式用來描述進程要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;進程式控制制塊用來記錄進程的外部特徵,描述進程的執行變化過程,系統可以利用它來控制和管理進程,它是系統感知進程存在的唯一標誌。
在早期的作業系統裡,電腦只有一個核心,進程執行程式的最小單位,任務調度採用時間片輪轉的搶佔式方式進行進程調度。每個進程都有各自的一塊獨立的記憶體,保證進程彼此間的記憶體位址空間的隔離。 隨著電腦技術的發展,進程出現了很多弊端,一是進程的建立、撤銷和切換的開銷比較大,二是由於對稱式多處理機(對稱式多處理機(SymmetricalMulti-Processing)又叫SMP,是指在一個電腦上彙集了一組處理器(多CPU),各CPU之間共用記憶體子系統以及匯流排結構)的出現,可以滿足多個運行單位,而多進程並行開銷過大。 這個時候就引入了線程的概念。 線程也叫輕量級進程,它是一個基本的CPU執行單元,也是程式執行過程中的最小單元,由線程ID、程式計數器、寄存器集合 和堆棧共同組成。線程的引入減小了程式並發執行時的開銷,提高了作業系統的並發效能。 線程沒有自己的系統資源,只擁有在運行時必不可少的資源。但線程可以與同屬與同一進程的其他線程共用進程所擁有的其他資源。
進程與線程之間的關係
線程是屬於進程的,線程運行在進程空間內,同一進程所產生的線程共用同一記憶體空間,當進程退出時該進程所產生的線程都會被強制退出並清除。線程可與屬於同一進程的其它線程共用進程所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資訊(如程式計數器、一組寄存器和棧)。
python 線程
Threading用於提供線程相關的操作,線程是應用程式中工作的最小單元。
1、threading模組
threading 模組建立在 _thread 模組之上。thread 模組以低級、原始的方式來處理和控制線程,而 threading 模組通過對 thread 進行二次封裝,提供了更方便的 api 來處理線程。
import threadingimport time def worker(num): """ thread worker function :return: """ time.sleep(1) print("The num is %d" % num) return for i in range(20): t = threading.Thread(target=worker,args=(i,),name=“t.%d” % i) t.start()
上述代碼建立了20個“前台”線程,然後控制器就交給了CPU,CPU根據指定演算法進行調度,分區執行指令。
Thread方法說明
t.start() : 啟用線程,
t.getName() : 擷取線程的名稱
t.setName() : 設定線程的名稱
t.name : 擷取或設定線程的名稱
t.is_alive() : 判斷線程是否為啟用狀態
t.isAlive() :判斷線程是否為啟用狀態
t.setDaemon() 設定為後台線程或前台線程(預設:False);通過一個布爾值設定線程是否為守護線程,必須在執行start()方法之後才可以使用。如果是後台線程,主線程執行過程中,後台線程也在進行,主線程執行完畢後,後台線程不論成功與否,均停止;如果是前台線程,主線程執行過程中,前台線程也在進行,主線程執行完畢後,等待前台線程也執行完成後,程式停止
t.isDaemon() : 判斷是否為守護線程
t.ident :擷取線程的標識符。線程標識符是一個非零整數,只有在調用了start()方法之後該屬性才有效,否則它只返回None。
t.join() :逐個執行每個線程,執行完畢後繼續往下執行,該方法使得多線程變得無意義
t.run() :線程被cpu調度後自動執行線程對象的run方法
2、線程鎖threading.RLock和threading.Lock
由於線程之間是進行隨機調度,並且每個線程可能只執行n條執行之後,CPU接著執行其他線程。為了保證資料的準確性,引入了鎖的概念。所以,可能出現如下問題:
例:假設列表A的所有元素就為0,當一個線程從前向後列印列表的所有元素,另外一個線程則從後向前修改列表的元素為1,那麼輸出的時候,列表的元素就會一部分為0,一部分為1,這就導致了資料的不一致。鎖的出現解決了這個問題。
import threadingimport time globals_num = 0 lock = threading.RLock() def Func(): lock.acquire() # 獲得鎖 global globals_num globals_num += 1 time.sleep(1) print(globals_num) lock.release() # 釋放鎖 for i in range(10): t = threading.Thread(target=Func) t.start()
3、threading.RLock和threading.Lock 的區別
RLock允許在同一線程中被多次acquire。而Lock卻不允許這種情況。 如果使用RLock,那麼acquire和release必須成對出現,即調用了n次acquire,必須調用n次的release才能真正釋放所佔用的瑣。
import threadinglock = threading.Lock() #Lock對象lock.acquire()lock.acquire() #產生了死瑣。lock.release()lock.release() import threadingrLock = threading.RLock() #RLock對象rLock.acquire()rLock.acquire() #在同一線程內,程式不會堵塞。rLock.release()rLock.release()
4、threading.Event
python線程的事件用於主線程式控制制其他線程的執行,事件主要提供了三個方法 set、wait、clear。
事件處理的機制:全域定義了一個“Flag”,如果“Flag”值為 False,那麼當程式執行 event.wait 方法時就會阻塞,如果“Flag”值為True,那麼event.wait 方法時便不再阻塞。
clear:將“Flag”設定為False
set:將“Flag”設定為True
Event.isSet() :判斷標識位是否為Ture。
import threading def do(event): print('start') event.wait() print('execute') event_obj = threading.Event()for i in range(10): t = threading.Thread(target=do, args=(event_obj,)) t.start() event_obj.clear()inp = input('input:')if inp == 'true': event_obj.set()
當線程執行的時候,如果flag為False,則線程會阻塞,當flag為True的時候,線程不會阻塞。它提供了本地和遠端並發性。
5、threading.Condition
一個condition變數總是與某些類型的鎖相聯絡,這個可以使用預設的情況或建立一個,當幾個condition變數必須共用和同一個鎖的時候,是很有用的。鎖是conditon對象的一部分:沒有必要分別跟蹤。
condition變數服從上下文管理協議:with語句塊封閉之前可以擷取與鎖的聯絡。 acquire() 和 release() 會調用與鎖相關聯的相應的方法。
其他和鎖關聯的方法必須被調用,wait()方法會釋放鎖,當另外一個線程使用 notify() or notify_all()喚醒它之前會一直阻塞。一旦被喚醒,wait()會重新獲得鎖並返回,
Condition類實現了一個conditon變數。 這個conditiaon變數允許一個或多個線程等待,直到他們被另一個線程通知。 如果lock參數,被給定一個非空的值,,那麼他必須是一個lock或者Rlock對象,它用來做底層鎖。否則,會建立一個新的Rlock對象,用來做底層鎖。
wait(timeout=None) : 等待通知,或者等到設定的逾時時間。當調用這wait()方法時,如果調用它的線程沒有得到鎖,那麼會拋出一個RuntimeError 異常。 wati()釋放鎖以後,在被調用相同條件的另一個進程用notify() or notify_all() 叫醒之前 會一直阻塞。wait() 還可以指定一個逾時時間。
如果有等待的線程,notify()方法會喚醒一個在等待conditon變數的線程。notify_all() 則會喚醒所有在等待conditon變數的線程。
注意: notify()和notify_all()不會釋放鎖,也就是說,線程被喚醒後不會立刻返回他們的wait() 調用。除非線程調用notify()和notify_all()之後放棄了鎖的所有權。
在典型的設計風格裡,利用condition變數用鎖去通許訪問一些共用狀態,線程在擷取到它想得到的狀態前,會反覆調用wait()。修改狀態的線程在他們狀態改變時調用 notify() or notify_all(),用這種方式,線程會儘可能的擷取到想要的一個等待者狀態。 例子: 生產者-消費者模型,
import threadingimport timedef consumer(cond): with cond: print("consumer before wait") cond.wait() print("consumer after wait") def producer(cond): with cond: print("producer before notifyAll") cond.notifyAll() print("producer after notifyAll") condition = threading.Condition()c1 = threading.Thread(name="c1", target=consumer, args=(condition,))c2 = threading.Thread(name="c2", target=consumer, args=(condition,)) p = threading.Thread(name="p", target=producer, args=(condition,)) c1.start()time.sleep(2)c2.start()time.sleep(2)p.start()
6、queue模組
Queue 就是對隊列,它是安全執行緒的
舉例來說,我們去麥當勞吃飯。飯店裡面有廚師職位,前台負責把廚房做好的飯賣給顧客,顧客則去前台領取做好的飯。這裡的前台就相當於我們的隊列。形成管道樣,廚師做好飯通過前台傳送給顧客,所謂單向隊列
這個模型也叫生產者-消費者模型。
import queueq = queue.Queue(maxsize=0) # 構造一個先進顯出隊列,maxsize指定隊列長度,為0 時,表示隊列長度無限制。q.join() # 等到隊列為kong的時候,在執行別的操作q.qsize() # 返回隊列的大小 (不可靠)q.empty() # 當隊列為空白的時候,返回True 否則返回False (不可靠)q.full() # 當隊列滿的時候,返回True,否則返回False (不可靠)q.put(item, block=True, timeout=None) # 將item放入Queue尾部,item必須存在,可以參數block預設為True,表示當隊列滿時,會等待隊列給出可用位置, 為False時為非阻塞,此時如果隊列已滿,會引發queue.Full 異常。 選擇性參數timeout,表示 會阻塞設定的時間,過後, 如果隊列無法給出放入item的位置,則引發 queue.Full 異常q.get(block=True, timeout=None) # 移除並返回隊列頭部的一個值,選擇性參數block預設為True,表示擷取值的時候,如果隊列為空白,則阻塞,為False時,不阻塞, 若此時隊列為空白,則引發 queue.Empty異常。 選擇性參數timeout,表示會阻塞設定的時候,過後,如果隊列為空白,則引發Empty異常。q.put_nowait(item) # 等效於 put(item,block=False)q.get_nowait() # 等效於 get(item,block=False)
代碼如下:
#!/usr/bin/env pythonimport Queueimport threadingmessage = Queue.Queue(10) def producer(i): while True: message.put(i) def consumer(i): while True: msg = message.get() for i in range(12): t = threading.Thread(target=producer, args=(i,)) t.start() for i in range(10): t = threading.Thread(target=consumer, args=(i,)) t.start()
那就自己做個線程池吧:
# 簡單往隊列中傳輸線程數import threadingimport timeimport queueclass Threadingpool(): def __init__(self,max_num = 10): self.queue = queue.Queue(max_num) for i in range(max_num): self.queue.put(threading.Thread) def getthreading(self): return self.queue.get() def addthreading(self): self.queue.put(threading.Thread)def func(p,i): time.sleep(1) print(i) p.addthreading()if __name__ == "__main__": p = Threadingpool() for i in range(20): thread = p.getthreading() t = thread(target = func, args = (p,i)) t.start()
#往隊列中無限新增工作import queueimport threadingimport contextlibimport timeStopEvent = object()class ThreadPool(object): def __init__(self, max_num): self.q = queue.Queue() self.max_num = max_num self.terminal = False self.generate_list = [] self.free_list = [] def run(self, func, args, callback=None): """ 線程池執行一個任務 :param func: 任務函數 :param args: 任務函數所需參數 :param callback: 任務執行失敗或成功後執行的回呼函數,回呼函數有兩個參數1、任務函數執行狀態;2、任務函數傳回值(預設為None,即:不執行回呼函數) :return: 如果線程池已經終止,則返回True否則None """ if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: self.generate_thread() w = (func, args, callback,) self.q.put(w) def generate_thread(self): """ 建立一個線程 """ t = threading.Thread(target=self.call) t.start() def call(self): """ 迴圈去擷取任務函數並執行任務函數 """ current_thread = threading.currentThread self.generate_list.append(current_thread) event = self.q.get() # 擷取線程 while event != StopEvent: # 判斷擷取的線程數不等於全域變數 func, arguments, callback = event # 拆分元祖,獲得執行函數,參數,回呼函數 try: result = func(*arguments) # 執行函數 status = True except Exception as e: # 函數執行失敗 status = False result = e if callback is not None: try: callback(status, result) except Exception as e: pass # self.free_list.append(current_thread) # event = self.q.get() # self.free_list.remove(current_thread) with self.work_state(): event = self.q.get() else: self.generate_list.remove(current_thread) def close(self): """ 關閉線程,給傳輸全域非元祖的變數來進行關閉 :return: """ for i in range(len(self.generate_list)): self.q.put(StopEvent) def terminate(self): """ 突然關閉線程 :return: """ self.terminal = True while self.generate_list: self.q.put(StopEvent) self.q.empty() @contextlib.contextmanager def work_state(self): self.free_list.append(threading.currentThread) try: yield finally: self.free_list.remove(threading.currentThread)def work(i): print(i) return i +1 # 返回給回呼函數def callback(ret): print(ret)pool = ThreadPool(10)for item in range(50): pool.run(func=work, args=(item,),callback=callback)pool.terminate()# pool.close()
python 進程
multiprocessing是python的多流程管理組件,和threading.Thread類似。
1、multiprocessing模組
直接從側面用subprocesses替換線程使用GIL的方式,由於這一點,multiprocessing模組可以讓程式員在給定的機器上充分的利用CPU。在multiprocessing中,通過建立Process對象產生進程,然後調用它的start()方法,
from multiprocessing import Process def func(name): print('hello', name) if __name__ == "__main__": p = Process(target=func,args=('zhangyanlin',)) p.start() p.join() # 等待進程執行完畢
在使用並發設計的時候最好儘可能的避免共用資料,尤其是在使用多進程的時候。 如果你真有需要 要共用資料, multiprocessing提供了兩種方式。
(1)multiprocessing,Array,Value
資料可以用Value或Array儲存在一個共用記憶體地圖裡,如下:
from multiprocessing import Array,Value,Process def func(a,b): a.value = 3.333333333333333 for i in range(len(b)): b[i] = -b[i] if __name__ == "__main__": num = Value('d',0.0) arr = Array('i',range(11)) c = Process(target=func,args=(num,arr)) d= Process(target=func,args=(num,arr)) c.start() d.start() c.join() d.join() print(num.value) for i in arr: print(i)
輸出:
3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
建立num和arr時,“d”和“i”參數由Array模組使用的typecodes建立:“d”表示一個雙精確度的浮點數,“i”表示一個有符號的整數,這些共用對象將被安全執行緒的處理。
Array(‘i’, range(10))中的‘i’參數:
‘c’: ctypes.c_char ‘u’: ctypes.c_wchar ‘b’: ctypes.c_byte ‘B’: ctypes.c_ubyte
‘h’: ctypes.c_short ‘H’: ctypes.c_ushort ‘i’: ctypes.c_int ‘I’: ctypes.c_uint
‘l’: ctypes.c_long, ‘L’: ctypes.c_ulong ‘f’: ctypes.c_float ‘d’: ctypes.c_double
(2)multiprocessing,Manager
由Manager()返回的manager提供list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array類型的支援。
from multiprocessing import Process,Managerdef f(d,l): d["name"] = "zhangyanlin" d["age"] = 18 d["Job"] = "pythoner" l.reverse() if __name__ == "__main__": with Manager() as man: d = man.dict() l = man.list(range(10)) p = Process(target=f,args=(d,l)) p.start() p.join() print(d) print(l)
輸出:
{0.25: None, 1: '1', '2': 2}
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Server process manager比 shared memory 更靈活,因為它可以支援任意的物件類型。另外,一個單獨的manager可以通過進程在網路上不同的電腦之間共用,不過他比shared memory要慢。
2、進程池(Using a pool of workers)
Pool類描述了一個背景工作處理序池,他有幾種不同的方法讓任務卸載背景工作處理序。
進程池內部維護一個進程式列,當使用時,則去進程池中擷取一個進程,如果進程池序列中沒有可供使用的進進程,那麼程式就會等待,直到進程池中有可用進程為止。
我們可以用Pool類建立一個進程池, 展開提交的任務給進程池。 例:
#applyfrom multiprocessing import Poolimport time def f1(i): time.sleep(0.5) print(i) return i + 100 if __name__ == "__main__": pool = Pool(5) for i in range(1,31): pool.apply(func=f1,args=(i,)) #apply_asyncdef f1(i): time.sleep(0.5) print(i) return i + 100def f2(arg): print(arg) if __name__ == "__main__": pool = Pool(5) for i in range(1,31): pool.apply_async(func=f1,args=(i,),callback=f2) pool.close() pool.join()
一個進程池對象可以控制背景工作處理序池的哪些工作可以被提交,它支援逾時和回調的非同步結果,有一個類似map的實現。
processes :使用的背景工作處理序的數量,如果processes是None那麼使用 os.cpu_count()返回的數量。
initializer: 如果initializer是None,那麼每一個背景工作處理序在開始的時候會調用initializer(*initargs)。
maxtasksperchild:背景工作處理序退出之前可以完成的任務數,完成後用一個心的背景工作處理序來替代原進程,來讓閑置的資源被釋放。maxtasksperchild預設是None,意味著只要Pool存在背景工作處理序就會一直存活。
context: 用在制定背景工作處理序啟動時的上下文,一般使用 multiprocessing.Pool() 或者一個context對象的Pool()方法來建立一個池,兩種方法都適當的設定了context
注意:Pool對象的方法只可以被建立pool的進程所調用。
New in version 3.2: maxtasksperchildNew in version 3.4: context
進程池的方法
apply(func[, args[, kwds]])
:使用arg和kwds參數調用func函數,結果返回前會一直阻塞,由於這個原因,apply_async()更適合并發執行,另外,func函數僅被pool中的一個進程運行。
apply_async(func[, args[, kwds[, callback[, error_callback]]]]) : apply()方法的一個變體,會返回一個結果對象。如果callback被指定,那麼callback可以接收一個參數然後被調用,當結果準備好回調時會調用callback,調用失敗時,則用error_callback替換callback。 Callbacks應被立即完成,否則處理結果的線程會被阻塞。
close() : 阻止更多的任務提交到pool,待任務完成後,背景工作處理序會退出。
terminate() : 不管任務是否完成,立即停止背景工作處理序。在對pool對象進程記憶體回收的時候,會立即調用terminate()。
join() : wait背景工作執行緒的退出,在調用join()前,必須調用close() or terminate()。這樣是因為被終止的進程需要被父進程調用wait(join等價與wait),否則進程會成為殭屍進程。
map(func, iterable[, chunksize])¶map_async(func, iterable[, chunksize[, callback[, error_callback]]])¶imap(func, iterable[, chunksize])¶imap_unordered(func, iterable[, chunksize])starmap(func, iterable[, chunksize])¶starmap_async(func, iterable[, chunksize[, callback[, error_back]]])
python 協程
線程和進程的操作是由程式觸發系統介面,最後的執行者是系統;協程的操作則是程式員。
協程存在的意義:對於多線程應用,CPU通過切片的方式來切換線程間的執行,線程切換時需要耗時(儲存狀態,下次繼續)。協程,則只使用一個線程,在一個線程中規定某個代碼塊執行順序。
協程的適用情境:當程式中存在大量不需要CPU的操作時(IO),適用於協程;
event loop是協程執行的控制點, 如果你希望執行協程, 就需要用到它們。
event loop提供了如下的特性:
註冊、執行、取消延時調用(非同步函數)
建立用於通訊的client和server協議(工具)
建立和別的程式通訊的子進程和協議(工具)
把函數調用送入線程池中
協程樣本:
import asyncio async def cor1(): print("COR1 start") await cor2() print("COR1 end") async def cor2(): print("COR2") loop = asyncio.get_event_loop()loop.run_until_complete(cor1())loop.close()
最後三行是重點。
asyncio.get_event_loop() : asyncio啟動預設的event loop
run_until_complete() : 這個函數是阻塞執行的,知道所有的非同步函數執行完成,
close() : 關閉event loop。
1、greenlet
import greenletdef fun1(): print("12") gr2.switch() print("56") gr2.switch() def fun2(): print("34") gr1.switch() print("78") gr1 = greenlet.greenlet(fun1)gr2 = greenlet.greenlet(fun2)gr1.switch()
2、gevent
gevent屬於第三方模組需要下載安裝包
pip3 install --upgrade pip3pip3 install gevent
import geventdef fun1(): print("www.baidu.com") # 第一步 gevent.sleep(0) print("end the baidu.com") # 第三步 def fun2(): print("www.zhihu.com") # 第二步 gevent.sleep(0) print("end th zhihu.com") # 第四步 gevent.joinall([ gevent.spawn(fun1), gevent.spawn(fun2),])
遇到IO操作自動切換:
import geventimport requestsdef func(url): print("get: %s"%url) gevent.sleep(0) date =requests.get(url) ret = date.text print(url,len(ret))gevent.joinall([ gevent.spawn(func, 'https://www.pythontab.com/'), gevent.spawn(func, 'https://www.yahoo.com/'), gevent.spawn(func, 'https://github.com/'),])
文章轉自:http://www.cnblogs.com/aylin/p/5601969.html