面試準備4

今天主要準備系統編程這一塊:進程、線程、協程
還記得大學的時候調試的代碼嗎?一直都是單進程、單線程在跑代碼,曾經也想過爲什麼程序只能這樣跑—那是因爲自己的學識還不夠,見的太少了。以前都是單條腿走路,現在可以多條腿走路類。
(1)引入
現實生活中的多任務,代碼中的同步和異步----程序世界中的同步和異步和現實世界中的剛好相反,程序中的同步表示一個任務做完接着做下一個任務,異步表示多個任務可以並行執行的。單核CPU和多核CPU,單核CPU執行代碼都是順序執行,那麼單核CPU執行任務是什麼樣?----這個問題就是多任務怎麼執行,答案就是操作系統輪流讓各個任務交替執行(任務1執行多少秒,然後切換到任務2…),由於CPU的執行速度實在是太快類,我們感覺就像所有的任務都在同時執行一樣。真正的並行執行多任務只能在多核CPU上實現,但是,由於任務數量遠遠多
於CPU的核心數量,所以,操作系統也會自動把很多任務輪流調度到每個核
心上執行。

(2)進程以及進程的創建
編寫完之後的代碼,在沒有運行的時候,稱之爲程序,正在運行的代碼,就稱爲進程。進程處理代碼以外還需要運行的環境等:進程=運行中的代碼+運行代碼所需要的環境

—進程創建方式1:
fork()創建--------------典型的fork炸彈
python的os模塊封裝類常見的系統調用,其中就包括fork,可以在python程序中輕鬆創建子進程,創建過程:
–程序執行到os.fork()時,操作系統會創建一個新的進程(子進程),然後複製父進程的所有信息到子進程中
–然後父進程和子進程都會從fork()函數中得到一個返回值,在子進程中這個值一定是0,而父進程中是子進程的id號,可以通過os.getpid()或者os.getppid獲得此進程的id以及對應的父進程的id

說明:多進程中,每個進程中所有數據(包括全局變量)都各有擁有一份,互不影響,進程之間獨立,互不影響

–創建進程方式2:multiprocessing
由於Python是跨平臺的,自然也應該提供一個跨平臺的多進程支持。multiprocessing模塊就是跨平臺版本的多進程模塊。
multiprocessing模塊提供了一個Process類來代表一個進程對象

#coding=utf-8
from	multiprocessing	import	Process
import	os
#	子進程要執行的代碼
def	run_proc(name):
	print('子進程運行中,name=	%s	,pid=%d...'	%	(name,	os.getpid()))
if	__name__=='__main__':
	print('父進程	%d.'	%	os.getpid())
	p	=	Process(target=run_proc,	args=('test',))
	print('子進程將要執行')
	p.start()
	p.join()
	print('子進程已結束')

創建子進程時,只需要傳入一個執行函數和函數的參數,創建一個
Process實例,用start()方法啓動,這樣創建進程比fork()還要簡單。
join()方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步。使用process不執行join方法,主程序也會等到子進程執行完畢之後在結束主進程。

也可以自己重寫Process類來實現進程的創建:
創建新的進程還能夠使用類的方式,可以自定義一個類,繼承Process類,每次實例化這個類的時候,就等同於實例化一個進程對象,請看下面的實例:

from	multiprocessing	import	Process
import	time
import	os
#繼承Process類
class	Process_Class(Process):
				#因爲Process類本身也有__init__方法,這個子類相當於重寫了這個方法,
				#但這樣就會帶來一個問題,我們並沒有完全的初始化一個Process類,所以就不能使用從這個
				#最好的方法就是將繼承類本身傳遞給Process.__init__方法,完成這些初始化操作
				def	__init__(self,interval):
								Process.__init__(self)
								self.interval	=	interval
				#重寫了Process類的run()方法
				def	run(self):
								print("子進程(%s)	開始執行,父進程爲(%s)"%(os.getpid(),os.getppid()
								t_start	=	time.time()
								time.sleep(self.interval)
								t_stop	=	time.time()
								print("(%s)執行結束,耗時%0.2f秒"%(os.getpid(),t_stop-t_start))
if	__name__=="__main__":
				t_start	=	time.time()
				print("當前程序進程(%s)"%os.getpid())								
				p1	=	Process_Class(2)
				#對一個不包含target屬性的Process類執行start()方法,就會運行這個類中的run()方法
				p1.start()
				p1.join()
				t_stop	=	time.time()
				print("(%s)執行結束,耗時%0.2f"%(os.getpid(),t_stop-t_start))

創建進程實例的時候和之前差不多,然後進程實例調用start方法,Process內部就會自動調用重寫的run方法-----進程所需要執行的操作放在了run裏面,之後執行join–主進程會等待子進程執行完畢之後繼續往下執行

–進程創建方式3:進程池Pool
當需要創建的子進程數量不多時,可以直接利用multiprocessing中的Process動態成生多個進程,但如果是上百甚至上千個目標,手動的去創建進程的工作量巨大,此時就可以用multiprocessing模塊提供的Pool方法。初始化Pool時,可以指定一個最大進程數,當有新的請求提交到Pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到指定的最大值,那麼該請求就會等待,直到池中有進程結束,纔會創建新的進程來執行

進程池核心代碼:

po=Pool(3)	#定義一個進程池,最大進程數3
for	i	in	range(0,10):
		#Pool.apply_async(要調用的目標,(傳遞給目標的參數元祖,))
		#每次循環將會用空閒出來的子進程去調用目標
		po.apply_async(worker,(i,))
print("----start----")
po.close()	#關閉進程池,關閉後po不再接收新的請求
po.join()	#等待po中所有子進程執行完成,必須放在close語句之後
print("-----end-----")

進程池和Process進程不太一樣,這裏進程池必須要進行等待纔可以,一旦主進程結束,不管進程池是否執行完畢,都會強制結束進程池中的進程,進程池包括非阻塞和阻塞方式調用函數,非阻塞表示所有的任務同時入池操作,阻塞表示一個進程執行完畢之後下一個任務才能入池,通常情況下使用非阻塞的進程池比較多。多任務同時入池

總結:以上就是創建進程的三種方式,fork()、multiprocessing中的Process、以及Pool

(3)進程之間的通信
首先需要明白進程之間是沒有關係的,對於全局、局部變量都是各自在各自的內存中,操作之間互不影響,所以進程之間的通信,操作系統提供了許多機制來實現

–1.Queue的使用:使用multiprocessing模塊中的Queue實現進程之間的數據傳遞,通過一個第三方的管道來進行通信
–2.進程池中的進程通信:使用multiprocessing.Manager()中的Queue()

以上就是進程的創建,使用,以及進程之間的通信問題

(4)線程:多線程
python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,線程的創建和使用和進程的創建以及使用差不多,但是兩種是不同的操作單元,調度者也不同。多線程併發的操作,花費時間要短很多。創建好的線程,需要調用 start()方法來啓動,主線程會等待所有的子線程結束後才結束這個和Process創建進程時的操作一致。

#coding=utf-8
import	threading
from	time	import	sleep,ctime
def	sing():
	for	i	in	range(3):
	print("正在唱歌...%d"%i)
	sleep(1)
def	dance():
	for	i	in	range(3):
		print("正在跳舞...%d"%i)
		sleep(1)
if	__name__	==	'__main__':
		print('---開始---:%s'%ctime())
		t1	=	threading.Thread(target=sing)
		t2	=	threading.Thread(target=dance)
		t1.start()
		t2.start()
		#sleep(5)	#	屏蔽此行代碼,試試看,程序是否會立⻢結束?
		print('---結束---:%s'%ctime())

核心代碼還是:創建線程實例,開啓線程實例

線程使用方式2:自定義線程類繼承自threading.Thread類

#coding=utf-8
import	threading
import	time
class	MyThread(threading.Thread):
		def	run(self):
				for	i	in	range(3):
					time.sleep(1)
					msg	=	"I'm	"+self.name+'	@	'+str(i)	#name屬性中保存的是當前線程的
					print(msg)
if	__name__	==	'__main__':
			t	=	MyThread()
			t.start()

調用start的時候直接調用類中重寫的run方法
說明:由於進程和線程的調度由操作系統決定,所有,多進程、多線程的執行順序是不確定的

線程的總結:

1)每個線程一定會有一個名字,儘管上面的例子中沒有指定線程對象的name,但是python會自動爲線程指定一個名字
(2)當線程的run()方法結束的時候該線程完成
(3)無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式
(4)線程的幾種狀態:新建、就緒、運行、等待、死亡

(6)多線程–共享全局變量
對於多進程來說,全局變量不是共享,每個進程都有一份自己的全局變量,但是多線程就存在全局變量共享,全局變量的共享缺點就是,線程之間對全局變量的隨意修改會造成全局變量的混亂,也就是說線程不安全。

(7)進程和線程的簡單理解
從功能上理解:
進程:能夠完成多任務,在一臺電腦上能夠同時運行多個wechat
線程:能夠完成多任務,比如一個wechat中可以開多個聊天窗口

從定義上:
進程:系統進行資源分配和調度的一個獨立單位
線程:線程是進程的一個實體,是CPU調度和分派的基本單位,他是比進程更小的能獨立運行的基本單位
也就是說進程和線程都是由操作系統來調度的

從區別上:
–(1)一個程序至少有一個進程,一個進程至少有一個線程
–(2)線程劃分尺度小於進程(資源比進程少),使得多線程程序的併發性高
–(3)進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而提高程序的運行效率
–(4)線程不能獨立執行,必須依存在進程中

優缺點:線程和進程使用上各有優缺點,線程執行的開銷小,但是不利於資源的管理和保護,而進程正好相反

(8)系統編程中的同步
同步-----一件事做完接着做另外一件事,有對接的

(9)線程中的互斥鎖
多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制,不然會造成共享數據的混亂,所以在多線程中引入了互斥鎖機制,給使用共享數據的代碼段進行上鎖
—上鎖
—釋放鎖
from threading import Thread, Lock
mutex = Lock()
上鎖:mutexFlag = mutex.acquire(True)
解鎖:mutex.release()
上鎖和解鎖之間放的就是操作共享的數據代碼片段

鎖的好處:確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行
鎖的壞處:阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖,在多線程開發中,全局變量是多個線程都共享的數據,而局部變量是各自線程的,是非共享的。死鎖狀態—死鎖就是兩個線程之間相互調用時出現的

(8)避免死鎖
死鎖出現在多線程中,解決死鎖的情況是讓多個線程有序的執行,同步的應用,可以使用互池鎖完成多個任務,有序的進程工作,這就是線程的同步

(9)生產者和消費者模式
–隊列:先進先出
–棧:先進後出
在線程世界裏,生產者就是生產數據的線程,消費者就是消費數據的線程。在多線程開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。爲了解決這個問題於是引入了生產者和消費者模式。
---------什麼是生產者消費者模式
生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產者生產完數據之後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。這個阻塞隊列就是用來給生產者和消費者解耦的。縱觀大多數設計模式,都會找一個第三者出來進行解耦,通過第三方來鏈接生產者和消費者

(10)ThreadLocal
在多線程環境下,每個線程都有自己的數據,一個只有在自己的線程中線程使用自己的局部變量比使用全局變量好,因爲局部變量只有自己線程能看到,不影響其他線程,而全局變量的修改必須加鎖
使用ThreadLocal的方法創建出來的變量在不同的線程中也是相互沒有關係的:

定義過程:

import	threading
#	創建全局ThreadLocal對象:
local_school	=	threading.local()
def	process_student():
				#	獲取當前線程關聯的student:
				std	=	local_school.student
				print('Hello,	%s	(in	%s)'	%	(std,	threading.current_thread().name))
def	process_thread(name):
				#	綁定ThreadLocal的student:
				local_school.student	=	name
				process_student()
t1	=	threading.Thread(target=	process_thread,	args=('dongGe',),	name=
t2	=	threading.Thread(target=	process_thread,	args=('老王',),	name=
t1.start()
t2.start()
t1.join()
t2.join()

一個ThreadLocal變量雖然是全局變量,但每個線程都只能讀寫自己線程的獨立副本,互不干擾。ThreadLocal解決了參數在一個線程中各個函數之間互相傳遞的問題

也就是說這個東西創建的方式是全局的,但是在各個線程中使用的是自己的

(11)異步
多個任務併發執行,在主任務執行的時候,各個子任務也是在同步執行的,深刻的理解同步和異步。使用多線程或者多進程來模擬異步的過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章