Python語法進階(1)- 程式與執行緒程式設計

葛老頭發表於2022-02-16

1.程式與多程式

1.1.什麼是程式

  • 程式就是程式執行的載體
  • 什麼叫多工?
    • 多工就是作業系統可以同時執行多個任務。比如你一邊在用瀏覽器學習,還一邊在聽音樂,,這就是多工,至少同時有3個任務正在執行。還有很多工悄悄地在後臺同時執行著,只是桌面上沒有顯示而已。
  • 什麼是程式?
    • 對於作業系統來說,一個任務就是一個程式,比如開啟一個瀏覽器就是啟動一個瀏覽器程式,開啟一個word就啟動了一個word程式,開啟兩個記事本就啟動了兩個記事本程式。
  • 怎樣的任務算一個程式?
    • 當一個任務被開啟後,作業系統會分配它所需的系統資源,包括記憶體,I/O和CPU等,如果系統資源不夠,則會出現系統崩潰,這樣的任務可稱為程式。
  • python中如何建立程式?
    • 使用模組:multiprocessing
    • 建立方法: multiprocessing.Process(...)


1.2.程式在生活中的應用

  • 我們開啟的每個軟體、遊戲、執行的每一個python指令碼都是啟動一個程式
  • 軟體(遊戲,指令碼)==程式

1.3.程式的口糧

每一個程式像人一樣需要吃飯,他的糧食就是:cpu和記憶體


1.4.多程式

可以啟動多個程式,他們之間互不干擾,執行自己的業務邏輯


1.5.多程式的執行方式

 

2.執行緒與多執行緒

2.1.什麼是執行緒

  • 先有程式再有執行緒,程式吸收足夠的資源(CPU、記憶體)然後交給執行緒,執行緒是真正執行邏輯的角色。
  • 執行緒是作業系統最小的執行單元,程式至少由一個執行緒組成。如何排程程式和執行緒,完全有作業系統決定,程式自己不能決定什麼時候執行,執行多長時間。有些程式還不止同時幹一件事,比如微信,它可以同時進行語音、發文字、瀏覽資訊等事情。
  • 怎樣的任務算一個執行緒?
    • 程式被執行後算一個執行緒,程式是不執行的,執行緒才會執行,而一個程式有多個執行緒就涉及到程式有多少可以被cpu單獨呼叫的模組,這個呼叫的模組可以通過手動建立執行緒來建立。
  • 在python中如何建立執行緒?
    • 使用模組:threading
    • 建立方法:threading.Thread(...)


2.2.執行緒與程式的關係

程式提供執行緒執行程式的前置要求,執行緒在重組的資源配備下,去執行程式


2.3.多執行緒

  • 舉例說明:開啟一個瀏覽器程式後,從瀏覽器(主執行緒)中建立出多個執行緒來開啟多個頁面即一個瀏覽器開啟多個tab頁,這些tab頁就是瀏覽器程式的多執行緒
  • 初級認知:多執行緒會比多程式更加節省資源

2.4.多執行緒的執行方式

  • 並行:在多個cpu核心上同時執行多個程式
  • 併發:在多個cpu時間片上同時執行多個執行緒
  • 執行緒(Thread)也叫輕量級程式,是作業系統能夠進行運算排程的最小單位。執行緒自己不擁有系統資源,只擁有一點在執行中必不可少的資源,但它可與同屬一個程式的其它執行緒共享程式所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程式中的多個執行緒之間可以併發執行,程式之間不能共享記憶體,但執行緒之間可以共享記憶體

 

3.多程式的建立

3.1.程式的建立模組-multiprocessing

 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 def work_a():
 6     for i in range(3):
 7         print(i,"a",os.getpid())    #os.getpid()獲取程式id
 8         time.sleep(1)
 9 
