CSS Ribbon

Reproducing the GitHub Ribbon in CSS

路飞学城Python-Day39(第四模块复习题)

并发编程
一、简答题
1,简述计算机操作系统的中断的作用
由于cpu本身一次只能执行一个程序,操作系统提供的中断机制使得cpu能够实现不断的在各个程序间进行切换,给人的感觉就是多个程序同时执行
为什么有中断?
现代操作系统一般都是采用基于时间片的优先级调度算法,把CPU的时间划分为很细粒度的时间片,一个任务每次只能时间这么多的时间,时间到了就必须交出使用权,即换其他的任务使用。这种要看操作系统的定时器机制了。那么时间片到之后,系统做了什么呢?这就要用到我们的中断了,时间片到了由定时器触发一个软中断,然后进入相应的处理历程
中断的概念?
计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得cpu暂时中断当前正在执行的程序而转去执行相应的事件处理程序。
待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。它使计算机可以更好更快利用有限的系统资源解决系统响应速度和运行效率的一种控制技术
中断的作用?
实时响应 + 系统调用

2,简述计算机内存的“内核态”和“用户态”
计算机中的“内核态”: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
计算机中的“用户态”: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态 和 内核态

3,为什么要有内核态和用户态?
内核态和用户态的区分就在于权限的不同,内核态属于最高权限,可以直接访问并操作操作系统命令,如果不做区分,用户可以直接操作操作系统调用硬件机制是非常危险的,所以要有用户态隔离重要的访问权限。

4,什么是进程?
进程就是一个正在启动的程序,一个程序不能称为进程,只有正在运行的程序才能称为进程,进程是一个抽象的概念,起源于操作系统。
进程是CPU执行的资源单位,进程就是资源集合。

5,什么是线程?
进程是CPU的资源单位,线程是CPU的执行单位,一个进程内至少有一个线程,同一个进程内的多个线程共享同一个内存地址
线程是CPU运行的最小执行单位,线程开销小,资源占用比进程少。

5、简述程序的执行过程;
1.程序开始加载,从硬盘中读取二进制字节给解释器
2.python程序同时调用GIL锁,拿到硬盘数据给解释器传参
3.解释器按顺序在内存中执行

6,什么是系统调用?
用户态申请内核态的权限的时候,需要向操作系统发出一个请求,让操作系统对用户态放开相应的内核态权限(以程序的名义)
这种机制就是系统调用机制,实际上还是基于用户态去操作,只是由操作系统放开了一定权限

7,threading模块event和condition的区别;
event事件用于主线程控制其他线程的执行,需要手动去设置全局的flag, Event实现与Condition类似的功能,不过比Condition简单一点。它通过维护内部的标识符flag来实现线程间的同步问题
可以把condition理解为一把高级的琐,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。threadiong.Condition在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已

8,进程间通信方式有哪些?
管道通信
无名管道: 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
有名管道: 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
队列通信
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
套接字
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信
共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信

9,简述对管道,队列的理解
管道通信
无名管道: 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
有名管道: 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
队列通信
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
队列的原理就是管道加锁

10,请简述你对join、daemon方法的理解,举出它们在生产环境中的使用场景;
join: 等待一个任务执行完毕;可以将并发变成串行。
 
daemon:
    守护进程(守护线程)会等待主进程(主线程)运行完毕后被销毁。
    运行完毕:
        1.对主进程来说,运行完毕指的是主进程代码运行完毕。
        2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守
护线程统统运行完毕,主线程才算运行完毕

11,简述IO多路复用模型的工作原理
IO多路复用的原理图如下:
IO多路复用是事件驱动型,使用select()对整个进程进行轮询
1.用户调用select模块,整个进程阻塞
2.内核态监控socket的状态,有数据时返回给select
3.select接收到数据后返回给系统
4.系统收到select数据命令后,系统调用内核拷贝数据给程序
select的优势在于可以处理多个连接,不适用于单个连接 

12,threading中Lock和RLock的相同点和不同点
Lock是互斥锁
Rlock是递归锁
相同点:互斥锁和递归锁的目的都是牺牲运行效率,将并行改为串行来实现数据共享的安全性,当一个进程(线程)拿到锁才能操作数据,操作完成后释放锁才能给其它进程(线程)使用
不同点:互斥锁会发生死锁现象,就是两个或以上的进程(线程)在争夺资源时互相等待,互相都不释放手中的锁让其他进程(线程)拿,递归锁就是互斥锁加计数器,所有进程(线程)都可以拿到锁,每有一个进程(线程)拿到锁就计数加一,直到该线程释放计数器的所有锁的计数,其他进程(线程)才能再操作。 
递归锁可以连续acquire多次,而互斥锁只能acquire一次

