练习题|并发编程

时间:2023-02-12 22:37:59

1、简述计算机操作系统中的“中断”的作用?

cpu会切:io阻塞、程序运行时间过长
中断:计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得cpu暂时中断当前正在执行的程序而转去执行相应的事件处理程序。
待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。它使计算机可以更好更快利用有限的系统资源解决系统响应速度和运行效率的一种控制技术。
实时响应 + 系统调用

 

2、简述计算机内存中的“内核态”和“用户态”;

 操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成;

内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。

用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

用户态的应用程序可以通过三种方式来访问内核态的资源:
1)系统调用
2)库函数
3)Shell脚本
用户态到内核态的切换:
1.系统调用 用户程序主动发起的 软中断 os.fork() process
2.异常 被动的 当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程
转向内核态执行相关的异常事件,典型的如缺页异常。
3.外围设备的硬中断 被动的 外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,
转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。

练习题|并发编程

详见:用户态和内核态的区别 https://blog.****.net/youngyoungla/article/details/53106671

https://blog.****.net/qq_34228570/article/details/72995997

 

3、进程间通信方式有哪些?

   进程间通信(IPC)

  1)管道

 管道分为有名管道和无名管道

无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。

有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

  2)信号量

信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.

  3)信号

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生.

  4)消息队列

消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序.

  5)共享内存

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信.

  6)套接字:可用于不同及其间的进程通信

 

4、简述你对管道、队列的理解;

  管道通常指无名管道
    1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
    2、它只能用于具有亲缘关系的进程中通信(也就是父与子进程或者兄弟进程之间)
    3、数据不可反复读取了,即读了之后欢喜红区中就没有了

  消息队列
    1、消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

    2、消息队列独立于发送与接收进程。进程终止时,消息队列及其内容不会被删除。
    3、消息队列可以实现消息随机查询。

   mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。

  队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。

  我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

 

5、请列举你知道的进程间通信方式;

  队列,信号量,Event事件,定时器Timer,线程queue,进程池线程池,异步调用+回调机制

 

6、什么是同步I/O,什么是异步I/O?

 同步I/O,用户进程需要主动读写数据。

异步I/O,不需要主动读写数据,只需要读写数据完成的通知。

 

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对象,但不存在锁定。当所编写的程序必须一次性操作大量的数组项时,如果同时使用这种数据类型和用于同步的单独锁定(如果需要的话),性能将得到极大的提升。

  详见:https://blog.****.net/winterto1990/article/details/48106505

    http://xiaorui.cc/2016/05/10/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90multiprocessing%E7%9A%84value-array%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E5%8E%9F%E7%90%86/

https://blog.****.net/qdx411324962/article/details/46810421

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无法被共享,而这个过程会消耗巨大资源,因此性能很差。

参考:http://www.kaka-ace.com/python-multiprocessing-module-2-managers-first-profile/

https://blog.****.net/alvine008/article/details/24310939

 

9、写一个程序,包含十个线程,子线程必须等待主线程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(10)
    for i in range(10):
        t = Thread(target=task, args=('线程 %s'%i, ))
        t.start()

打印:
线程 0 2018-04-24 13:37:38
线程 1 2018-04-24 13:37:38
线程 2 2018-04-24 13:37:38
线程 3 2018-04-24 13:37:38
线程 4 2018-04-24 13:37:38
线程 5 2018-04-24 13:37:38
线程 6 2018-04-24 13:37:38
线程 7 2018-04-24 13:37:38
线程 8 2018-04-24 13:37:38
线程 9 2018-04-24 13:37:38

 

10、写一个程序,包含十个线程,同时只能有五个子线程并行执行;

 ##用信号亮

from threading import Thread, Semaphore, currentThread
import time
sem = Semaphore(5)
def task():
    with sem:
        print('%s in'%currentThread().getName())
        time.sleep(2)
if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=task,  )
        t.start()

打印:一开始只有5个子进程并行执行 Thread
-1 in Thread-2 in Thread-3 in Thread-4 in Thread-5 in Thread-6 in Thread-8 in Thread-7 in Thread-9 in Thread-10 in

##用线程池

from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import os,time
def task():
    print('name:%s in pid:%s'%(currentThread().getName(), os.getpid()))
    time.sleep(2)
if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)
    for i in range(10):
        pool.submit(task, )
    pool.shutdown(wait=True)

打印:
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_0 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_1 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_2 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_3 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_4 in pid:9152


name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_1 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_0 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_2 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_3 in pid:9152
name:<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000000001E8FCF8>_4 in pid:9152

 

11、写一个程序,要求用户输入用户名和密码,要求密码长度不少于6个字符,且必须以字母开头,如果密码合法,则将该密码使用md5算法加密后的十六进制概要值存入名为password.txt的文件,超过三次不合法则退出程序;

 

import re, hashlib, json
def func():
    count = 0
    while count < 3:
        usename = input('usename:')
        password = input('password:')
        if len(password) <6 or not re.search('\A([a-z]|[A-Z])', password):
            count += 1
        else:
            obj = {'usename':usename, 'password':hashlib.md5(password.encode('utf-8')).hexdigest()}
            json.dump(obj, open('password.txt', 'a', encoding='utf-8'))
            break
if __name__ == '__main__':
    func()

 

12、写一个程序,使用socketserver模块,实现一个支持同时处理多个客户端请求的服务器,要求每次启动一个新线程处理客户端请求;

##服务端
import socketserver
class Handler(socketserver.BaseRequestHandler):
    def handle(self):
        print('connection:', self.client_address)
        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', 8080), Handler)
    server.serve_forever()

 

##客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
    msg = input('>>>:')
    if not msg:break
    client.send(msg.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode('utf-8'))