10 def work_b():
11     for i in range(3):
12         print(i,"b",os.getpid())
13         time.sleep(1)
14 
15 if __name__=='__main__':
16     start=time.time()
17     work_a()
18     work_b()
19     end=time.time()-start
20     print(end)
21     print('parent pid is %s' % os.getpid())
22 '''
23 0 a 36436
24 1 a 36436
25 2 a 36436
26 0 b 36436
27 1 b 36436
28 2 b 36436
29 6.002437114715576
30 parent pid is 36436 所有的程式id跟主程式一致,都是36436
31 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #建立程式
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()獲取程式id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     a_p=multiprocessing.Process(target=work_a) #將work_a()方法單獨作為一個程式執行,由於work_a()方法沒有引數,因此args為None,也可以不填
22     a_p.start() #啟動work_a()程式
23     work_b()
24     end=time.time()-start
25     print(end)
26     print('parent pid is %s' % os.getpid())
27 '''
28 0 b 35196
29 0 a 24640
30 1 b 35196
31 1 a 24640
32 2 b 35196
33 2 a 24640
34 3.0127575397491455
35 parent pid is 35196
36 work_a()單獨用了一個程式執行,work_b()依舊與主執行緒用的同一個程式
37 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #多個程式執行
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()獲取程式id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主程式
21     a_p=multiprocessing.Process(target=work_a)  #子程式1
22     b_p=multiprocessing.Process(target=work_b)  #子程式2
23     end=time.time()-start   #主程式
24     print(end)  #主程式
25     print('parent pid is %s' % os.getpid()) #主程式
26     for i in (a_p,b_p):
27         i.start()
28 '''
29 0.0
30 parent pid is 19692
31 0 a 35408
32 0 b 34000
33 1 a 35408
34 1 b 34000
35 2 a 35408
36 2 b 34000
37 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #join方法使用
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()獲取程式id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主程式
21     a_p=multiprocessing.Process(target=work_a)  #子程式1
22     b_p=multiprocessing.Process(target=work_b)  #子程式2
23     for i in (a_p,b_p):
24         i.start()
25         i.join()
26     end=time.time()-start   #主程式
27     print(end)  #主程式
28     print('parent pid is %s' % os.getpid()) #主程式
29 '''
30 0 a 35816
31 1 a 35816
32 2 a 35816
33 0 b 35172
34 1 b 35172
35 2 b 35172
36 6.118025302886963
37 parent pid is 14624
38 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #kill方法使用
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()獲取程式id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主程式
21     a_p=multiprocessing.Process(target=work_a)  #子程式1
22     b_p=multiprocessing.Process(target=work_b)  #子程式2
23     for i in (a_p,b_p):
24         i.start()
25         if i==b_p:
26             i.kill()
27     end=time.time()-start   #主程式
28     print(end)  #主程式
29     print('parent pid is %s' % os.getpid()) #主程式
30 '''
31 0.016000986099243164
32 parent pid is 35268
33 0 a 34584
34 1 a 34584
35 2 a 34584
36 '''

3.2.程式的問題

  • 通過程式模組執行的函式無法獲取返回值 

  • 多個程式同時修改檔案,可能會出現錯誤

  • 程式太多會導致資源不足


3.3.多程式總結

一、當多個程式執行時,可能會出現的問題及解決方案

  • 通過程式模組執行的函式無法獲取返回值--程式間如何通訊:通過佇列
  • 多個程式同時修改檔案可能會出現錯誤--程式間如何避免資源搶佔:建立程式鎖
  • 程式數量太多可能會造成資源不足 甚至當機等情況--如何避免建立程式數量過多:建立程式池

二、什麼是佇列

佇列是一種資料儲存結構,它的資料儲存特點類似於排隊,先進入佇列的會先出來,後進入佇列的 後出來,因此它的資料只要通過put()放入,get()取出即可,不需要安排取哪些資料程式的資料可放 入佇列,哪些程式需要,從佇列中通過取出,即可使用。

三、如何建立佇列

  • 使用的模組:  queue
  • 建立的方法:  queue. Queue(…)

 

4.程式池與程式鎖

4.1.什麼是程式池


4.2.程式池的建立

