1、什么是进程和线程?
首先我们要知道进程是系统进行资源分配和调度的基本单位,而线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。
比如我们打开一个 csdn 的软件,其实就打开一个叫csdn 的进程,既然一个进程汇中至少要有一个线程,那肯定就会有多线程,什么是多线程?
1、多线程是指从软硬件上实现多条执行路径的技术。 2、多线程用在哪里,有什么好处?
例如铁路12306购票系统。
例如过年回家抢票,不可能只有你一个人在买票,那每个人进来的时候都要有一个执行路径,那这个之后就需要用到多线程。
2、创建多进程
在python 中创建多进程我们要知道一个模块-- multiprocessing
函数 | 介绍 | 参数 | 返回值 |
---|---|---|---|
Process | 创建一个进程 | target(函数),args(函数的参数) | 进程对象 |
start | 执行进程 | 无 | 无 |
join | 阻塞程序 | 无 | 无 |
kill | 杀死进程 | 无 | 无 |
is_alive | 进程是否存活 | 无 | bool |
接下来我们进入代码实操。
2.1 创建
我们创建了两个函数,并将两个都放在主函数里面执行,我们来看看执行的结果。
从控制台打印的结果可以看出,我们的两个函数和主函数都是在同一个进程内,接下来我们要进入正题,我们这里要稍微改造一下代码。
我们创建了一个进程去执行 work_a 函数,我们来看看执行的结果。
从执行结果来看我们work_a 已经执行在另外一个进程中了,work_b 和 主函数 的 函数id 是一样的,
说明它们实在同一个进程中的。
而我们也发现执行的时间少了一半,因为我们的进程之间是无不干扰的,你跑你的,我跑我的,两个一起跑,这大大提升了我们的执行效率。
我们现在给 work_b 也 放到一个进程中去,看看执行的效果是怎么样的。
我们可以看到时间直接打印了,这是为什么呢?
前面我们有说到。进程之间是无不干扰的,然后 name 是主进程,主进程没有了干扰,所以就直接执行了。
2.2 阻塞
如果我们想要让两个
子进程先执行完毕再执行主进程这个就可以使用到join。
我们来优化一下代码。
再来看看执行效果。
从执行结果来看,我们确实是实现了先执行完子线程再执行主线程,至于为什么控制台看起来优点乱,是因为有的进程它执行的时间是一致的。重叠在一起了。
3、 进程池和进程锁
由于每个进程都会消耗内存和cpu 资源,所以我们不能无限创建进程,这样有可能会发生系统的死机的情况。
为了解决这个问题,我们可以使用多线程来替代,或者进程池。
还有多进程还存在同时修改一个文件的时候,会出现问题,解决方方法就是给这个文件上一把锁。
3.1 进程池
我们现在知道,进程不能创建太多,太多容易造成系统死机 ,所以我们要固定进程的创建数量,这个时候借助进程池的帮助。
我们可以认为进程池就是个池子,在这个池子创建好一定数量的进程 。
比如上面这张图中的一个正方形的池子,里面有六个进程,这六个进程会伴随着进程池一起被创建。
我们要知道,普通的进程都要经历创建和关闭,这一建一关都要损耗一定的性能,而进程池中的性能,只需要经历一次创建就能一直往下使用,这也就避免了创建和关闭的性能损耗,伴随着进程池关闭,进程也会随之关闭。
了解了进程池的作用,我们就来了解如何去创建进程池。
函数名 | 介绍 | 参数 | 返回值 |
---|---|---|---|
Pool | 进程池创建 | Processcount | 进程池创建 |
apply_async | 任务加入进程池(异步) | 参数 | 无 |
close | 关闭进程池 | 无 | 无 |
join | 等待进程池任务结束 | 无 | 无 |
进入代码实操。
看看执行效果。
从执行效果来看,我们可以看到,有五个不同的进程id,这说明我们确实是在进程池创建了五个不同的进程,还有就是进程的执行,并没有按照顺序,这是因为它用了异步的处理方法,谁先干完活,谁就去接新的任务。
上面我们是用了sleep 去阻塞主进程的执行效率。因为如果不阻塞的话,主进程立马就执行完毕并关闭。这样子进程根本没有时间执行。
可不可以不用sleep就能实现子进程执行完再关闭,当然可以,这就可以使用到我们的joinl了,使用join往往伴随着 我们的close。
我们来看看代码:
执行效果是一样的,需要注意的是,在某些场景中,我们是需要主进程一直启动着,只要有任务进来就执行,这个时候我们就不需要使用close,但如果是那种一次性的脚本任务,就需要使用到close 去关闭主进程了。
接下来我们来看看如何获得进程中的返回值。
3.2 进程锁
了解完进程池,我们就可以来了解一下进程锁了,其实锁,大家都理解,我们可以给大门上一把锁。
举个栗子,很多人冲向一个厕所,但是厕所只有一个马桶,肯定不能支持这么多人进去,所以第一个人进去之后,就把门锁上了,只有等第一个人解决完之后出来把锁解了,第二个人才能进去,第二个人再把门锁上,后面的以此类推。
了解了进程锁,我们就来看看如果使用进程锁进行加锁与解锁。
函数名 | 介绍 | 参数 | 返回值 |
---|---|---|---|
acquire | 上锁 | 无 | 无 |
release | 开锁(解锁) | 无 | 无 |
使用方式还是很简单的,执行效果得大家去试试看了,它会一个一个的执行,而不是像前面五个五个的执行。
4、进程之间的通信
进程之间的通信依赖于队列,所以我们来看看如何创建队列。
我们依然要用到 multiprocessing这个模块。
函数名 | 介绍 | 参数 | 返回值 |
---|---|---|---|
Queue | 队列的创建 | mac_count | 队列对象 |
put | 信息放入队列 | message | 无 |
get | 获取队列信息 | 无 | str |
# coding:utf-8
import time
import json
import multiprocessing
# 声明一个类
class Work(object):
def __init__(self, q):
self.q = q # 初始化,创建队列
def send(self, message):
if not isinstance(message, str):
message = json.dumps(message)
self.q.put(message) # 信息放入队列
def send_all(self):
for i in range(20):
self.q.put(i) # 信息放入队列
time.sleep(1)
def receive(self):
while 1:
result = self.q.get() # 获取队列信息
try:
res = json.loads(result)
except:
res = result
print('recv is %s' % res)
if __name__ == '__main__':
q = multiprocessing.Queue() # 队列
work = Work(q) # 实例化类
send = multiprocessing.Process(target=work.send, args=({'name': '一切总会归于平淡'},)) # 创建线程
recv = multiprocessing.Process(target=work.receive) # 创建线程
send_all_p = multiprocessing.Process(target=work.send_all) # 创建线程
send.start() # 启动线程
recv.start() # 启动线程
send_all_p.start() # 启动线程
send_all_p.join() # 阻塞执行时间最长的线程
recv.terminate() # 关闭队列
执行结果:
这样我们就通过队列实现进程之间的通信了。
5、线程的创建
在python中有很多的多线程模块,其中最常用的就是 – threading。
函数名 | 说明 | 用法 |
---|---|---|
Thread | 创建线程 | Thread(target,args) |
start | 启动线程 | start() |
join | 阻塞直到线程执行结束 | join(timout=None) |
getName | 获取线程的名字 | getName() |
setName | 设置线程的名字 | setNmae(name) |
is_alive | 判断线程是否存活 | is_alive() |
setDaemon | 守护线程 | setDaemon(True) |
看完上面的介绍,其实大家发现,线程中的方法跟我们进程中的大同小异。
接下来我们看代码演示。
现在我们还没有用到多线程,执行时间是10秒,接下来我们创建线程去提高我们代码执行的效率。
我们再看看执行效果。
哇,只花了一秒就执行完毕了。
6、线程池的创建
线程池和进程池的原理是相同的,这里就不再给大家做解释了。
我们使用Python 的配置包 – concurrent 来帮助我们完成创建下线程池的任务。
方法名 | 说明 | 举例 |
---|---|---|
futures.ThreadPoolExecutor | 创建线程池 | tpool = ThreadPoolExecutor(max_workers) |
submit | 往线程池中加入任务 | submit(target,args) |
done | 线程池中的某个线程是否完成了任务 | done() |
result | 获取当前线程执行任务的结果 | result() |
上代码
7、异步
要了解异步,我们就要只要什么是同步。
平常我们的代码是从上往下执行的,就像 1,2,3,4 …,2要等1 执行完才能轮到2 后面的也都一样,如果其中一个要执行的特别久就容易发生阻塞。
而异步不需要这样,2不需要等1执行2完,它就可以进入执行状态,而且就算其中有一个发生了阻塞,后面的也不会受到影响,听来是不是优点类似与多进程和多线程?
确实如此,异步和多进程和多线程它们类似于兄弟,让我们看看它们之间有何相同有何不同。
首先我们要知道,异步实际上也是一种线程。只不过它是一种比较轻量级的线程,我们将其称为 ‘协程’;所以协程也是进程下的一部分,但和多线程,多进程不同的是,多线程和多进程不能获取函数的返回值,但是异步可以获取。
7.1 async 与 await 关键字
名字 | 说明 | 用法 |
---|---|---|
async | 定义异步 | async def 方法名(): |
await | 执行异步 | async def 方法名(): retult = await 方法名() |
注意 :
使用await,方法必须被 async 定义。
这里有一个问题,我们知道最上层的代码一定不是个函数,它无法定义async,例如我们的主程序。
这里我们要借助一个帮手,asyncio,这个帮手它有太多使用异步的方法,这篇就只介绍两种。
函数名 | 介绍 | 参数 | 返回值 |
---|---|---|---|
gather | 将异步函数批量执行 | asyncfunc | List 函数的返回值 |
run | 执行主导异步函数 | [task] | 执行函数的返回结果 |
看看执行结果:
可以看我们确实是实现了异步操作,而它们都是在同一个进程。
2、gevent
这个模块是需要下载的。
代码语言:javascript复制pip install gevent
函数名 | 介绍 | 参数 | 返回值 |
---|---|---|---|
spawn | 创建协程对象 | Func,args | 协程对象 |
joinall | 批量处理协程对象 | [spawnobj] | [spawnobj] |
执行效果: