多任务概念
同一时间执行多个任务
多任务优势
最大的好处是充分利用CPU资源,提高程序的执行效率
GIL锁(全局解释锁)
让一个进程中同一个时刻只有一个线程可以被CPU调用,可以解决线程安全问题,有线程锁也有进程锁
Rlock:递归锁
lock:同步锁
多任务的两种表现方式
并发:在一时间段内交替去执行多个任务
如:对于单核cpu处理多任务,操作系统轮流让各个任务交替执行
并行:在一段时间内真正的同时一起执行多个任务
如:对于多核CPU处理多任务,操作系统会给CPU的每个内核安排一个执行的任务,多个内核是真正的一起同时执行多个任务。这里需要注意多核CPU的并行的执行多任务,始终有多个任务一起执行。
程序中实现多任务的方式
进程
进程的概念
进程是资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,通俗理解:一个正在运行的程序就是一个进程。例如:正在运行的QQ微信等,它们都是一个进程。
多进程的作用
充分利用CPU资源,提高程序的执行效率,进程与进程之间相互隔离
进程间的数据共享
进程是资源分配的最小单元,每个进程中都维护自己独立的数据,不共享。如果想要它们之间进行共享,则可以借助一些特殊的东西来实现。
多进程完成多任务
进程的三步创建步骤
通过进程类创建进程对象
进程对象=multiprocessing.Process(target=任务名)
进程创建与启动的代码
进程执行带有参数的任务
除了target参数还有另外两种参数
参数args(以元组方式),其中元组的顺序就是任务的参数的顺序
参数kwargs(以字典方式),其中传参字典的key一定要和参数名保持一致
获取进程编号
进程编号的作用:当程序中进程的数量越来越多时,如果没有办法区分主进程和子进程,还有不同的子进程,那么就无法进行有效的进程管理,为了方便管理,实际上每个进程都是有自己编号的。
获取当前进程的编号
os.getpid()
获取当前进程父进程的编号
os.getppid()
常见方法
p.start():当前进程准备就绪,等待CPU调度(工作单元其实是进程中的线程)
p.join():等待当前进程的任务执行完毕后再向下继续进行
p.setFaemon(布尔值):守护进程(必须放在start之前)
p.setFaemon(布尔值):设置为守护进程,主进程执行完毕后,子进程也会自动关闭
p.setFaemon(布尔值):设置为非守护进程,主进程等待子进程,子进程执行完毕后,主进程才会结束(默认)
p.name=“xxx”:进程的名称和设置
multiprocessing.current_process().name:获取当前执行代码的进程名
os.getpid():获取当前进程id
os.getppid():获取当前进程父进程的id
len(threading.enumerate()):获取当前进程中的线程个数
multiprocessing.cpu_count():获取当前cpu个数
进程的注意点
1)主进程会等待所有的子进程执行结束再结束
举例:虽然“主进程执行完成了哦”可能夹在“工作中...”,但是它输出后函数并没有结束,子进程还在执行,待子进程结束后,函数才终止
2)设置守护主进程
举例:加了这行代码后,一旦输出“主进程完成了哦”,就代表函数的终止,子进程会自动销毁,不会在执行
进程池
进程不是开得越多越好,线程池里的线程个数要适量
不建议:无限制的创建进程
建议:使用进程池
线程
线程的概念
线程是程序执行的最小单位,实际上进程只负责分配资源,而利用这些资源执行程序的是线程,也就说进程是线程的容器,一个进程中最少有一个线程来负责执行程序。同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
为什么使用线程
进程是分配资源的最小单位,一旦创建一个进程就会分配一定的资源,就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源
但是线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源,这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样,实现多任务的同时也节省了资源。
多线程的作用
相比进程,实现多任务的同时也节省了资源
多线程完成多任务
线程的三步创建步骤
通过进程类创建线程对象
线程对象=threading.Thread(target=任务名)
线程创建与启动的代码
线程执行带有参数的任务
除了target参数还有另外两种参数
参数args(以元组方式),其中元组的顺序就是任务的参数的顺序
参数kwargs(以字典方式),其中传参字典的key一定要和参数名保持一致
主线程和子线程的结束顺序
主线程会等待所有的子线程结束后再结束
设置守护主线程:可以让主线程不等待子线程执行完成
线程间的执行程序
线程之间是无序的,是由CPU调度决定某个线程先执行的
获取当前的线程信息
常见方法
t.start():当前线程准备就绪(等待CPU调度,具体时间由CPU来决定)
t.join():等待当前线程的任务执行完毕后再向下继续进行
t.setFaemon(布尔值):守护线程(必须放在start之前)
t.setFaemon(布尔值):设置为守护线程,主线程执行完毕后,子线程也会自动关闭
t.setFaemon(布尔值):设置为非守护线程,主线程等待子线程子线程执行完毕后,主线程才会结束(默认)
t.setName():线程的名称和设置
t.threading.current_thread().getName():获取当前执行代码的线程名
线程池
线程不是开得越多越好,开的多了可能会导致系统的性能更低了。
不建议:无限制的创建线程
建议:使用线程池
进程和线程对比
关系对比
线程是依托在进程里面的,没有进程就没有线程
一个进程默认提供一条线程,进程可以创建多个线程
区别对比
创建进程的资源开销要比创建线程的资源开销大
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
线程不能够独立运行,必须依托在进程中
优缺点对比
进程优缺点
优点:可以使用多核
缺点:资源开销大
线程优缺点
优点:资源开销小
缺点,不能使用多核
应用场景
如果每个子进程执行需要消耗的时间非常短(执行+1操作等),这不必使用多进程,因为进程的上下文切换(启动关闭)也会耗费资源
使用多进程往往是用来处理CPU密集型(科学计算)的需求,如果是IO密集型(文件读取,爬虫等)则可以使用多线程去处理
想利用计算机的多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)
不利用计算机的多核优势,适用于多线程开发