程式池的join函式一般伴隨著close函式

 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 def work(count):
 8     print(count,os.getpid())
 9     time.sleep(5)
10 
11 
12 if __name__=='__main__':
13     pool=multiprocessing.Pool(5)    #建立程式池,裡面有5個程式
14     for i in range(20):
15         pool.apply_async(func=work,args=(i,))   #程式池和程式物件process建立物件的時候裡面的引數是元組型別的,單個引數時,結尾使用,逗號標明是元組型別
16 
17     #程式池屬於主程式裡面的子程式,主程式結束,pycharm就退出了
18     #因此需要使用等待或者pool.close等方法
19     pool.close()
20     pool.join()
21     #為什麼是先關閉程式池再等待呢?
22     #pool.close()是關閉程式池,使其不在接受新的任務。防止任何更多的任務被提交到池中。 一旦完成所有任務,工作程式將退出。需要在join之前呼叫;
23     #可以這樣理解,這裡的程式池就相當於一輛大巴車,而close相當於大巴車不準上客,join大巴車上的乘客(子程式)都下車之後,大巴車就停運了
24 '''
25 0 10092
26 1 19704
27 2 36164
28 3 37212
29 4 34628
30 5 10092
31 6 19704
32 7 36164
33 8 37212
34 9 34628
35 10 10092
36 11 19704
37 12 36164
38 13 37212
39 14 34628
40 15 10092
41 16 19704
42 17 36164
43 18 37212
44 19 34628
45 '''
 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 #通過程式池可以獲取返回值
 8 def work(count):
 9     print(count,os.getpid())
10     time.sleep(5)
11     return 'result is %s ,pid is %s' % (count,os.getpid())
12 
13 
14 if __name__=='__main__':
15     pool=multiprocessing.Pool(5)    #建立程式池,裡面有5個程式
16     results=[]
17     for i in range(20):
18         result=pool.apply_async(func=work,args=(i,))   #程式池和程式物件process建立物件的時候裡面的引數是元組型別的,單個引數時,結尾使用,逗號標明是元組型別
19         results.append(result)
20 
21     for res in results:
22         print(res.get())
23 
24 '''
25 0 10092
26 1 19704
27 2 36164
28 3 37212
29 4 34628
30 5 10092
31 6 19704
32 7 36164
33 8 37212
34 9 34628
35 10 10092
36 11 19704
37 12 36164
38 13 37212
39 14 34628
40 15 10092
41 16 19704
42 17 36164
43 18 37212
44 19 34628
45 '''

4.3.程式鎖


4.4.程式鎖的用法

 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 #程式鎖
 8 def work(count,lock):
 9     lock.acquire()
10     print(count,os.getpid())
11     time.sleep(1)
12     lock.release()
13     return 'result is %s ,pid is %s' % (count,os.getpid())
14 
15 
16 if __name__=='__main__':
17     pool=multiprocessing.Pool(5)
18     manger=multiprocessing.Manager()
19     lock = manger.Lock()
20     results=[]
21     for i in range(20):
22         result=pool.apply_async(func=work,args=(i,lock))
23 
24     pool.close()
25     pool.join()

 

5.程式間的通訊

5.1.什麼是程式的通訊

使用佇列進行程式之間的通訊


5.2.程式通訊的方法

 1 # coding:utf-8
 2 import json
 3 import multiprocessing
 4 
 5 class Work(object):
 6     def __init__(self,q):
 7         self.q=q
 8 
 9     def send(self,message):
10         if not isinstance(message,str):
11             message=json.dumps(message)
12         self.q.put(message)
13 
14     def receive(self):
15         while 1:
16             result=self.q.get()
17             try:
18                 res=json.loads(result)
19             except:
20                 res = result
21             print('recv is %s' % res)
22 
23 if __name__=='__main__':
24     q=multiprocessing.Queue()
25     work=Work(q)
26     send=multiprocessing.Process(target=work.send,args=({'name':'zhangsan'},))
27     recv=multiprocessing.Process(target=work.receive)
28     send.start()
29     recv.start()
30 
31     send.join()
32     recv.terminate()    #強行終止
 1 # coding:utf-8
 2 import json
 3 import multiprocessing
 4 import time
 5 
 6 
 7 class Work(object):
 8     def __init__(self,q):
 9         self.q=q
10 
11     def send(self,message):
12         if not isinstance(message,str):
13             message=json.dumps(message)
14         self.q.put(message)
15 
16     def send_all(self):
17         for i in range(10):
18             self.q.put(i)
19             time.sleep(1)
20 
21     def receive(self):
22         while 1:
23             result=self.q.get()
24             try:
25                 res=json.loads(result)
26             except:
27                 res = result
28             print('recv is %s' % res)
29 
30 if __name__=='__main__':
31     q=multiprocessing.Queue()
32     work=Work(q)
33     send=multiprocessing.Process(target=work.send,args=({'name':'zhangsan'},))
34     recv=multiprocessing.Process(target=work.receive)
35     send_all_p=multiprocessing.Process(target=work.send_all)
36     send.start()
37     recv.start()
38     send_all_p.start()
39 
40     send_all_p.join()
41     recv.terminate()    #強行終止

 

6.執行緒的建立

6.1.執行緒的建立

 1 # coding:utf-8
 2 
 3 import random
 4 import time
 5 
 6 lists=['python','django','tornado','flask','bs5','requests','uvloop']
 7 
 8 new_lists=[]
 9 
10 def work():
11     if len(lists) == 0:
12         return
13     data=random.choice(lists)
14     lists.remove(data)
15     new_data='%s_new' % data
16     new_lists.append(new_data)
17     time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     for i in range(len(lists)):
22         work()
23     print('old list',lists)
24     print('new list',new_lists)
25     print('time is %s' % (time.time()-start))
26 '''
27 old list []
28 new list ['uvloop_new', 'requests_new', 'django_new', 'bs5_new', 'flask_new', 'python_new', 'tornado_new']
29 time is 7.003076553344727
30 '''
 1 # coding:utf-8
 2 
 3 import random
 4 import time
 5 import threading
 6 lists=['python','django','tornado','flask','bs5','requests','uvloop']
 7 
 8 new_lists=[]
 9 
10 def work():
11     if len(lists) == 0:
12         return
13     data=random.choice(lists)
14     lists.remove(data)
15     new_data='%s_new' % data
16     new_lists.append(new_data)
17     time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     t_list=[]
22     for i in range(len(lists)):
23         t=threading.Thread(target=work)
24         t_list.append(t)
25         t.start()
26 
27     for t in t_list:
28         t.join()
29     print('old list',lists)
30     print('new list',new_lists)
31     print('time is %s' % (time.time()-start))
32 '''
33 old list []
34 new list ['requests_new', 'tornado_new', 'flask_new', 'bs5_new', 'python_new', 'django_new', 'uvloop_new']
35 time is 1.0019862651824951
36 '''

6.2.執行緒的問題


6.3.執行緒建立總結

一、當多個執行緒執行時,可能會出現的問題及解決方案:

  • 通過執行緒執行的函式無法獲取返回值——執行緒間如何通訊:通過佇列
  • 多個執行緒同時修改檔案可能造成資料錯錯亂——執行緒間如何避免資源搶佔:建立執行緒鎖
  • 執行緒數量太多可能會造成資源不足,甚至當機等情況——如何避免建立執行緒數量過多:建立執行緒池

二、通過佇列通訊來解決

 

三、建立執行緒鎖:

線上程程式碼中需要加上鎖的地方寫上加鎖程式碼,要釋放鎖的地方寫解鎖程式碼即可

  • 使用模組:threading
  • 加鎖:threading.Lock().acquire()
  • 解鎖:threading.Lock().release()

四、建立執行緒池:

首先寫出建立執行緒池的方法,之後往執行緒池中放入執行緒即可

  • 使用模組:concurrent.futures
  • 建立方法:concurrent.futures.ThreadPoolExecutor()

 

7.執行緒池

