02-多任务编程
程序员文章站
2022-10-06 08:19:13
一、多任务 多任务是指在同一时间内执行多个任务,使用多任务就能充分利用CPU资源,提高程序的执行效率 执行方式: 并发:在一段时间内交替去执行任务;;单核cpu处理多任务,操作系统轮流让各个软件交替执行 并行:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的 ......
一、多任务
多任务是指在同一时间内执行多个任务,使用多任务就能充分利用cpu资源,提高程序的执行效率
执行方式:
并发:在一段时间内交替去执行任务;;单核cpu处理多任务,操作系统轮流让各个软件交替执行
并行:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。
cup核心(八核)数大于任务数时——并行,反之,并发
二、进程:实现多任务的一种方式
1.进程:一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
作用:是操作系统进行资源分配的基本单位,每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据,给程序的运行(线程)提供了资源和环境
注意:
一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。
2.import multiprocessing #导入进程包
1.进程类说明
process([group [, target [, name [, args [, kwargs]]]]])
group:指定进程组,目前只能使用none
target:执行的目标任务名--------执行的函数名
name:进程名字
args:以元组方式给执行任务传参;组方式传参一定要和参数的顺序保持一致
kwargs:以字典方式给执行任务传参;字典方式传参字典中的key一定要和参数名保持一致
2.process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
join():等待子进程执行结束;其他进程阻塞在此处,等待该进程执行结束在执行
terminate():不管任务是否完成,立即终止子进程
3.process创建的实例对象的常用属性:
name:当前进程的别名,默认为process-n,n为从1开始递增的整数
4.下面的代码可以看出两个是同时运行的
1 import multiprocessing 2 import time 3 4 5 # 跳舞任务 6 def dance(): 7 for i in range(5): 8 print("跳舞中...") 9 time.sleep(1) 10 11 # 唱歌任务 12 def sing(): 13 for i in range(5): 14 print("唱歌中...") 15 time.sleep(1) 16 17 if __name__ == '__main__': 18 # 创建跳舞的子进程 19 # group: 表示进程组,目前只能使用none 20 # target: 表示执行的目标任务名(函数名、方法名) 21 # name: 进程名称, 默认是process-1, ..... 22 dance_process = multiprocessing.process(target=dance, name="myprocess1") 23 sing_process = multiprocessing.process(target=sing) 24 25 # 启动子进程执行对应的任务 26 dance_process.start() 27 sing_process.start()
3.进程编号:
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。
os.getpid() 当前进程编号
os.getppid() 当前父进程编号
4.注意:
1.进程之间不共享全局变量
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
2.进程之间执行也是无序的
它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
为什么无序:因为程序执行之前要先用cpython解释器,不管有几核的cpu,它的全局解释器锁(gil)每次只允许通过一个线程通过,所以python是个假的多任务
3.主进程会等待所有的子进程执行结束再结束
若想让主进程和子进程同步结束,可以设置:守护主进程、子进程销毁
设置守护主进程方式: 子进程对象.daemon = true
进程对象 = multiprocessing.process(target=show_info, daemon=true)
销毁子进程方式: 子进程对象.terminate()
eg:
1 2 import multiprocessing 3 import time 4 5 # 定义进程所需要执行的任务 6 def task(): 7 for i in range(10): 8 print("任务执行中...") 9 time.sleep(0.2) 10 11 if __name__ == '__main__': 12 # 创建子进程 13 sub_process = multiprocessing.process(target=task) 14 # 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程 15 # sub_process.daemon = true 16 sub_process.start() 17 18 time.sleep(0.5) 19 print("over") 20 # 让子进程销毁 21 sub_process.terminate() 22 exit() 23
三、线程:多线程可以完成多任务
1.线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。
线程是python程序中实现多任务的另外一种方式,线程的执行需要cpu调度来完成。
2.线程模块:import threading
线程类:thread([group [, target [, name [, args [, kwargs]]]]])
name: 线程名,一般不用设置;其他属性同进程类
线程传参:同进程
3.注意:
1.线程之间执行是无序的
线程之间执行是无序的,它是由cpu调度决定的,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
1 import threading 2 import time 3 4 def task(): 5 time.sleep(1) 6 print("当前线程:", threading.current_thread().name) 7 8 if __name__ == '__main__': 9 for _ in range(5): 10 sub_thread = threading.thread(target=task) 11 sub_thread.start()
2.主线程会等待所有的子线程执行结束再结束
设置守护主线程(主线程退出子线程销毁)有两种方式:
线程对象 = threading.thread(target=show_info, daemon=true)
线程对象.setdaemon(true)
线程没有terminate()函数
拓展:守护线程:
其工作方式是:守护线程一般是一个等待客户端请求服务的服务器,如果没有客户端请求,守护线程就闲置。如果把一个线程设置成守护线程,就表示这个线程是不重要的,程序退出时不需要等待这个线程执行完成
3.线程之间共享全局变量:数据共享
共享全局变量数据出现错误问题:
线程同步:保证同一时刻只能有一个线程去操作全局变量,同步:就是协同步调,按预定的先后次序进行运行。
实现方式:
1.线程等待(join):a.start() a.join() b.start()
2.互斥锁
四、互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
threading模块中定义了lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
使用步骤:
# 创建锁 mutex = threading.lock() # 上锁 mutex.acquire() ... 这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定... # 释放锁 mutex.release()
注意:
1.acquire和release方法之间的代码同一时刻只能有一个线程去操作
2.如果在调用acquire方法的时候其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
eg:
1 import threading 2 3 4 # 定义全局变量 5 g_num = 0 6 7 # 创建全局互斥锁 8 lock = threading.lock() 9 10 # 循环一次给全局变量加1 11 def sum_num1(): 12 # 上锁 13 lock.acquire() 14 for i in range(1000000): 15 global g_num 16 g_num += 1 17 18 print("sum1:", g_num) 19 # 释放锁 20 lock.release() 21 22 # 循环一次给全局变量加1 23 def sum_num2(): 24 # 上锁 25 lock.acquire() 26 for i in range(1000000): 27 global g_num 28 g_num += 1 29 print("sum2:", g_num) 30 # 释放锁 31 lock.release() 32 33 if __name__ == '__main__': 34 # 创建两个线程 35 first_thread = threading.thread(target=sum_num1) 36 second_thread = threading.thread(target=sum_num2) 37 # 启动线程 38 first_thread.start() 39 second_thread.start() 40
小结:
互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
互斥锁如果没有使用好容易出现死锁的情况
死锁:一直等待对方释放锁的情景就是死锁
使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。
死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了(程序不会终止,不会报错,只是一直停在无法执行处,阻塞住,必须手动停止)。
五、进程和线程的对比
1.关系对比
线程是依附在进程里面的,没有进程就没有线程。
一个进程默认提供一条线程(主线程),进程可以创建多个线程。
2.区别对比
进程之间不共享全局变量
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
创建进程的资源开销要比创建线程的资源开销要大
进程是操作系统资源分配的基本单位,线程是cpu调度的基本单位
线程不能够独立执行,必须依存在进程中
多进程开发比单进程多线程开发稳定性要强
3.优缺点对比
进程优缺点:
优点:可以用多核
缺点:资源开销大
线程优缺点:
优点:资源开销小
缺点:不能使用多核
4.小结
进程和线程都是完成多任务的一种方式
多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
多进程可以使用cpu的多核运行,多线程可以共享全局变量。
线程不能单独执行必须依附在进程里面
下一篇: Nginx反向代理