哈哈哈,标题有点长,但我们依然由浅入深。
# 通常情况下需要我们的程序同时进行多个任务,并发运行。由于cpu的执行效率非常高,时间片非常短,在各个任务之间快速的切换,给人的感觉就是多个任务在同时进行,并发运行。
# 进程和进程之间的区别
# 1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
# 2、一个进程是由一个或多个线程组成,线程是一个进程中代码的不同执行路线
# 3、进程之间相互独立,但同一个进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其他进程是不可见的
# 4、调度和切换:线程上下文切换比进程上下文切换要快得多
# 总之,进程与线程都是一种抽象的概念,线程是一种比进程更小的抽象,线程和进程都可用于实现并发
# 可以这样想象为,一个APP在手机中运行,这一个APP是一个进程,对于这个APP而言,我们在内部要加载很多页面,每一个页面都是一个线程,一个手机中又可以有很多APP,即有很多进程。
单线程
先看个例子:单线程顺序执行。
多线程
# 在Python的标准库中有两个模块,thread 和threading,thread 是低级模块,threading是高级模块,对thread进行了封装。 一般情况下,我们只使用threading这个模块。
主线程
上述例子我们创建了两个新的线程,其实当我们运行这个Python程序的时候,存在一个主线程。
看个例子更好说明。
# 出现这种情况是因为: 是在python程序的主线程中,t1,t2 是我们自己创建的新线程。不是一个线程,大家都是同步执行的。要想达到我们在音乐和电影线程都结束,再执行主线程那么我们需要阻塞主线程。
这是想要主线程等待其他线程执行完再执行,通过join()阻塞主线程。
但是如果我没想执行完主线程,就将其他的线程结束,这时候就需要setDaemon(boolean)方法守护线程。
这里就可以讲讲为什么一般情况下,我们只使用threading这个模块?
# thread不支持守护线程,当主线程退出时,所有的子线程不管他们是否还在工作,都会被强行退出, threading模块支持守护线程。
锁
看个不加锁的例子
在多线程对同一个全局变量进行操作的时候,t1在未执行完对balance 的操作 t2抢占执行,t2未执行完,t1又开始抢占资源。因为两个线程公用一个资源,所以给改差皮了。
这时候我们需要加一个锁。
# 在Python中不建议多线程,建议多进程。
# 在Python中,无论你写多少个线程,在唯一时间点上只有一个线程在运行,其他的都在等待
# 即保证同一时刻只有一个线程对共享资源进行存取。(GIL全局解释锁),Python 大锁。
单进程
多进程
# multiprocessing库的出现很大程度上是为了弥补threading库因为GIL低效的缺陷。 它完整的复制了一套threading所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,完全并行,无GIL的限制(进程中包括线程),可充分利用多cpu多核的环境,因此也不会出现进程之间的GIL争抢。
锁
两个进程虽然同时开启,但是进程1对公共资源修改完成,进程2再开始修改。
进程池
先介绍两个概念:
# 同步执行:一个进程在执行任务时,另一个进程必须等待执行完毕,才能继续执行, 加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。# 异步执行:一个进程在执行任务时,另一个进程无需等待其执行完毕就可以执行, 当有消息返回时,系统会提醒后者进行处理,这样会很好的提高运行效率。
通过执行结果可以看出,每次执行2个子进程,当有一个子进程执行完毕,会有一个新的子进程进入进程池开始执行。极大的优化效率。
# Pool可以提供指定数量的进程,供用户调用。当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行它。
跨进程通信
两种方式:队列( Queue );管道 (Pipe)
注意:
terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁。
线程间通信
# 通过队列 queue.Queue()
# 如果生产者的速度 远远大于,消费者的速度 ,生产者只会多生产10个,之后,消费者每消费一个,生产者生产一个。如果生产者的速度小于消费者,消费者线程阻塞,等待生产者生产后,消费者再消费。