執行緒池的建立與使用方法,執行緒池不像程式池需要close(),而後join()

 1 # coding:utf-8
 2 import time
 3 from concurrent.futures import ThreadPoolExecutor
 4 
 5 def work(i):
 6     print(i)
 7     time.sleep(1)
 8 
 9 if __name__=="__main__":
10     t=ThreadPoolExecutor(2)
11     for i in range(10):
12         t.submit(work,i)
13         
14 '''
15 0
16 1
17 23
18 
19 4
20 5
21 6
22 7
23 8
24 9
25 '''
 1 # coding:utf-8
 2 import threading
 3 import time
 4 from concurrent.futures import ThreadPoolExecutor
 5 
 6 #執行緒鎖和程式鎖的區別:程式鎖需要將鎖傳到方法中,而執行緒鎖只需要定義一個全域性鎖,直接呼叫即可
 7 lock=threading.Lock()
 8 
 9 def work(i):
10     lock.acquire()
11     print(i)
12     time.sleep(1)
13     lock.release()
14 
15 if __name__=="__main__":
16     t=ThreadPoolExecutor(2)
17     for i in range(10):
18         t.submit(work,i)
19 
20 '''
21 0
22 1
23 2
24 3
25 4
26 5
27 6
28 7
29 8
30 9
31 '''
 1 # coding:utf-8
 2 import os
 3 import threading
 4 import time
 5 from concurrent.futures import ThreadPoolExecutor
 6 
 7 #執行緒池和程式池一樣可以獲得執行緒的返回值,多執行緒的os.getpid和當前程式的一致,用的同一個,因為執行緒池就是在當前程式下執行的
 8 
 9 def work(i):
10     time.sleep(1)
11     return 'result %s' % i
12 
13 if __name__=="__main__":
14     print(os.getpid())      #57764
15     t=ThreadPoolExecutor(2)
16     list_1=[]
17     for i in range(10):
18         result=t.submit(work,i)
19         list_1.append(result)
20     print(list_1,type(list_1))   #[<Future at 0x2bfbe0bc748 state=running>, <Future at 0x2bfbe2fbd08 state=running>, <Future at 0x2bfbe301808 state=pending>, <Future at 0x2bfbe301908 state=pending>, <Future at 0x2bfbe3019c8 state=pending>, <Future at 0x2bfbe301b48 state=pending>, <Future at 0x2bfbe301cc8 state=pending>, <Future at 0x2bfbe301e88 state=pending>, <Future at 0x2bfbe30c048 state=pending>, <Future at 0x2bfbe301b08 state=pending>] <class 'list'>
21 
22     for i in list_1:
23         print(i.result())
24 '''
25 result 0
26 result 1
27 result 2
28 result 3
29 result 4
30 result 5
31 result 6
32 result 7
33 result 8
34 result 9
35 '''

 

8.全域性鎖

 一、什麼是全域性鎖:

GIL是全域性直譯器鎖,這個GIL並不是python的特性,他是在Cpython直譯器裡引入的一個概念,而 在其他的語言編寫的直譯器裡就沒有這個GIL 

二、全域性鎖是主要的作用 :

因為多執行緒的程式設計方式,使得執行緒之間資料的一致性和狀態同步難以把控,為了解決資料不能同步 的問題,設計了GIL全域性直譯器鎖。

三、全域性鎖是如何發揮作用的 :

在Cpython直譯器中,當python程式碼有一個執行緒開始訪問直譯器的時候,GIL會把這個執行緒給鎖上, 此時此刻其他的執行緒只能乾等著,無法對直譯器的資源進行訪問,需要等這個執行緒分配的時間到 了,這個執行緒把GIL釋放掉,另外的執行緒才開始跑起來,其實這無疑也是一個單執行緒。這類似於給 執行緒加鎖 threading.Lock().acquire() ,解鎖 threading.Lock().release() 一樣。

python有全域性鎖gil,多執行緒只能在單一cpu工作。可以用多程式+多執行緒配合使用

 

 

 

