Python多进程
进程
一系列有序指令集用来实现某些功能,每个进程都有自己独立的一块内存空间,一个进程可以有多个线程.
程序就是我们安装好的应用,我们打开程序时,就创建了相应的进程,操作系统会为进程分配相应的资源
线程
是进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享进程分配的资源和数据。
因此,我们在切换线程时的负担比切换进程小很多,毕竟线程时共享资源的
两者关系
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位,也是cpu可调度的最小单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
多进程库
python中想要创建进程需要使用multiprocessing库,我们只有用其中的Process类来创建进程
import multiprocessing
procsess1=Process(group,target,name,args,kwargs)
因为不学所以这里带过
并发和并行
并发是指
两个时间同时发生,但是进行时交替执行
并行是指
多个任务同时执行
多线程库
python中想要创建线程需要使用threading库,我们只有用其中的Process类来创建进程
import threading
我们有两种方式创建线程
函数式:
t1=Thread(target,name,args,kwargs)
target: 线程将要执行的目标函数。
name: 线程的名称。
args: 目标函数的参数,以元组形式传递。
kwargs: 目标函数的关键字参数,以字典形式传递。
继承式:
class SubThread(Thread):def run:print('ok')
启动
我们创建了线程后,我们想要线程运行起来,需要用到线程的start方法
线程名.start()
start运行的是target传过去的函数,或者是新创建子类里的run函数
也就是说,基于Thread类的线程都会执行run函数
而且每个线程只能start一次,多了会报错
阻塞
当多个线程运行时,它们的先后是由cpu调度决定的,我们不可控
我们有时需要线程由先后的顺序,调用thread.join()的作用是确保thread子线程执行完毕后才能执行下一个线程
线程名.join(times)
join的作用是等待线程执行完它的任务然后销毁线程
有参数的话,最多等待线程执行times秒,之后不管线程有没有执行完都会销毁
join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。确保其他的正常代码不会在线程没有执行完时就执行
共享
需要注意的是,我们在创建线程时,如果需要一个共享的量,那么我们需要将其设为全局变量
global gonggong
但这也会导致另一个问题,就是一个变量在操作前会同时被两个线程使用,会出现类似同时卖出第n张票的情况
Lock锁
锁对象可以看作把共享资源上锁,在被锁定时,别的线程不能进来操作,只有在锁被release之后才可以继续
我们一般再target函数或者子类的run里面用锁
一般是在操作全局变量那里锁上,操作完再释放,不要锁循环
我们在使用锁的时候,需要创建一个锁对象
lock1=threading.Lock()#创建锁
lock1.acquire()#上锁
lock1.release()#解锁
用锁会导致线程速度变慢,而且可能会只有部分线程被调度,但是可以保护数据
经典线程模型—生产者消费者
由两个线程,一个执行存入操作,一个执行取出操作,当没有的时候取出会等待存入先操作
这种实现需要一个线程的队列优先级(Queue)
内置模块Queue
import queue
我们使用时需要先创建一个队列
queue1=queue.Queue(max)
#max表示队列的最大容量
往往这个队列要作为参数传入函数中
Queue.get() 从队列中取出元素遵循fifo规则,即先进先出,如果队列为空,get()方法会阻塞直到有元素可用。
Queue.put(item) 写入队列,如果队列已满,put()方法会阻塞直到有空余空间。
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号,每完成一个调用一次
Queue.join() 实际上意味着等到队列为空(等到队列操作完了),再执行别的操作
Queue.empty() 判断是不是空的,是返回true,不是返回false
如果线程里每从队列里取一次,但没有执行task_done(),则join无法判断队列到底有没有结束,在最后执行个join()是等不到结果的,会一直挂起。
可以理解为,每task_done一次 就从队列里删掉一个元素,这样在最后join的时候根据队列长度是否为零来判断队列是否结束,从而执行主线程。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/sjyttkl/article/details/79887720
queue可以避免手动上锁,非常豪用