13、什么是select,请简述它的工作原理,简述它的优缺点;
select模块是python的IO多路复用的使用模块
select模块通过提供文件描述符给内核态,当没有文件描述符的时候,进程的执行被阻塞,当select提供文件描述符的时候,就交给内核态去拷贝数据
优点 只用一个单进程(线程)执行,占用的CPU资源少,能实现简单的socket多客户端的通信
缺点 如果文件描述符过大(数值过大轮询耗时间)的时候,select需要花大量的时间去处理;事件的探测和事件响应杂糅,事件响应体庞大的时候将影响整个模型。

14、什么是epoll,请简述它的工作原理,简述它的优缺点;
epoll是linux的IO多路复用的使用模块
1.epoll首先会通过linux(unix)系统申请一个简易的文件系统,建立这个epoll对象
2.在epoll里添加或删除连接
3.epoll里的wait收集正在发生事件的连接
优点 epoll的大并发效果更好,不像select那样去遍历句柄,而是利用内部的算法(红黑树增删连接、双向链表rdlist存储描述符使调用更高效)
缺点 epoll只兼容linux和unix

15、简述select和epoll的区别;
select 是通过轮询的方式监听句柄,遍历句柄给内核做系统调用,并且有个数限制1024个
epoll  不采用轮询的方式监听文件描述符,用共享内存、红黑树和双向链表,采用回调的方式给内核做系统调用
select兼容性比epoll好,epoll只适用于linux和unix

16、简述多线程和多进程的使用场景;
多线程使用于IO密集型,因为多线程占用CPU资源少、创建速度快共享内存间地址的数据
多进程使用于计算密集型,可以利用多核的优势并发执行CPU计算

17,请分别简述threading.Condition、threading.event、threading.semaphore的使用场景;
Condition   条件执行,控制复杂的线程同步问题,把锁作为参数传入控制锁的挂起和释放
event         事件执行,简单的线程同步,用标识符设置为evect后其他线程就阻塞
semaphore 设置线程的最大连接数的限制(递归锁原理)

18、假设有一个名为threading_test.py的程序里有一个li = [1, 2, 3, 4]的列表,另有a,b两个函数分别往该列表中增加元素,a函数需要修改li之前需要获得threading.Lock对象,b函数不需要,请问当线程t1执行a函数获取到Lock对象之后并没有release该对象的情况下,线程t2执行b函是否可以修改li,为什么?
可以,因为线程间本身就是数据共享的,虽然线程t1加了锁,但是线程t2没有加锁,不受锁的限制,可以直接修改数据

19、简述你对Python GIL的理解;
GIL全局解释锁是建立在python解释器级别的一把互斥锁,目的是用来处理python内部的垃圾回收机制
有了GIL的全局解释锁就让python一次只能执行一个任务代码,并不能实现真正多线程的执行任务

20、什么是同步I/O,什么是异步I/O?
同步I/O 做IO操作的时候会发生阻塞的就是同步IO    
           典型模型:阻塞IO、非阻塞IO、多路复用IO
异步I/O 做IO操作的时候不会发生阻塞
           典型模型:异步IO
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用
所谓异步,当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别

21,什么是管道,如果两个进程尝试从管道的同一端读写数据,会出现什么情况?
管道是socket套接字间的一种通信方式
管道是数据单向性的,读数据的时候不能进行写数据操作,写数据的时候不能进行读数据操作

22,为什么要使用线程池/进程池?
计算机虽然能够使用多进程或多线程,但是每次执行的个数实际是收到CPU的核数限制的,即便开启了多个进程也不能超过核数的去执行,所以要设置进程池来保证进程执行个数

23,如果多个线程都在等待同一个锁被释放,请问当该锁对象被释放的时候,哪一个线程将会获得该锁对象?
操作系统为线程分配锁的机制

24,import threading;s = threading.Semaphore(value=-1)会出现什么情况?
抛出异常ValueError: semaphore initial value must be >= 0,数值必须大于0

25,请将二进制数10001001转化为十进制;
 1*2^7 + 1*2^3 + 1*2^0 = 137

26,某进程在运行过程中需要等待从磁盘上读入数据,此时该进程的状态将发生什么变化?
当进程在执行的过程中遇到IO操作的时候就会从执行的状态跳转至阻塞状态,直到IO操作结束,才会将阻塞调度到就绪状态

27,请问selectors模块中DefaultSelector类的作用是什么;
DefaultSelector会根据python中的os模块提出操作系统,选择最优的接口( select/poll/epoll)