9.非同步

9.1.什麼是非同步與非同步的好處


9.2.非同步與多執行緒多程式


9.3.async、await與asyncio模組的使用

 1 # coding:utf-8
 2 import random
 3 import time
 4 
 5 
 6 def a():
 7     for i in range(10):
 8         print(i,'a')
 9         time.sleep(random.random()*2)
10     return 'a function'
11 
12 def b():
13     for i in range(10):
14         print(i,'b')
15         time.sleep(random.random()*2)
16     return 'b function'
17 
18 
19 if __name__=='__main__':
20     start=time.time()
21     a()
22     b()
23     print(time.time()-start)
24 '''
25 0 a
26 1 a
27 2 a
28 3 a
29 4 a
30 5 a
31 6 a
32 7 a
33 8 a
34 9 a
35 0 b
36 1 b
37 2 b
38 3 b
39 4 b
40 5 b
41 6 b
42 7 b
43 8 b
44 9 b
45 19.68557095527649
46 '''
 1 # coding:utf-8
 2 import asyncio
 3 import random
 4 import time
 5 
 6 
 7 async def a():
 8     for i in range(10):
 9         print(i,'a')
10         await asyncio.sleep(random.random()*2)
11     return 'a function'
12 
13 async def b():
14     for i in range(10):
15         print(i,'b')
16         await asyncio.sleep(random.random()*2)
17     return 'b function'
18 
19 async def main():
20     result=await asyncio.gather(
21         a(),
22         b()
23     )
24     print(result)
25 
26 if __name__=='__main__':
27     start=time.time()
28     asyncio.run(main())
29     print(time.time()-start)
30 '''
31 0 a
32 0 b
33 1 b
34 2 b
35 1 a
36 3 b
37 2 a
38 3 a
39 4 b
40 4 a
41 5 a
42 5 b
43 6 a
44 7 a
45 6 b
46 8 a
47 7 b
48 9 a
49 8 b
50 9 b
51 ['a function', 'b function']
52 10.612190246582031
53 '''
 1 # coding:utf-8
 2 import asyncio
 3 import random
 4 import time
 5 
 6 
 7 async def a():
 8     for i in range(10):
 9         print(i,'a')
10         await asyncio.sleep(random.random()*2)
11     return 'a function'
12 
13 async def b():
14     for i in range(10):
15         print(i,'b')
16         await asyncio.sleep(random.random()*2)
17     return 'b function'
18 
19 async def main():
20     result=await asyncio.gather(
21         a(),
22         b()
23     )
24     print(result)
25 
26 if __name__=='__main__':
27     start=time.time()
28     # asyncio.run(main())
29     asyncio.run(b())
30     print(time.time()-start)
31 '''
32 0 b
33 1 b
34 2 b
35 3 b
36 4 b
37 5 b
38 6 b
39 7 b
40 8 b
41 9 b
42 6.932542085647583
43 '''
 1 # coding:utf-8
 2 import asyncio
 3 import os
 4 import random
 5 import time
 6 
 7 
 8 async def a():
 9     for i in range(10):
10         print(i,'a',os.getpid())
11         await asyncio.sleep(random.random()*2)
12     return 'a function'
13 
14 async def b():
15     for i in range(10):
16         print(i,'b',os.getpid())
17         await asyncio.sleep(random.random()*2)
18     return 'b function'
19 
20 async def main():
21     result=await asyncio.gather(
22         a(),
23         b()
24     )
25     print(result)
26 
27 if __name__=='__main__':
28     start=time.time()
29     asyncio.run(main())
30     print(time.time()-start)
31     print('parent is %s' % os.getpid())#非同步相當於執行緒的存在,用的都是同一個程式id
32 '''
33 0 a 96060
34 0 b 96060
35 1 b 96060
36 2 b 96060
37 1 a 96060
38 3 b 96060
39 4 b 96060
40 2 a 96060
41 5 b 96060
42 6 b 96060
43 3 a 96060
44 4 a 96060
45 7 b 96060
46 8 b 96060
47 5 a 96060
48 6 a 96060
49 7 a 96060
50 9 b 96060
51 8 a 96060
52 9 a 96060
53 ['a function', 'b function']
54 8.424062490463257
55 parent is 96060
56 
57 Process finished with exit code 0
58 
59 '''

