0x00-前言
其实关于Python多线程、多并发的学习以及开发,在很早之前就学过部分,早已有些淡忘了,时至今日,因为开发脚本需要用到多并发,于是再次拾起,并撰写出这篇文档,以供日后自我学习,疏漏之处,还望指正!
0x01-所涉名词概要
进程(Process)
首先说说操作系统:计算机的核心是CPU
,它是中央处理器,承担了所有的计算任务,而操作系统是计算机的管理者,提供友好的页面,负责任务的调动,资源的分配,管理着整个计算机。而应用程序是举有某种功能的程序,是运行于操作系统上的
进程是一个举有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进程资源分配和调度运行的基本单位是一个抽象的概念,从来没有统一的标准定义,一般是程序,数据集合,进程控制块三部分组成:
程序
用于描述进程要完成的功能,是控制进程执行的指令集数据集合
是程序在执行时所需要的数据和工作区程序控制块
包含进行的描述信息和控制信息,是进程存在的唯一标识
进程具有的特征:
动态性
:进程是程序在操作系统上的一次执行过程,是临时且有生命力的,会动态产生,也会动态消亡并发性
:任何进程都可以同其他程序一起执行(并发)独立性
:进程是系统进行资源分配和调度的独立单位结构性
:进程有程序,数据,进程控制块三部分组成
线程(thread)
早起操作系统没有线程的概念,进程是有用资源和独立运行的最小单位,也是程序执行的最小单位,每个进程都有独立的一块内存,使各个进程之间内存地址相互隔离,互不干扰。
随着计算机发展,对CPU
要求越来越大,进程之间切换开销大,已经无法满足越来越复杂的程序要求,于是诞生了
线程是程序执行中单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分配的基本单元
一个进程可以拥有一个或多个线程,当最后一个线程执行完毕,进程消亡。
各个进程之间共享程序的内存空间(也就是进程的内存空间)
一个标准的线程由:线程ID,当前指令指针PC,寄存器和堆栈组组成,而进程由内存空间(代码,数据,进程空间,打开的文件)和一个到多个线程组成
线程与进程的区别:
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
- 一个进程有一个到多个线程组成,线程是一个进程中代码的不同执行路线
- 进程之间相互独立,但是同一进程下各个线程之间共享程序的内存空间,但与其他进程下线程不互通
- 线程上下文切换比进程要快得多
- 总之,线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。
最后关于为什么使用多线程而不是多进程:
线程廉价,线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且线程彼此分享了大部分核心对象(File Handle)的拥有权,如果使用多重进程,但是不可预期,且测试困难
多任务
多任务是指同一时间内执行多个任务
对于多任务有俩中表达方式:
- 多并发
在一段时间内交替去执行多个任务。
对于单核CPU处理多任务,由于交替太块,根本察觉不到,就会以为是同一时间执行了多个任务
- 并行
在一段时间内真正的同时一起执行多任务
对于多核CPU处理任务,操作系统会给CPU的每个内核安排一个任务,无需交替,是真正意义上的一起执行
多个任务,多核CPU是并行执行多任务,始重有多个任务一起执行
0x03-通过多进程完成多任务
Python中进程的创建步骤:
- 导入进程包
import multiprocessing
- 通过进程类创建进程对象
进程对象 = multiprocessing.Process(target=任务名)
target
:执行目标任务名,这里指函数名(也就是方法名)name
:进程名,一般不用设置group
:进程组,目前只能使用None
- 启动进程执行任务
进程对象.start()
最简单一个多进程完成多任务的例子:
#1. 导入进程库
import multiprocessing
import time
#猫函数
def cat():
for i in range(4):
print("喵喵喵~")
time.sleep(0.5)
#狗函数
def dog():
for i in range(4):
print("汪汪汪^")
time.sleep(0.5)
if __name__ == '__main__':
#2. 利用进程类创建进程对象
process_cat = multiprocessing.Process(target=cat)
process_dog = multiprocessing.Process(target=dog)
#3. 启动多进程
process_cat.start()
process_dog.start()
进程类创建带参数的进程对象
进程类创建进程对象的函数名传参有两种方式:
元组传参:进程对象 = multiprocessing.Process(target=任务名,args=(x,))
这里元组传参,x为对应函数参数的值,类型也与本身函数形参类型一致,必须要按照本身函数的形参的顺序传值
字典传参:进程对象 = multiprocessing.Process(target=任务名,kwargs={"x":y})
这里字典传参,x为对应函数形参名,y为参数的值,可以不需要按函数本身顺序传值,但参数名必须相同
获取进程的PID与父进程的PID
需要导入os库import os
获取当前进程的PID:os.getpid()
获取父进程的PID:os.getppid()
在cat
函数中添加:
print("cat进程PID:",os.getpid())
print("cat进程的父进程PID:",os.getppid())
在dog
函数中也同样如此,顺便在主进程中也输出当前进程PID:print("主进程PID:",os.getpid())
,运行结果如下:
通过结果说明,cat
和dog
进程都是主进程下创建的子进程
tips:
主进程会等待所有子进程执行结束后再销毁结束,如果不想这么做,可以设置子进程守护主进程进程对象.deamon = True
这样当主进程运行完毕后,子进程随之结束,不在继续执行
0x04-通过多线程完成多任务
Python中进程的创建步骤:
- 导入线程包
import threading
- 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
target
:执行目标任务名,这里指函数名(也就是方法名)name
:线程名,一般不用设置group
:线程组,目前只能使用None
- 启动线程执行任务
线程对象.start()
最简单一个多线程完成多任务的例子:
#1. 导入线程库
import threading
import time
#猫函数
def cat():
for i in range(4):
print("喵喵喵~")
time.sleep(0.5)
#狗函数
def dog():
for i in range(4):
print("汪汪汪^")
time.sleep(0.5)
if __name__ == '__main__':
#2. 利用线程类创建进程对象
cat_threading = threading.Thread(target=cat)
dog_threading = threading.Thread(target=dog)
#3. 启动多线程
cat_threading.start()
dog_threading.start()
线程类创建带参数的线程对象
线程类创建进程对象的函数名传参有两种方式:
元组传参:线程对象 = threading.Thread(target=任务名,args=(x,))
这里元组传参,x为对应函数参数的值,类型也与本身函数形参类型一致,必须要按照本身函数的形参的顺序传值
字典传参:线程对象 = threading.Thread(target=任务名,kwargs={"x":y})
这里字典传参,x为对应函数形参名,y为参数的值,可以不需要按函数本身顺序传值,但参数名必须相同
tips:
主线程和子线程的运行顺序:主线程执行完后会等待所有子线程执行完毕后,再销毁
如果不想这么做,当主线程执行完毕,直接销毁,可以设置线程守护:
- threading.Thread(target=work,
deamon=True
)#传入deamon参数为True - 线程对象.
setDeamon(True)
#使用线程对象,调用setDeamon方法线程间的执行顺序
其实并不是根据线程的创建的先后顺序来判断执行顺序的,线程间的执行顺序是无序的
使用threading.current_thread()
获取子进程中的对象信息 ```python import threading import time def speak(): time.sleep(1) thread = threading.current_thread() print(thread)
if name == ‘main‘:
for i in range(5):
speak_threading = threading.Thread(target=speak)
speak_threading.start()
```
可以看到,延迟一秒后,子线程4比线程5率先执行,所以线程中的执行顺序是无序的,由CPU调度为准
多进程和多线程的取决
- 线程是依附于进程的,线程无法独立运行,没有进程就没有线程
- 一个进程默认提供一条线程,但一个进程可以存在多个线程
- 进程创建的资源开销大,线程创建的资源开销小
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 进程的优缺点:
可以使用多核,但资源开销大
- 线程的优缺点:
无法使用多核,但资源开销小