28,简述异步I/O的原理;
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了

29,请问multiprocessing模块中的Value、Array类的作用是什么?举例说明它们的使用场景
Value和Array类实现的是共享内存的作用,一般来说进程与进程之间是不能互相共享数据的,但是使用了Value和Array以后就能实现进程间的数据共享
import multiprocessing
 
 
def f(n, a):
n.value = 3.14
a[0] = 5
 
 
if __name__ == '__main__':
num = multiprocessing.Value('d', 0.0)
arr = multiprocessing.Array('i', range(10))
p = multiprocessing.Process(target=f, args=(num, arr))
p.start()
p.join()
print (num.value)
print (arr[:])
这里我们实际上只有主进程和Process对象代表的进程。我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为0.0。而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享

30,请问multiprocessing模块中的Manager类的作用是什么?与Value和Array类相比,Manager的优缺点是什么
Manager对象比Value和Array实现更多的共享数据类型
Manager对象类似于服务器与客户之间的通信 (server-client),与我们在Internet上的活动很类似。我们用一个进程作为服务器,建立Manager来真正存放资源。其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型
Manager类的作用共享资源,manger的的优点是可以在poor进程池中使用,缺点是windows下环境下性能比较差,因为windows平台需要把Manager.list放在if name='main'下,而在实例化子进程时,必须把Manager对象传递给子进程,否则lists无法被共享,而这个过程会消耗巨大资源,因此性能很差

31,请说说你对multiprocessing模块中的Queue().put(), Queue().put_nowait(), Queue().get(), Queue().get_nowait()的理解;
q = Queue(3) 队列 先进先出  进程间通信; 队列 = 管道 + 锁
q.put()
q.put_nowait()  # 无阻塞,当队列满时,直接抛出异常queue.Full
q.get()
q.get_nowait()  # 无阻塞,当队列为空时,直接抛出异常queue.Empty

32,什么是协程?使用协程与使用线程的区别是什么?
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的
一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU
线程进程都是同步机制,而协程则是异步
协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

33,asyncio的实现原理是什么?
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

34.如何获取多线程的返回结果
将线程放入队列中,实例化队列Queue->开启线程->放线程进入队列->提取队列中的线程
队列的三种方式(先进先出,后进先出(堆栈),设置优先级)

35.GIL锁造成的后果是什么
GIL锁的后果就是在cpython中实际上是基于解释器上的互斥锁,导致每次运行的时候只能单线程的运行,并不能实现多线程运行,只不过在遇到IO操作的线程的时候GIL锁就会释放锁(不提供锁),让其他的线程去执行,所以在读写和写入方面GIL锁的限制就小很多,但是计算密集型的时候还是只能单线程的运行。

36.多线程的适合什么场景?为什么线程不能操作CPU?
多线程适用于IO操作密集型,因为其开销小占用资源少,启动快的特性,并且在单进程内的多线程是实现内存数据共享的,
线程是进程的基本组成单位,进程是CPU的资源单位,单个或多个线程才能组成进程,所以说线程是不能直接操作CPU的。

37.为什么要有互斥锁?
互斥锁是为了在多进程(线程)的情况下,为了数据共享的安全性,牺牲了运行的效率(并行变为串行)而实现同一个时刻内,只允许有一个进程(线程)操作数据

二、编程题
1、请写一个包含10个线程的程序,主线程必须等待每一个子线程执行完成之后才结束执行,每一个子线程执行的时候都需要打印当前线程名、当前活跃线程数量;
from threading import Thread,currentThread,activeCount
import random
import time


def task():
    print('正在运行的线程%s'%currentThread().getName())
    start = time.time()
    time.sleep(random.randint(1,5))
    stop = time.time()
    print('任务执行时间%sS'%(stop-start))
    print('当前活跃的线程数%s'%activeCount())


if __name__ == '__main__':
    # 并发版
    t_l = []
    for i in range(10):
        t = Thread(target=task)
        t.start()
        t_l.append(t)
    for t in t_l:
        t.join()
    print('主线程%s结束'%currentThread().getName())
    # 串行版
    # for i in range(10):
    #     t = Thread(target=task)
    #     t.start()
    #     t.join()
    # print('主线程%s结束' % currentThread().getName())
View Code

2、请写一个包含10个线程的程序,并给每一个子线程都创建名为"name"的线程私有变量,变量值为“james”
# 2、请写一个包含10个线程的程序,并给每一个子线程都创建名为"name"的线程私有变量,变量值为“james”
from threading import Thread,currentThread
import random
import time


