美文网首页
python面试大全-高级

python面试大全-高级

作者: hsiaojun | 来源:发表于2018-09-20 00:01 被阅读0次

    1,线程 进程 多进程 多线程 协程

    同步 + 阻塞
    异步 + 非阻塞
    
    1.从计算机硬件角度:
    
    计算机的核心是CPU,所有计算任务都由CPU负责。
    
    单个CPU核心,在一个CPU时间片里,只能执行一个程序任务。
    
    台式机 intel i5 处理器 四核四线程:
    四个CPU核心,每个核心有一个逻辑处理器
    同时可以处理四个任务。
    
    台式机 intel i7 处理器 四核八线程 (intel 超线程技术)
    四个CPU核心,每个核心有两个逻辑处理器,
    同时可以处理八个任务。
    
    注意:CPU的x核x线程,和操作系统调度的线程,不是一回事。
    
    2. 从操作系统角度:
    
    进程和线程,都是CPU执行任务的执行单元。
    
    进程:表示一个程序的执行活动(上下文管理,打开、读写、关闭)
    线程:表示进程工作时的最小调度单位(执行功能a,执行功能b...)
    
    一个程序至少有一个进程,一个进程内至少有一个线程
    
    3).并行:
    
    执行单元CPU == 执行的任务
    
    任务1: ----------
    任务2: ----------
    任务3: ----------
    
    
    4).并发:
    执行单元CPU < 执行的任务
    
    一个CPU、3个任务
    任务1:------                  -------
    任务2:      ------      ------
    任务3:            ------
    
    
    
    5).多进程 和 多线程 的概念:
    表示一个程序可以同时执行多个任务,进程和线程调度都是有操作系统完成。
    
    多进程:进程和进程之间不共享任何状态,每个进程都有自己独立的内存空间。
        如果进程之间做数据通讯或切换,操作系统开销很大。
    
    多线程:同一个进程下的多线程共享,该进程的内存空间,
        如果线程之间做数据通讯和切换,操作系统开销很小。
    
    共享意味着竞争,可能会带来数据的安全隐患,所以有了"互斥锁"。
    
    6).互斥锁:一种安全有序的让多个线程访问进程内存空间的机制,避免产生数据安全问题。
    
    
    解释型语言:利用解释器按行执行代码,程序执行后做语法检查。开发更快
    编译型语言:利用编译器编译代码生成可执行文件,程序执行前做语法检查。更稳定一些
    
    GIL 全局解释器锁: 同一时刻只能有一个线程在工作。
    
    Python只有一个GIL,当线程需要执行任务时必须获取GIL,那么其他线程就处理等待状态。
    
    坏处:不能充分利用CPU多核资源。
    好处:直接从根本上杜绝了多线程访问内存空间的安全问题。
    
    
    当程序执行一个IO阻塞的函数时,解释器会释放GIL。
    
    如果程序中没有IO阻塞操作,解释器会每隔100次操作,会释放GIL,让其他线程尝试工作。
    sys.getcheckinterval()   结果为100
    
    
    
    7).多进程:密集CPU任务(大量的并行计算),可以充分使用多核CPU的资源。
        multiprocessing 模块
    
        缺点:进程之间通信和切换成本高,如果需要大量数据通信和切换的场景,不适合用多进程。
    
    8).多线程:密集I/O(输入 输出)任务(网卡IO、磁盘IO、数据库IO)
        threading、 multiprocessing.dummy(多进程里面的多线程模块)
    
        优点:线程之间通信成本和切换开销极小。
        缺点:在Python里一个CPU时间片只能执行一个线程,不能充分利用多核CPU的资源。
    
    9).协程:在单线程上执行多个任务,任务调度由程序员控制,基于用户管理,不受操作系统控制。
        所以没有线程,进程的切换开销,也不需要处理互斥锁。
        通过代码逻辑,不停的切换需要指定的函数。
    
        gevent
        from gevent import monkey 猴子补丁
        monkey.patch_all()
        给Python底层的网络库(socket、select)打个补丁,在处理网络IO任务时,可以按异步非阻塞方式执行。
    
        优点:不经过操作系统调度,所以执行效率高。
    
        缺点:因为是单线程执行,所以处理(CPU密集任务、密集本地IO )不合适。
    
    
    
    多进程:密集CPU任务
    多线程:密集IO任务
    协程:密集网络IO任务
    
    
    主线程结束后,非守护线程不会结束,守护线程会结束,Python默认创建的线程都是非守护线程。
    setDeamon()
    

    2,内存和硬盘有什么区别

    1、内存属于计算机内存储器,硬盘属于计算机外存储器;
    2、内存是计算机的工作场所,硬盘用来存放暂时不用的信息;
    3、内存是半导体材料制作,硬盘是磁性材料制作;
    4、内存中的信息会随掉电而丢失,硬盘中的信息可以长久保存。
    
    # 什么是硬盘?
    硬盘:用来存储数据的计算机配件。是非易失性储存器,说得直白点,就是你关了机,里面的数据也不会丢。平时电脑里的C盘、D盘、E盘,都是指硬盘。其外观是一个方形的盒子,里面有盘片(像光盘一样),还有读写盘片的磁头。
    
    # 什么是内存?
    内存:由于硬盘速度比较慢,CPU如果运行程序的时候,所有数据都直接从硬盘中读写,会非常影响效率。所以CPU会将运行软件时要用的数据一次性从硬盘调用到运行速度很快的内存,然后再CPU再与内存进行数据交换。内存是易失性存储器,只要你断了电,内存中的数据就没有了。内存本身是一块集成电路板,上面有数颗用于存储数据的的芯片。由于这块电路板一般都做成长条形,所以叫“内存条”。
    

    3,多线程在web项目中的应用,项目中多线程同时操作某段代码怎么处理

    多线程一般使用在进行io操作。
    
    在python中由于GIL线程就是“鸡肋”
    
    使用场景:
    
    1. 在viewes视图中, 视图同时调用几个第三方的接口, 就可以使用线程实现, 要并行使用。
    2. 创建订单时候, 对于每张表都可以使用线程进行操作。
    
    解决办法:
    
    1. 把代码加锁, 线程只有拿到锁的使用权,才能对代码进行操作
    2. 把多线程假如队列, 依次来执行这段代码,执行效率不高
    3. 使用乐观锁。 效率比较高。
    

    4,线程中start 和 run 方法的区别

    • 若调用start,则先执行主进程,后执行子进程;
    • 若调用run,相当于正常的函数调用,将按照程序的顺序执行

    5,可迭代对象 iterable

    # 可以使用 for 来循环遍历的对象[列表 字典 元组 字符串]
    
    - 迭代是重复反馈过程的活动,每一次迭代得到的结果会作为下一次迭代的初始值
    - 特点:只能向前,不能后退
    - for 循环时迭代遍历
    
    
    # 可迭代对象
    - 一个对象只要定义了__iter__方法,这个对象就是可迭代对象(能返回一个帮忙迭代遍历的对象)
    - __next__ 是迭代器, 它能可迭代对象,返回迭代器的对象. raise StopIteration 抛出异常
    # calctor = obj.__iter__() # 或者calctor = iter(obj) ,iter()函数对上面进行了封装
    # while True:
    #     try:
    #         ret = calctor.__next__() # 或者ret = next(calctor) 等价的
    #         print("ret = ", ret)
    #     except StopIteration: # 接收异常
    #         break
    
    - 列表 元组 字典 字符串 是可迭代对象,能for循环遍历
    

    6,迭代器 iterator

    - 迭代器是一个可以记住遍历位置的对象
    - 只能往前不能往后
    - 一个类中,同时实现了iter()和next()方法,就是迭代器
    - 迭代器肯定是迭代对象,但迭代对象不一定是迭代器
    - 作用 应用场景:不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。
        
    优点:取值的时候不依赖于索引,这样可以遍历哪些没有索引的对象(文件和字典)
        与列表相比,迭代器是惰性计算,更节省内存
    缺点:无法获取迭代器的长度,没有列表灵活
        只能往后取值 ,不能倒着取值
        
    next()取值,当取完了会报异常Stoplteration,用try-except-else就是一个最常用的异常处理结构:
    
    使用Iterable模块可以判断对象是否是可迭代的
    使用Iterator模块可以判断对象是否是迭代器:
    

    7,生成器 generator

    - 生成器是一类特殊的迭代器
    - 带有yield关键字的函数不是普通函数,是生成器,也是迭代器
    - 作用: 可以用更少的中间变量写流式代码  相比其它容器对象更能节省内存和cpu 更少的代码来实现相似的功能
    
    - 通过列表生成器创建生成器 ,把[] 改成()
    将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表变为生成器
    
    - 使用yield创建生成器 (通过 next() send 调用函数)
    - yield 保存当前运行状态 让程序暂停,将yield关键字后面表达式的值作为返回值返回,
    - 生成器在每次暂停的时候,函数的状态将被保存下来
    - 根据生成器创建一个生成器对象
    - 这个生成器对象和yield 后面的返回值没有任何关系
    - 函数只要有yield关键字,这个生成器函数就会返回一个生成器对象
    
    - 要想获取生成器return返回值,必须人为处理异常,return返回值和异常一起抛出
    
    while True:
        try:
            v = next(gen)
            print("v = ", v)
        except Exception as ret: # Exception是所有异常类的基类
            print("ret.value = ", ret.value)
            break
    - next()等价于send(None)
    - send()可以在唤醒的同事向断点处传入一个附加数据
    - send(None) 首次调用调用必须传None(首次也可以用next)
    

    8,协程 gevent

    参考地址:https://www.cnblogs.com/zingp/p/5911537.html

    #  概念:协程是一种用户态的轻量级线程,
    概念加深理解: 线程是系统级别的,它们是由操作系统调度;协程是程序级别的,由程序员根据需要自己调度。我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以中断去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。也就是说同一线程下的一段代码<1>执行着执行着就可以中断,然后跳去执行另一段代码,当再次回来执行代码块<1>的时候,接着从之前中断的地方开始执行。
        
       
    协程的调度完全由用户控制。
    协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切 换的开销,# 可以不加锁的访问全局变量,所以上下文的切换非 常快。 
    协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置
    
    
    # 小结
    1).cpu值认识线程,而不认识协程,协程是用户自己控制的,cpu根本都不知道它们的存在。
    2).线程的上下文切换保存在cpu的寄存器中,但是协程拥有自己的寄存上下文和栈。
    3).#协程是串行的,无需锁。
    
    # 符合下面四个条件才能称之为协程:
    1)必须在只有一个单线程里实现并发
    2)修改共享数据不需加锁
    3)用户程序里自己保存多个控制流的上下文栈
    4)一个协程遇到IO操作自动切换到其它协程
        
    # 协程的优点: 
    利用好等待时间做任务切换
        1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
      (2)无需原子操作锁定及同步的开销
      (3)方便切换控制流,简化编程模型
      (4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
    
    # 协程的缺点(无法利用多核资源)
    要耗时等待
    1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
    - 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
    
    2)进行阻塞操作(如IO时)会阻塞掉整个程序
        
    - 通过yield 和next 简单实现协程
    - 并不是所有耗时都可以切换, 如果是延时,默认只能是gevent.sleep(), 延时目的为了产生耗时
    - 主程序不等待协程
    - join 不让主程序结束 产生耗时,做任务切换
    - joinall() 等待所有的协程结束 里面的参数是列表[]
    
    import gevent
    # 概念
    Gevent 是一个第三方库,可以轻松通过gevent实现协程程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度
    
    gevent会主动识别程序内部的IO操作,当子程序遇到IO后,切换到别的子程序。如果所有的子程序都进入IO,则阻塞
    
    gevent.sleep(0.5)
    g1 = gevent.spawn(work1)
    g2 = gevent.spawn(work2)
    
    gevent.joinall([
            gevent.spawn(work1, 5), # 逗号
            gevent.spawn(work2, 15)
        ]) #等待所有的协程  ,里面的参数是列表
    
    #打补丁
    - 有了gevent 就要打补丁
    from gevent import monkey
    monkey.patch_all() #打补丁的目的,有耗时(不局限于gevent中的延时)就任务切换,利用别的一些耗时做切换(并不是所有的耗时,主要指网络的接收耗时)
    
    #套接字端口复用
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    # 线程存在进程中
    # 协程存在于线程中,协程由程序员调度,顺序确定
    # 主(程序)线程不会等待协程,主线程(主程序)结束了,协程就不存在
    
    # join 产生耗时,做任务切换
    

    9,async/await 异步IO

    asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持,用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine(协程)类型,然后在coroutine(协程)内部用yield from调用另一个coroutine(协程)实现异步操作
    
    Python从3.5版本开始为asyncio提供了async和await的新语法;
    import asyncio
    
    @asyncio.coroutine
    def hello():
        print("Hello world!")
        # 异步调用asyncio.sleep(1):
        r = yield from asyncio.sleep(1)
        print("Hello again!")
    
    # 获取EventLoop:
    loop = asyncio.get_event_loop()
    # 执行coroutine
    loop.run_until_complete(hello())
    loop.close()
    
    异步操作需要在coroutine中通过yield from完成;
    多个coroutine可以封装成一组Task然后并发执行
    

    10,yield的作用

    • 保存当前运行状态(断点),然后暂停执行,即将函数挂起
    • 将yeild关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用,当使用next()、send()函数(它还可以接收调用者发出的参数) 让函数从断点处继续执行,即唤醒函数。

    11,协程中的send

    ①唤醒生成器
    ②给yield传一个值,就是yield接收到的这个值。这个说明yield在被唤醒的时候可以接收数据

    12,进程

    - 进程的相关命令查询: ps -aux | grep python3  查看当前用户下python3所有进程
    - kill + 进程号pid 终止指定进程
    - 进程状态(就绪态 执行态 等待态)
    
    - 创建出来的进程是子进程,子进程是父进程的一份拷贝
    - 主进程从头开始执行,子进程必须等主进程start后才能启动
    
    import multiprocessing
    import os # 进程号
    os.getpid() 获取进程号
    os.getppid() 获取父进程号
    p1 = multiprocessing.Process(target=work1)
    p2 = multiprocessing.Process(target=work2)
    
    p1.start()# 启动进程
    p2.start()
    - args:给target指定的函数传递的参数,以元组的方式传递
    - is_alive():判断进程子进程是否还在活着
    - join([timeout]):是否等待子进程执行结束,或等待多少秒
    - terminate():不管任务是否完成,立即终止子进程
    
    - 进程间不共享全局变量(子进程是父进程的份拷贝)
    # 进程间通信Quere
    q = multiprocessing.Queue()
    # 创建2个进程
    p1 = multiprocessing.Process(target=branch1, args=(q,))
    p2 = multiprocessing.Process(target=branch2, args=(q,))
    
    - 进程间通信队列创建:multiprocessing.Queue()
    - 进程池通信队列创建:multiprocessing.Manager().Queue()
    # 多进程中要使用multiprocessing.JoinableQueue queue = Queue() 因为普通队列无法实现多进程间通信
    
    # 传统多进程的缺点:进程过多,进程可能创建失败,时间耗在进程的创建和销毁 ===> 引入 进程池Pool
    - 父进程不会等待进程池里面的进程
    - 方法三,po.join()  join之前必须先close,关闭代表不添加新任务,但不代表进程池不工作了..等待进程池工作完,才往下走
    # 创建进程池,里面有3个进程
    po = multiprocessing.Pool(3)
    for i in range(20):
            # 添加任务到队列中,让进程池,自动取任务,自动管理
            # 函数直接写名字即可,无需target
            # 进程池无需启动,添加任务后,自动启动
            po.apply_async(work, args=(i+1,))
    
        # join之前,必须先close(), 关闭代表不添加新任务,不代表进程池不工作
        po.close()
        po.join() # 等待进程池工作完毕,才往下走主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用
            
    # 进程有系统调度,顺序不确定
    # 有3个进程,主程序等待子进程,子程序不结束,程序就不结束
    
    
    - 进程间通信 q = multiprocessing.Queue() #参数不写,队列长度不做限制
    - # 创建2个进程
        p1 = multiprocessing.Process(target=branch1, args=(q,))
        p2 = multiprocessing.Process(target=branch2, args=(q,))
    
        # 启动进程
        p1.start()
        p1.join() # p1执行完后,才能往下走
        p2.start()
        
        
    - 进程池间通信 
     q = multiprocessing.Manager().Queue() #创建进程间通信
     po = multiprocessing.Pool(1) # 创建进程池
     po.apply_async(branch1, args=(q,)) #把任务添加到队列中
     po.apply_async(branch2, args=(q,)) #把任务添加到队列中
     po.close()
     po.join() # 等待结束
    

    13,线程

    import threading
    mutex = threading.Lock() # 创建锁,锁默认是开着的
    mutex.acquire() # 上锁
    mutex.release() # 解锁
    mutex.acquire(timeout = 1)# 可通过添加超时时间避免死锁 1s 后不开锁,自动解锁  防止死锁
    
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
    
    t1.start()
    t2.start()
    多线程共享全局变量会出现资源竞争
    - 优点:在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据,线程之间通信成本和切换开销极小
    - 缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
    解决方法: 线程同步 引入互斥锁
    锁的好处:
    - 确保了某段关键代码只能由一个线程从头到尾完整地执行
    锁的坏处:
    - 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    - 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
    
    
    
    # 线程由进程调度,执行顺序不确定
    # 主线程等待子线程,子线程不结束,程序就不结束
    # 3个线程都存在于进程中,只有一个空间
    
    # threading.enumerate()获取当前程序的线程信息,返回值是一个列表
    

    14,多进程和多线程

    # 多进程
    -编程相对容易;通常不需要考虑锁和同步资源的问题。 
    -更强的容错性:比起多线程的一个好处是一个进程崩溃了不会影响其他进程。 
    -有内核保证的隔离:数据和错误隔离。
    
    在python中有三种方式创建多进程:fork,process,pool
    # 多进程案例
    1).nginx主流的工作模式是多进程模式(也支持多线程模型)
        - worker_processes:nginx 进程数,建议按照cpu 数目来指定,一般为它的倍数 (如,2个四核的cpu计为8)
        - use epoll , 使用epoll的io模型
        - worker_rlimit_nofile 进程打开的最多文件描述符数目,最好与ulimit -n 的值保持一致
        - worker_connections 204800 每个进程允许的最多连接数,理论上每台nginx 服务器的最大连接数为进程*每个进程最大连接数
        - Gunicorn配置
            1) workers: 最优进程数:CPU+1
            2) backlog = 2048 : 等待链接最大数
            3) worker_class = 'gevent' : 异步处理模块
            4) threads: 最优线程数:CPU*2+1
            5) worker_connections: 单个worker最大连接数 1000
            6) max_requests:单个worker最大请求数 2000
            7) timeout = 30 : 链接超时时间
    2).redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)
        单线程操作,避免了频繁的上下文切换/ 采用了非阻塞 I/O 多路复用机制 
    3).chrome浏览器也是多进程方式
        浏览器中你打开的每个页面,都是一个进程。如果一个页面崩溃了,不会影响其他页面(进程相互独立)。但是谷歌浏览器占用内存相比于其他浏览器多,实际应用中,打开页面太多,占用内存较大。其他浏览器采用多线程来实现,每个页面就是一个线程,所以一个页面崩溃,会导致整个浏览器崩溃。比如我的web服务器,我就采用多线程来与数据库连接,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的。还有大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的
    
    
    # 多线程
    - 创建速度快,方便高效的数据共享 
    - 多线程间可以共享同一虚拟地址空间;多进程间的数据共享就需要用到共享内存、信号量等IPC技术;
    - 较轻的上下文切换开销 - 不用切换地址空间,不用更改寄存器,不用刷新TLB。
    
    # 多线程案例
    1).桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程; 
    2).memcached
    
    # Python GIL
    全局解释器锁GIL,用于管理解释器的访问,Python线程的执行必须先竞争到GIL权限才能执行。因此无论是单核还是多核CPU,任意给定时刻只有一个线程会被Python解释器执行
    
    # 总结
    由于Python的GIL限制,多线程更适合于I/O密集型应用(如典型的爬虫程序)。而对于计算密集型的应用,为了实现更好的并行性,可使用多进程以使CPU的其他内核加入执行。
    

    15,多线程 多进程 线程池 协程池爬虫

    参考爬虫课件. 多线程爬虫里面主要  python解释器有个GIL锁的存在
    
    # 多线程爬虫思路
    如何实现多线程爬虫
    准备URL队列,响应html队列,解析后数据列表队列
    开1个线程: 把URL放到URL队列中
    开3个线程: 从URL队列中取出URL,发送请求获取数据放入html队列
    开2个线程: 从html队列取出HTML解析,把数据存储到数据队列
    开1个线程: 从数据队列中取出数据, 写入文件中
        
    # 多进程爬虫
    多进程中要使用multiprocessing.JoinableQueue queue = Queue() 因为普通队列无法实现多进程间通信
    把原来的threading.Thread修改为multiprocessing.Process
    把queue.Queue修改为multiprocessing.JoinableQueue
    
    # 线程池爬虫
    from multiprocessing.dummy import Pool 导入线程池
    p.apply_async(func, ('任务1', ))  执行异步任务
    from queue import Queue 线程池中使用queue.Queue负责线程间的通讯
    
    把多线程版该线程池版:
        去掉线程相关代码
        导入线程池 from multiprocessing.dummy import Pool
        在init方法中创建线程池
        把run_use_more_task函数中, 使用线程池异步任务执行函数
        在调用队列的join方法前,小睡一下
        
    # 协程池爬虫
    from gevent import  monkey
    monkey.patch_all() 导入猴子补丁
    from gevent.pool import Pool 导入协程池
    在协程池中使用队列, 与在线程池使用队列一样.
    

    16,线程 进程 协程的区别

    # 进程
        进程是资源分配的单位                       ===== 内存
        进程切换需要的资源很最大,效率很低,好处是安全
        多进程中,子进程是父进程的一份拷贝,进程间不共享全局变量,进程间通信Quere
        多进程通信: multiprocessing.JoinableQueue 来创建队列
        创建进程池: multiprocessing.Pool(3)
        
    # 线程   
        线程存在进程中
        线程是操作系统调度的单位                   ====== CPU 
        线程切换需要的资源一般,效率一般
        线程是最小的执行单元
        所有线程共享进程的内存
        多线程爬虫: 创建线程池 from multiprocessing.dummy import Pool
        
    # 协程
        协程切换任务资源很小,效率高,但是要耗时等待
        协程是在一个线程中(只使用一个线程),所以是并发
        协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理
        协程池: from gevent.pool import Pool 导入协程池
    

    17,线程同步->互斥锁->死锁

    - 在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
    - 缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
    - 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁
    - 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
    
    # 怎么实现互斥锁
    import threading
    # 创建锁
    mutex = threading.Lock()
    # 锁定,上锁
    mutex.acquire()
    # 释放,解锁
    mutex.release()
    # 可通过添加超时时间避免死锁
    mutex.acquire(timeout=1) # 1s后不开锁,自动解锁
    
    /锁的好处:确保了某段关键代码只能由一个线程从头到尾完整地执行
    /锁的坏处:
    - 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    - 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
    

    18,线程的生命周期

    1.)New 新建
    2.)Runnable 就绪; 等待线程调度,调度后进入运行状态
    3.)Running 运行
    4.)Blocked 阻塞,暂停运行,解除阻塞后进入Runnable状态重新等待调度
    5.)Dead 消亡,线程方法执行完毕 或异常终止
    # 三种情况: 从运行状态--> 阻塞状态
    - 同步: 线程中获取同步锁,但是资源已经被其它程序锁定时
        
    - 睡眠: 线程运行sleep()或join()方法后,线程进入Sleeping状态。区别在于sleep等待固定的时间,而join是等待子线程执行完。sleep()确保先运行其他线程中的方法。当然join也可以指定一个“超时时间”, 最常见的情况是在主线程中join所有的子线程。
        
    - 等待: 线程中执行wait()方法后,线程进入Waiting状态,等待其他线程的通知(notify)。wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。
                                                 
    threading.Lock()不允许同一线程多次acquire(), 而RLock允许, 即多次出现acquire和release
    

    19,猴子补丁的深入 --> 非阻塞

    - 在运行时替换方法、属性等
    - 在不修改第三方代码的情况下增加原来不支持的功能
    - 在运行时为内存中的对象增加patch(补丁)而不是在磁盘的源代码中增加
    - 带了便利的同时也有搞乱源代码优雅的风险
    
    模块运行时替换的功能
    gevent的猴子补丁就可以对ssl、socket、os、time、select、thread、subprocess、sys等模块的功能进行了增强和替换
    
    monkey patch指的是在运行时动态替换,一般是在startup的时候.
    用过gevent就会知道,会在最开头的地方gevent.monkey.patch_all();把标准库中的thread/socket等给替换掉.这样我们在后面使用socket的时候可以跟平常一样使用,无需修改任何代码,但是它变成非阻塞的了.
    
    另外:如果你没读过mock模块的话,真的值得花时间读一读。这个模块非常有用
    

    20,标准库线程安全的队列是哪一个?不安全的是哪一个?logging是线程安全的吗?

    其实Python队列都是线程安全的
    
    # 线程
    线程安全和非线程安全这些概念在其他的编程语言也同样使用.
    所谓线程安全:就是对于多线程同时操作是是安全的而不会发生写冲突,比如python的Queue
    相反非线程安全:就是多线成同时操作时会发生写冲突,比如python的其他list,set,dict
    
    # 队列
    Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.这些队列都实现了锁原语,能够在多线程中直接使用.可以使用队列来实现线程间的同步.
    
    # 三种队列
    Python queue模块有三种队列:
    1.FIFO队列先进先出.(线程安全)
    2.LifoQueue类似于堆,即先进后出(线程安全)
    3.PriorityQueue优先级队列,级别越低,越先出来(线程安全)
    
    # 队列构造函数
    针对这三种队列分别有三个构造函数:
    1.class Queue.Queue(maxsize) FIFO
    2.class Queue.LifoQueue(maxsize) LIFO
    3.class Queue.PriorityQueue(maxsize) 优先级队列
    
    # logging
    logging是Python标准库里的日志模块(线程安全)
    

    21,什么是死锁,产生死锁的条件是什么?怎么解决?

    # 什么是死锁?
    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
    
    # 产生死锁的四个必要条件
    ● 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
    
    ● 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    
    ● 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
    
    ● 循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
    
      这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
        
    # 怎么解决死锁的问题
      ● 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
    
      ● 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
    
      ● 打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
    
      ● 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
    

    相关文章

      网友评论

          本文标题:python面试大全-高级

          本文链接:https://www.haomeiwen.com/subject/ncownftx.html