9.4.gevent非同步模組的使用

 1 # coding:utf-8
 2 import os
 3 import random
 4 import time
 5 import gevent
 6 
 7 def gevent_a():
 8     for i in range(5):
 9         print(i,'a',os.getpid())
10         gevent.sleep(random.random()*2)
11     return 'a function'
12 
13 def gevent_b():
14     for i in range(5):
15         print(i,'b',os.getpid())
16         gevent.sleep(random.random()*2)
17     return 'b function'
18 
19 if __name__=='__main__':
20     start=time.time()
21     g_a=gevent.spawn(gevent_a)
22     g_a.run()
23     print(time.time()-start)
24     print('parent is %s' % os.getpid())#非同步相當於執行緒的存在,用的都是同一個程式id
25 '''
26 0 a 99784
27 1 a 99784
28 2 a 99784
29 3 a 99784
30 4 a 99784
31 6.335636615753174
32 parent is 99784
33 '''
 1 # coding:utf-8
 2 import os
 3 import random
 4 import time
 5 import gevent
 6 
 7 def gevent_a():
 8     for i in range(5):
 9         print(i,'a',os.getpid())
10         gevent.sleep(random.random()*2)
11     return 'a function'
12 
13 def gevent_b():
14     for i in range(5):
15         print(i,'b',os.getpid())
16         gevent.sleep(random.random()*2)
17     return 'b function'
18 
19 if __name__=='__main__':
20     start=time.time()
21     g_a=gevent.spawn(gevent_a)
22     g_b=gevent.spawn(gevent_b)
23     gevent_list=[g_a,g_b]
24     result=gevent.joinall(gevent_list)
25     print(result)
26     print(result[0].value)
27     print(time.time()-start)
28     print('parent is %s' % os.getpid())#非同步相當於執行緒的存在,用的都是同一個程式id
29 '''
30 0 a 99840
31 0 b 99840
32 1 b 99840
33 2 b 99840
34 1 a 99840
35 3 b 99840
36 2 a 99840
37 4 b 99840
38 3 a 99840
39 4 a 99840
40 [<Greenlet at 0x20ec1afcee8: _run>, <Greenlet at 0x20ec1afcca8: _run>]
41 b function
42 8.296916961669922
43 parent is 99840
44 '''

9.5.非同步總結

非同步相對同步而言,非同步意味著無序,而同步意味著有序。正因為非同步的無序,使得各個程式間的協調成為一大難題。由此,非同步程式設計應運而生。它是以程式,執行緒,協程,函式/方法作為執行程式的基本單位,結合回撥,事件迴圈,訊號量等機制,以提高程式整體執行效率和併發能力的一種程式設計方式。

在python中,如何實現非同步?

方法一:

  • 如何定義一個非同步: async
  • 如何在一個非同步程式中,呼叫另外一個非同步,使用關鍵字 await
  • 非非同步程式,如何呼叫非同步函式: asyncio

方法二:通過非同步包gevent

  • 如何建立協程即非同步物件:spwan 函式
  • 如何批量處理協程物件:joinall

 

10.多執行緒多程式面試常問知識點

談談你對程式,執行緒,協程的理解程式:

  • 一個執行的程式(程式碼)就是一個程式,沒有執行的程式碼叫程式,程式是系統資源分配的最小單位,程式擁有自己獨立的記憶體空間,所有程式間資料不共享,開銷大。程式的狀態有:新建態,就緒態,執行態,阻塞態,終止態
  • 執行緒: cpu排程執行的最小單位,也叫執行路徑,不能獨立存在,依賴程式存在,一個程式至少有一個線 程,叫主執行緒,而多個執行緒共享記憶體(資料共享,共享全域性變數),從而極大地提高了程式的執行效率。
  • 協程: 是一種使用者態的輕量級執行緒,協程的排程完全由使用者控制。協程擁有自己的暫存器上下文和棧。 協程排程時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和 棧,直接操中棧則基本沒有核心切換的開銷,可以不加鎖的訪問全域性變數,所以上下文的切換非常快。

