併發編程練習題
1、簡述計算機操作系統中的“中斷”的作用?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 計算機操作系統的中斷的作用:cpu會切:io阻塞,程序運行時間過長 中斷:計算機執行期間,系統內發生任何非尋常的或非預期的急需處理事件,使得 cpu暫時中斷當前正在執行的程序而轉去執行相應的事件處理程序。 待處理完畢後又返回原來被中斷處理急需執行或者調度新的進程執行的過程,它使計算 機可以更好更快的利用有限的系統資源解決系統響應速度和運行效率的一種控制技術: 實時響應 + 系統調用 中斷裝置是由一些特定的寄存器和控制線路組成,中央處理器和外圍設備等識別到的 事件保存在特定的寄存器中。 中央處理器每執行完一條指令,均有中斷裝置判別是否有事件發生。 若無事件發生,CPU繼續執行。 若有事件發生,則中斷裝置中斷原佔有CPU的程序的執行,讓操作系統的處理事件服 務程序佔用CPU,對出現的事件進行處理,事件處理完後,再讓原來的程序繼續佔用CPU執行 |
2、簡述計算機內存中的“內核態”和“用戶態”;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 操作系統的核心是內核,獨立於普通的應用程序,內核可以訪問受保護的內存空間, 也可以訪問底層硬件設備的所有權限,爲了保證用戶進程不能直接操作內核,保證內核 的安全,操作系統將虛擬空間劃分爲兩部分,一部分是內核空間,一部分是用戶空間。 內核態:運行操作系統的程序,os的數據存放 用戶態:運行用戶程序,用戶進程的數據存放 用戶態的應用程序可以通過三種方式來訪問內核態的資源: 1 )系統調用 2 )庫函數 3 )Shell腳本 用戶態到內核態的切換: 1. 系統調用 用戶程序主動發起的 軟中斷 os.fork() process 2. 異常 被動的 當CPU正在執行運行在用戶態的程序時,突然發生某些預 先不可知的異常事件,這個時候就會觸發從當前用戶態執行的進程轉向內核態執行相關的 異常事件,典型的如缺頁異常。 3. 外圍設備的硬中斷 被動的 外圍設備完成用戶的請求操作後,會像CPU發出中斷信號, 此時,CPU就會暫停執行下一條即將要執行的指令,轉而去執行中斷信號對應的處理程序, 如果先前執行的指令是在用戶態下,則自然就發生從用戶態到內核態的轉換。 參考:https: / / www.cnblogs.com / bakari / p / 5520860.html https: / / blog.csdn.net / qq_34228570 / article / details / 72995997 |
3、進程間通信方式有哪些?
1 2 3 4 5 6 7 | 進程間通信(IPC) 消息隊列( 隊列 = 管道 + 鎖) 管道(使用消息傳遞的) 有名管道(FIFO) 信號量 共享內存 套接字(socket) |
4、簡述你對管道、隊列的理解;
1 2 3 4 5 6 7 8 9 10 | 管道通常指無名管道 1 、它是半雙工的(即數據只能在一個方向上流動),具有固定的讀端和寫端 2 、它只能用於具有親緣關係的進程中通信(也就是父與子進程或者兄弟進程之間) 3 、數據不可反覆讀取了,即讀了之後歡喜紅區中就沒有了 消息隊列 1 、消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級 2 、消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容不會被刪除。 3 、消息隊列可以實現消息隨機查詢。 隊列 = 管道 + 鎖 |
5、請列舉你知道的進程間通信方式;
1 | 隊列,信號量,Event事件,定時器Timer,線程queue,進程池線程池,異步調用 + 回調機制 |
6、什麼是同步I/O,什麼是異步I/O?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 同步IO指的是同步傳輸 ,當發送一個數據請求時,會一直等待,直到有返回結果爲止 異步IO指的是異步傳輸 ,當發送一個數據請求時,會立即去處理別的事情,當有數據 處理完畢後,會自動的返回結果 一般同步傳輸能保證數據正確性 ,而異步能最大化性能。 如給u盤複製一個大的數據文件,你開了緩衝優化,是異步 工作, 複製的快了, 你要是剛複製完了直接拔 會丟數據, 你要是關了,複製的慢了,但你要是關了緩衝優化,複製完了直接拔 不會丟數據, 異步IO 用戶進程發起read操作之後,立刻就可以開始去做其它的事。而另一方 面,從kernel的角度,當它受到一個asynchronous read之後,首先它會 立刻返回,所以不會對用戶進程產生任何block。然後,kernel會等待數據 準備完成,然後將數據拷貝到用戶內存,當這一切都完成之後,kernel會給 用戶進程發送一個signal,告訴它read操作完成了。 |
7、請問multiprocessing模塊中的Value、Array類的作用是什麼?舉例說明它們的使用場景
通常,進程之間彼此是完全孤立的,唯一的通信方式是隊列或者管道,但是可以使用兩個對象來表示共享數據。其實這些對象使用了共享內存(通過mmap模塊)使訪問多個進程成爲可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Value( typecode, arg1, … argN, lock ) 在共享內容中常見ctypes對象。typecode要麼是包含array模塊使用的相同類型代碼 (如’i’,’d’等)的字符串,要麼是來自ctypes模塊的類型對象(如ctypes.c_int、 ctypes.c_double等)。 所有額外的位置參數arg1, arg2 ….. argN將傳遞給指定類型的構造函數。lock是隻能 使用關鍵字調用的參數,如果把它置爲 True (默認值),將創建一個新的鎖定來包含對值的訪問。 如果傳入一個現有鎖定,比如Lock或RLock實例,該鎖定將用於進行同步。如果v是Value創建 的共享值的實例,便可使用v.value訪問底層的值。例如,讀取v.value將獲取值,而賦值v.value 將修改值。 RawValue( typecode, arg1, … ,argN) 同Value對象,但不存在鎖定。 Array( typecode, initializer, lock ) 在共享內存中創建ctypes數組。typecode描述了數組的內容,意義與Value()函數中的相同。 initializer要麼是設置數組初始大小的整數,要麼是項目序列,其值和大小用於初始化數組。 lock是隻能使用關鍵字調用的參數,意義與Value()函數中相同。 如果a是Array創建的共享數組的實例,便可使用標準的python索引、切片和迭代操作訪問它 的內容,其中每種操作均由鎖定進行同步。對於字節字符串,a還具有a.value屬性,可以吧整個 數組當做一個字符串進行訪問。 RawArray(typecode, initializer ) 同Array對象,但不存在鎖定。當所編寫的程序必須一次性操作大量的數組項時,如果同時 使用這種數據類型和用於同步的單獨鎖定(如果需要的話),性能將得到極大的提升。 |
應該注意,使用多進程後,通常不必再擔心與鎖定、信號量或類似構造的底層同步,這一點與線程不相伯仲。在某種程度上,管道上的send()和receive()操作,以及隊列上的put()和get()操作已經提供了同步功能。但是,在某寫特定的設置下還是需要用到共享值和鎖定。下面這個例子說明了如何使用共享數組代替管道,將一個浮點數的python列表發送給另一個進程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import multiprocessing class FloatChannel( object ): def __init__( self ,maxsize): self . buffer = multiprocessing.RawArray( 'd' ,maxsize) self .buffer_len = multiprocessing.Value( 'i' ) self .empty = multiprocessing.Semaphore( 1 ) self .full = multiprocessing.Semaphore( 0 ) def send( self ,values): self .empty.acquire() #只在緩存爲空時繼續 nitems = len (values) self .buffer_len = nitems #設置緩衝區大小 self . buffer [:nitems] = values #將複製到緩衝區中 self .full.release() #發信號通知緩衝區已滿 def recv( self ): self .full.acquire() #只在緩衝區已滿時繼續 values = self . buffer [: self .buffer_len.value] #複製值 self .empty.release() #發送信號通知緩衝區爲空 return values #性能測試 接收多條消息 def consume_test(count,ch): for i in xrange (count): values = ch.recv() #性能測試 發送多條消息 def produce_test(count,values,ch): for i in xrange (count): ch.send(values) if __name__ = = "__main__" : ch = FloatChannel( 100000 ) p = multiprocessing.Process(target = consume_test,args = ( 1000 ,ch)) p.start() values = [ float (x) for x in xrange ( 100000 )] produce_test( 1000 ,values,ch) print "Done" p.join() |
8、請問multiprocessing模塊中的Manager類的作用是什麼?與Value和Array類相比,Manager的優缺點是什麼?
可以通過使用Value或者Array把數據存儲在一個共享的內存表中;Manager()返回一個manager類型,控制一個server process,可以允許其它進程通過代理複製一些python objects 支持list,dict,Namespace,Lock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value,Array ;
Manager類的作用共享資源,manger的的優點是可以在poor進程池中使用,缺點是windows下環境下性能比較差,因爲windows平臺需要把Manager.list放在if name='main'下,而在實例化子進程時,必須把Manager對象傳遞給子進程,否則lists無法被共享,而這個過程會消耗巨大資源,因此性能很差。
multiprocessing 是一個使用方法類似threading模塊的進程模塊。允許程序員做並行開發。並且可以在UNIX和Windows下運行。
通過創建一個Process 類型並且通過調用call()方法spawn一個進程。
一個比較簡單的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from multiprocessing import Process import time def f(name): time.sleep( 1 ) print 'hello ' ,name print os.getppid() #取得父進程ID print os.getpid() #取得進程ID process_list = [] if __name__ = = '__main__' : for i in range ( 10 ): p = Process(target = f,args = (i,)) p.start() process_list.append(p) for j in process_list: j.join() |
進程間通信:
有兩種主要的方式:Queue、Pipe
1- Queue類幾乎就是Queue.Queue的複製,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from multiprocessing import Process,Queue import time def f(name): time.sleep( 1 ) q.put([ 'hello' + str (name)]) process_list = [] q = Queue() if __name__ = = '__main__' : for i in range ( 10 ): p = Process(target = f,args = (i,)) p.start() process_list.append(p) for j in process_list: j.join() for i in range ( 10 ): print q.get() |
2- Pipe 管道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from multiprocessing import Process,Pipe import time import os def f(conn,name): time.sleep( 1 ) conn.send([ 'hello' + str (name)]) print os.getppid(), '-----------' ,os.getpid() process_list = [] parent_conn,child_conn = Pipe() if __name__ = = '__main__' : for i in range ( 10 ): p = Process(target = f,args = (child_conn,i)) p.start() process_list.append(p) for j in process_list: j.join() for p in range ( 10 ): print parent_conn.recv() |
Pipe()返回兩個連接類,代表兩個方向。如果兩個進程在管道的兩邊同時讀或同時寫,會有可能造成corruption.
進程間同步
multiprocessing contains equivalents of all the synchronization primitives from threading.
例如,可以加一個鎖,以使某一時刻只有一個進程print
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from multiprocessing import Process,Lock import time import os def f(name): lock.acquire() time.sleep( 1 ) print 'hello--' + str (name) print os.getppid(), '-----------' ,os.getpid() lock.release() process_list = [] lock = Lock() if __name__ = = '__main__' : for i in range ( 10 ): p = Process(target = f,args = (i,)) p.start() process_list.append(p) for j in process_list: j.join() |
進程間共享狀態 Sharing state between processes
當然盡最大可能防止使用共享狀態,但最終有可能會使用到.
1-共享內存
可以通過使用Value或者Array把數據存儲在一個共享的內存表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from multiprocessing import Process,Value,Array import time import os def f(n,a,name): time.sleep( 1 ) n.value = name * name for i in range ( len (a)): a[i] = - i process_list = [] if __name__ = = '__main__' : num = Value( 'd' , 0.0 ) arr = Array( 'i' , range ( 10 )) for i in range ( 10 ): p = Process(target = f,args = (num,arr,i)) p.start() process_list.append(p) for j in process_list: j.join() print num.value print arr[:] 輸出: james@James:~ / projects$ python pp.py 81.0 [ 0 , - 1 , - 2 , - 3 , - 4 , - 5 , - 6 , - 7 , - 8 , - 9 ] |
'd'和'i'參數是num和arr用來設置類型,d表示一個雙精浮點類型,i表示一個帶符號的整型。
更加靈活的共享內存可以使用multiprocessing.sharectypes模塊
Server process
Manager()返回一個manager類型,控制一個server process,可以允許其它進程通過代理複製一些python objects
支持list,dict,Namespace,Lock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value,Array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | from multiprocessing import Process,Manager import time import os def f(d,name): time.sleep( 1 ) d[name] = name * name print d process_list = [] if __name__ = = '__main__' : manager = Manager() d = manager. dict () for i in range ( 10 ): p = Process(target = f,args = (d,i)) p.start() process_list.append(p) for j in process_list: j.join() print d 輸出結果: { 2 : 4 } { 2 : 4 , 3 : 9 } { 2 : 4 , 3 : 9 , 4 : 16 } { 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 } { 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 , 6 : 36 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 , 6 : 36 , 8 : 64 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 , 6 : 36 , 7 : 49 , 8 : 64 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 , 6 : 36 , 7 : 49 , 8 : 64 , 9 : 81 } { 0 : 0 , 1 : 1 , 2 : 4 , 3 : 9 , 4 : 16 , 5 : 25 , 6 : 36 , 7 : 49 , 8 : 64 , 9 : 81 } |
Server process managers比共享內存方法更加的靈活,一個單獨的manager可以被同一網絡的不同計算機的多個進程共享。比共享內存更加的緩慢
使用工作池 Using a pool of workers
Pool類代表 a pool of worker processes.
It has methods which allows tasks to be offloaded to the worker processes in a few different ways.
9、寫一個程序,包含十個線程,子線程必須等待主線程sleep 10秒鐘之後才執行,並打印當前時間;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # _*_ coding: utf-8 _*_ # 寫一個程序,包含十個線程,子線程必須等待主線程sleep 10秒鐘 # 之後才執行,並打印當前時間 from threading import Thread import time def task(name): print (name,time.strftime( '%Y-%m-%d %H:%M:%S' ,time.localtime())) if __name__ = = '__main__' : time.sleep( 2 ) for i in range ( 10 ): t = Thread(target = task,args = ( '線程 %s' % i,)) t.start() |
10、寫一個程序,包含十個線程,同時只能有五個子線程並行執行;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # _*_ coding: utf-8 _*_ # 10、寫一個程序,包含十個線程,同時只能有五個子線程並行執行; from threading import Thread from threading import currentThread from concurrent.futures import ThreadPoolExecutor import time import random def task(): print (currentThread().getName()) time.sleep(random.randint( 1 , 3 )) if __name__ = = '__main__' : pool = ThreadPoolExecutor( 5 ) for i in range ( 10 ): pool.submit(task) |
11、寫一個程序,要求用戶輸入用戶名和密碼,要求密碼長度不少於6個字符,且必須以字母開頭,如果密碼合法,則將該密碼使用md5算法加密後的十六進制概要值存入名爲password.txt的文件,超過三次不合法則退出程序;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # _*_ coding: utf-8 _*_ # 11、寫一個程序,要求用戶輸入用戶名和密碼,要求密碼 # 長度不少於6個字符,且必須以字母開頭,如果密碼合法, # 則將該密碼使用md5算法加密後的十六進制概要值存入名 # 爲password.txt的文件,超過三次不合法則退出程序; import re import hashlib import pickle def func(): count = 0 while count< 3 : username = input ( "username>>>" ).strip() password = input ( "password>>>" ).strip() if len (password) > = 6 and re.search( '\A([a-z)|[A-Z])' ,password): md5_password = hashlib.md5(password.encode( 'utf-8' )).hexdigest() file_obj = { 'username' :username, 'passworf' :md5_password} f = open ( 'password.txt' , 'ab' ) pickle.dump(file_obj,f) break else : print ( "請輸入合法的密碼" ) count + = 1 else : print ( "您的機會已經用完" ) if __name__ = = '__main__' : func() |
12、寫一個程序,使用socketserver模塊,實現一個支持同時處理多個客戶端請求的服務器,要求每次啓動一個新線程處理客戶端請求;
服務端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # _*_ coding: utf-8 _*_ # 12、寫一個程序,使用socketserver模塊, # 實現一個支持同時處理多個客戶端請求的服務器, # 要求每次啓動一個新線程處理客戶端請求; import socketserver class Handler(socketserver.BaseRequestHandler): def handle( self ): while True : try : data = self .request.recv( 1024 ) if not data: break print ( 'client data' ,data.decode()) self .request.send(data.upper()) except Exception as e: print (e) break if __name__ = = '__main__' : server = socketserver.ThreadingTCPServer(( '127.0.0.1' , 8888 ),Handler) server.serve_forever() |
客戶端
1 2 3 4 5 6 7 8 9 10 11 12 | # _*_ coding: utf-8 _*_ import socket ip_port = ( '127.0.0.1' , 8888 ) client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(ip_port) while True : msg = input ( ">>>>" ).strip() if not msg: continue client.send(msg.encode( 'utf-8' )) data = client.recv( 1024 ) print (data) |