天天看點

并發程式設計練習題

1、簡述計算機作業系統中的“中斷”的作用?

計算機作業系統的中斷的作用:cpu會切:io阻塞,程式運作時間過長

    中斷:計算機執行期間,系統内發生任何非尋常的或非預期的急需處理事件,使得
cpu暫時中斷目前正在執行的程式而轉去執行相應的事件處理程式。
    
    待處理完畢後又傳回原來被中斷處理急需執行或者排程新的程序執行的過程,它使計算
機可以更好更快的利用有限的系統資源解決系統響應速度和運作效率的一種控制技術:
    實時響應 + 系統調用
    
    中斷裝置是由一些特定的寄存器和控制線路組成,中央處理器和外圍裝置等識别到的
事件儲存在特定的寄存器中。
    中央處理器每執行完一條指令,均有中斷裝置判别是否有事件發生。
    若無事件發生,CPU繼續執行。
    若有事件發生,則中斷裝置中斷原占有CPU的程式的執行,讓作業系統的處理事件服
務程式占用CPU,對出現的事件進行處理,事件處理完後,再讓原來的程式繼續占用CPU執行
      

  

2、簡述計算機記憶體中的“核心态”和“使用者态”;

作業系統的核心是核心,獨立于普通的應用程式,核心可以通路受保護的記憶體空間,
也可以通路底層硬體裝置的所有權限,為了保證使用者程序不能直接操作核心,保證核心
的安全,作業系統将虛拟空間劃分為兩部分,一部分是核心空間,一部分是使用者空間。

    核心态:運作作業系統的程式,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、程序間通信方式有哪些?

程序間通信(IPC)
    消息隊列(    隊列 = 管道 + 鎖)
    管道(使用消息傳遞的)
    有名管道(FIFO)
    信号量
    共享記憶體
    套接字(socket)
      

4、簡述你對管道、隊列的了解;

管道通常指無名管道
1、它是半雙工的(即資料隻能在一個方向上流動),具有固定的讀端和寫端
2、它隻能用于具有親緣關系的程序中通信(也就是父與子程序或者兄弟程序之間)
3、資料不可反複讀取了,即讀了之後歡喜紅區中就沒有了
消息隊列
1、消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級
2、消息隊列獨立于發送與接收程序。程序終止時,消息隊列及其内容不會被删除。
3、消息隊列可以實作消息随機查詢。

    隊列 = 管道 + 鎖
      

5、請列舉你知道的程序間通信方式;

隊列,信号量,Event事件,定時器Timer,線程queue,程序池線程池,異步調用+回調機制
      

6、什麼是同步I/O,什麼是異步I/O?

同步IO指的是同步傳輸 ,當發送一個資料請求時,會一直等待,直到有傳回結果為止

        異步IO指的是異步傳輸 ,當發送一個資料請求時,會立即去處理别的事情,當有資料
處理完畢後,會自動的傳回結果
 
    一般同步傳輸能保證資料正确性 ,而異步能最大化性能。 
    如給u盤複制一個大的資料檔案,你開了緩沖優化,是異步 工作, 複制的快了, 
    你要是剛複制完了直接拔 會丢資料, 
     你要是關了,複制的慢了,但你要是關了緩沖優化,複制完了直接拔 不會丢資料,

異步IO
    使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方
面,從kernel的角度,當它受到一個asynchronous read之後,首先它會
立刻傳回,是以不會對使用者程序産生任何block。然後,kernel會等待資料
準備完成,然後将資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給
使用者程序發送一個signal,告訴它read操作完成了。    
      

7、請問multiprocessing子產品中的Value、Array類的作用是什麼?舉例說明它們的使用場景

  通常,程序之間彼此是完全孤立的,唯一的通信方式是隊列或者管道,但是可以使用兩個對象來表示共享資料。其實這些對象使用了共享記憶體(通過mmap子產品)使通路多個程序成為可能。

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清單發送給另一個程序:

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一個程序。

一個比較簡單的例子:

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的複制,示例:

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 管道

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

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把資料存儲在一個共享的記憶體表中

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

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秒鐘之後才執行,并列印目前時間;

# _*_ 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、寫一個程式,包含十個線程,同時隻能有五個子線程并行執行;

# _*_ 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的檔案,超過三次不合法則退出程式;

# _*_ 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子產品,實作一個支援同時處理多個用戶端請求的伺服器,要求每次啟動一個新線程處理用戶端請求;

 服務端:

# _*_ 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()
      

用戶端

# _*_ 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)
      

不經一番徹骨寒 怎得梅花撲鼻香

繼續閱讀