什麼是多執行緒競爭

  • 執行緒是非獨立的,同一個程式裡執行緒是資料共享的,當各個執行緒訪問資料資源時會出現競爭狀態,即:資料幾乎同步會被多個執行緒佔用,造成資料混亂,即所謂的執行緒不安全 那麼怎麼解決多執行緒競爭問題?—鎖
  • 鎖的好處: 確保了某段關鍵程式碼(共享資料資源)只能由一個執行緒從頭到尾完整地執行能解決多執行緒資 源競爭下的原子操作問題。
  • 鎖的壞處: 阻止了多執行緒併發執行,包含鎖的某段程式碼實際上只能以單執行緒模式執行,效率就大大地下 降了
  • 鎖的致命問題: 死鎖

解釋一下什麼是鎖,什麼是死鎖

  • 鎖:當一個執行緒去訪問某一個資源的時候,對這個資源上鎖,其他執行緒就無法對這個資源進行訪問,等這個執行緒處理完成了,對鎖進行釋放,其他執行緒再訪問這個資源
  • 死鎖:當因為某些原因,比如鎖無法釋放,迴圈等待資源釋放的時候,需要該資源的執行緒一直無法執行,就進入死鎖狀態

什麼是執行緒安全,什麼是互斥鎖

  • 互斥鎖是一種獨佔鎖,同一時刻只有一個執行緒可以訪問共享的資料。每個物件都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問該物件。
  • 同一個程式中的多執行緒之間是共享系統資源的,多個執行緒同時對一個物件進行操作,一個執行緒操作尚未結束,另一個執行緒已經對其進行操作,導致最終結果出現錯誤,此時需要對被操作物件新增互斥鎖,保證每個執行緒對該物件的操作都得到正確的結果。

python中程式和執行緒的使用場景

  • 多程式適合在CPU密集操作,業務處理(cpu操作指令比較多,如位多的的浮點運算)。
  • 多執行緒適合在IO密性型操作(讀寫資料操作比多的的,比如爬蟲)

 

11.擴充

Python程式與執行緒的概念及區別 :

  • 程式可以被稱為執行的程式,一個程式擁有完整的資料空間和程式碼空間,每一個程式的地址空 間都是獨立的,程式之間不能共享資料。 
  • 執行緒是程式的一個實體, 是CPU排程和分派的基本單位,它是比程式更小的能獨立執行的基本 單位。執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源,但是它可與同屬一 個程式的其他的執行緒共享程式所擁有的全部資源。
  • 通俗的講一條流水線的執行過程是一個執行緒,一條流水線必須屬於一個車間,一個車間的執行 過程就是一個程式。

 二者之間的關係:

 一個程式至少有一個程式,一個程式至少有一個執行緒 

區別:

  • 程式有獨立的地址空間,多程式較穩定,因為其中一個出現狀況不影響另外一個;同一個程式的多個執行緒,共用地址空間,多執行緒相比於多程式,穩定性要差,因為一個執行緒出現問題會嚴 重影響其他執行緒。 
  • 程式之間需要共享資料,要利用程式間通訊;同一個程式中的執行緒不需要。 
  • 程式只是資源分配的最小單位;執行緒是執行的最小單位,也就是說實際執行的是執行緒。 

CPU密集型和IO密集型:

  • CPU密集型程式碼(各種迴圈處理、計數等等) 
  • IO密集型程式碼(檔案處理、網路爬蟲等) 

執行緒與程式誰更快: 

因為python鎖的問題,執行緒進行鎖競爭、切換執行緒,會消耗資源。在CPU密集型任務下,多程式更快或者說效果更好;而IO密集型,多執行緒能有效提高效率。

相關文章