def task():
    start = time.time()
    print('线程%s启动!'%currentThread().getName())
    time.sleep(random.randint(1,2))
    stop = time.time()
    print('任务的执行时间%s'%(stop - start))

if __name__ == '__main__':
    # 线程不再写串行版,自行脑补
    t_l = []
    for i in range(10):
        t = Thread(target=task, name='james—%s'%i)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
    print('主线程结束')
View Code

3、请使用协程写一个消费者生产者模型;
'''
两个注意点
1.gevent.spawn内部是传函数地址,别传函数(),参数写函数地址后面
2.队列拿前判断下是不是空 q.empty()
'''
from multiprocessing import Queue
import time
import random
from gevent import monkey;monkey.patch_all()
import gevent
from threading import currentThread


def producer(q,i):
    start = time.time()
    print('开始生产')
    time.sleep(random.randint(1,2))
    res = '机器人%s号'%i
    stop = time.time()
    print('生产者生产了机器人%s号,花费时间%s' % (i, (stop - start)))
    # print(currentThread().getName())
    q.put(res)


def consumer(q):
    while not q.empty():
        start = time.time()
        print('开始消费')
        time.sleep(random.randint(1,2))
        stop = time.time()
        res = q.get()
        # print(currentThread().getName())
        print('消费者消费了%s,花费时间%s' % (res, (stop - start)))


if __name__ == '__main__':
    q = Queue()
    for i in range(10):
        gevent.joinall([gevent.spawn(producer,q,i), gevent.spawn(consumer,q)])
    print('主进程结束')
View Code

4、写一个程序,包含十个线程,子线程必须等待主线程sleep 10秒钟之后才执行,并打印当前时间;
from threading import Event,Thread,currentThread
import time
import random


def task():
    start = time.time()
    time.sleep(random.randint(1,4))
    stop = time.time()
    print('线程-%s运行的时间%s'%(currentThread().getName(),stop-start))
    print(time.strftime('%Y-%m-%d %X'))

if __name__ == '__main__':
    event = Event()
    event.wait(10)
    for i in range(10):
        t = Thread(target=task)
        t.start()
View Code

5、写一个程序,包含十个线程,同时只能有五个子线程并行执行;
from threading import Thread,currentThread
from concurrent.futures import ThreadPoolExecutor
import time
import random


def task():
    start = time.time()
    time.sleep(random.randint(1,4))
    stop = time.time()
    print('程序运行时间%s'%(stop-start))
    print('线程的名称%s'%currentThread().getName())


if __name__ == '__main__':
    ex = ThreadPoolExecutor(max_workers=5)
    for i in range(10):
        ex.submit(task)
    ex.shutdown(True)
View Code

6、写一个程序 ,包含一个名为hello的函数,函数的功能是打印字符串“Hello, World!”,该函数必须在程序执行30秒之后才开始执行(不能使用time.sleep());
from threading import Timer


def hello():
    print("hello, world")


t = Timer(30, hello)
t.start()
View Code

7、写一个程序,利用queue实现进程间通信;
from multiprocessing import Queue,Process
import time
import random


def produce(q,i):
    start = time.time()
    time.sleep(random.randint(1,2))
    stop = time.time()
    print('悄悄地拿到了%s,耗时%s'%(i,(stop-start)))
    q.put(i)


def consumer(q):
    while not q.empty():
        start = time.time()
        res = q.get()
        time.sleep(random.randint(1,2))
        stop = time.time()
        print('悄悄的亲了下%s,耗时%s'%(res,(stop-start)))


if __name__ == '__main__':
    q = Queue()
    p = Process(target=produce,args=(q,'小宝贝',))
    c = Process(target=consumer, args=(q,))
    p.start()
    p.join()
    c.start()
    c.join()
    print('主进程结束')
View Code

8、写一个程序,利用pipe实现进程间通信;
from multiprocessing import Process, Pipe


def task(conn):
    conn.send('hello world')
    conn.close()


if __name__ == "__main__":
    parent_conn, child_conn = Pipe()
    p = Process(target=task, args=(child_conn,))
    p.start()
    p.join()
    print(parent_conn.recv())
View Code

9、使用selectors模块创建一个处理客户端消息的服务器程序;

10、使用socketserver创建服务器程序时,如果使用fork或者线程服务器,一个潜在的问题是,恶意的程序可能会发送大量的请求导致服务器崩溃,请写一个程序,避免此类问题;

11、请使用asyncio实现一个socket服务器端程序;

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

 
 

posted on 2018-08-08 13:23  pandaboy1123  阅读(725)  评论(0编辑  收藏  举报

导航