面试题:
-
请聊聊进程队列的特点和实现原理
-
进程之间可以互相通信 IPC
-
数据安全
-
先进先出
-
实现原理
-
管道+锁
-
管道是基于文件级别的socket+pickle实现的
-
-
-
你了解生产者消费者模型吗?
-
了解
-
为什么了解?
-
工作经历:
-
采集图片,爬取音乐,主要是爬取大量数据,想提高爬虫效率,有用过一个生产者消费者模型,这个模型使用的是消息中间件,用的是redis,获取网页的过程作为生产者,分析与获取歌曲作为消费者
-
-
在python中实现生产者消费者模型可以用那些机制
-
消息中间件
-
celery:定时发送短信的任务
-
-
-
从你的角度说说进程在计算机中扮演什么角色
-
资源分配的最小单位
-
进程与进程之间内存隔离
-
进程是由操作系统负责调度,并且进程与进程之间是竞争关系
-
我们对进程的三状态时刻关注,因此尽量减少IO操作,或者在进程内开线程来规避IO
-
让我们写的程序在运行时,能够更多的占用CPU资源
-
-
为什么线程之间数据不安全
-
线程之间数据共享
-
多线程的情况下:
-
如果在计算某一个变量时,还要进行赋值操作,这个过程不是由一条完整的CPU指令完成的,
-
如果在判断某一个bool表达式时,再做某些操作,这个过程也不是由一条完整的CPU指令完成的,
-
在中间发生了GIL锁的切换或时间片的轮转,可能导致数据不安全
-
-
-
守护线程
-
守护线程会等待主线程(包括其他子线程)结束之后才结束
-
守护线程的结束原理,主进程结束之后,守护线程和其他所有线程资源一起被回收掉
-
-
线程锁——最好只创建一把锁,线程锁一定锁不了进程
-
互斥锁:
-
在同一个线程中,不能连续被acquire多次,一次acquire必须对应一次release
-
效率高
-
-
递归锁
-
在同一个线程中,可以被连续acquire多次,但一个aquire对应一个release
-
效率低
-
-
死锁现象:
-
多把锁交替使用,在第一把锁未release释放之前已经被acqiure了
lock.acquire()
lock.acquire()
-
如何解决:
-
最快的解决办法是:把所有的互斥锁修改成递归锁,影响效率
-
后期在慢慢整理,把递归锁能解决的问题用互斥锁处理
-
-
-
判断数据是否安全
-
是否数据共享,是同步还是异步,(数据共享且异步)
-
+=,-=,*=,/=计算之后,数据不安全
-
if while条件这两个判断由多个线程完成,数据不安全
-
-
-
队列queue
-
线程队列
-
数据安全
-
先进先出
-
原理:加锁+链表
-
先进先出的队列 Queue
-
后进先出的队列 FILOQueue 栈
-
优先级队列priorityQueue 根据放入数据的ascii码从小到大输出
-
-
二、今日内容
1、池 (重要)
-
线程池
-
一般更具IO的比例定制
-
个数:cpu_count * 5
#线程池
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import current_thread
import time
import random
import os
def func():
print(current_thread().ident,'开始')
time.sleep(random.randint(1,4))
print(current_thread().ident,'end')
tp = ThreadPoolExecutor(3) #创建一个池,内部存在三个线程
for i in range(20):
tp.submit(func) #提交线程任务 -
-
进程池
-
高计算(没有IO操作,没有文件操作,没有网络操作,没有input)
-
个数:cup_count * 1 < cup_count * 2
-
#进程池
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import current_thread
import time
import random
import os
def func():
print(os.getpid(),'开始')
time.sleep(random.randint(1,4))
print(os.getpid(),'end')
if __name__ == '__main__':
tp = ProcessPoolExecutor(3)
for i in range(20):
tp.submit(func)
-
函数有返回值
#有返回值
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import current_thread
import time
import random
import os
def func(i):
print(current_thread().ident,'开始')
time.sleep(random.randint(1,4))
print(current_thread().ident,'end')
return i * (i+1)
tp = ThreadPoolExecutor(3)
tp_lst = []
for i in range(20):
ret = tp.submit(func,i) #异步非阻塞
tp_lst.append(ret)
for ret in tp_lst:
print(ret.result()) #获取函数的返回结果 #同步阻塞 -
使用回调函数 效率最高
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import current_thread
import time
import random
import os
def func(i):
print(current_thread().ident,'开始')
time.sleep(random.randint(1,4))
print(current_thread().ident,'end')
return i * (i+1)
def get_result(ret):
print(ret.result())
tp = ThreadPoolExecutor(3)
for i in range(20):
ret = tp.submit(func,i)
ret.add_done_callback(get_result) #异步阻塞
2、协程
-
协程概念(非常重要)
-
是操作系统不可见的
-
协程本质就是一条线程,多个任务在一条线程上来回切换,来规避IO操作,就达到我们将一条线程中的io操作降到最低
-
模块 规避io的两个模块
-
gevent 利用了 greenlet 底层模块完成的切换—自动规避io的功能
-
asyncio 利用了 yield 底层语法完成的切换—自动规避io的功能
-
-
-
-
gevent 第三方模块
-
会用
-
能处理一些基础的网络操作
from gevent import monkey
monkey.patch_all()
import gevent
import time
import random
def func(i):
print('start:',i)
time.sleep(random.randint(1,4))
print('end:',i)
g = gevent.spawn(func,1)
g1 = gevent.spawn(func,1)
g2 = gevent.spawn(func,1)
gevent.joinall([g,g1,g2])
-
-
import asyncio
async def func(i):
print('start:', i)
await asyncio.sleep(2)
print('end:', i)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([func(1),func(3)]))
3、区别
-
进程:计算机资源分配的最小单位 数据不安全 开销大 可以利用多核 操作系统级别
-
线程 计算机(cpu)调度的最小单位 数据不安全 开销小 不能利用多核 操作系统级别
-