前言
本文详细介绍了进程与线程的概念与对比,两者作用都为提高程序的执行效率。
提示:以下是本篇文章正文内容,下面案例可供参考
多任务介绍
-
比如百度网盘的并行下载,操作系统都是多任务操作系统
-
好处:利用cpu资源,提高程序执行效率
两种表现形式:
- 并发:在一段时间内交替去执行多个任务(任务数量>cpu的核心数)
- 例子:多个任务交替执行,A0.01s->B0.01s->C0.01s->A0.01s
- 并行:同一段时间内真正的同时一起执行多个任务(任务数量<=cpu的核心数)
- 例子:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的任务,多个内核一起同时执行多个任务
进程的介绍
实现多任务的方式是使用进程
def:进程(Process)是资源分配的最小单位,他是操作系统进行资源分配和调度运行的基本单位,通俗理解,一个正在运行的程序就是一个进程。
- 例如:微信,QQ(任务管理器可查看)
多进程的作用(实现多任务的一种方式):提升程序执行效率
多进程完成多任务
-
进程的创建步骤
- 导入进程包
- import multiprocessing
- 通过进程类创建进程对象
- 进程对象 = multiprocessing.Process()
- 启动进程执行任务
- 进程对象.start()
- 导入进程包
-
通过进程类创建进程对象
进程对象 = multiprocessing.Process(target = 任务名)
参数名 说明 target 执行的目标任务名,这里指的是函数名(方法名) name 进程名,一般不设置 group 进程组,目前只能使用None -
进程创建与启动的代码
# 创建子进程 coding_process = multiprocessing.Process(target = coding) # 创建子进程 music_process = multiprocessing.Process(target = music) # 启动进程 coding_process.start() music_process.start()
-
案例
import time import multiprocessing #编写代码 def coding(): for i in range(3): print("Coding...") time.sleep(0.2) def music(): for i in range(3): print("music...") time.sleep(0.2) if __name__ == '__main__': # coding() # music() # 创建进程对象 coding_process = multiprocessing.Process(target=coding) music_process = multiprocessing.Process(target=music) #启动进程 coding_process.start() music_process.start()
#执行结果如下 music... Coding... music... Coding... music... Coding...
-
知识要点
牢记创建进程顺序
-
进程执行带有参数的任务
args:元组形式(注意参数顺序)
kwargs:字典形式(注意key要与形参名一致)
import time import multiprocessing #编写代码 def coding(num, name): for i in range(num): print(name) print("Coding...") time.sleep(0.2) def music(count): for i in range(count): print("music...") time.sleep(0.2) if __name__ == '__main__': # coding() # music() # 创建进程对象 coding_process = multiprocessing.Process(target=coding, args = (3,"lalal")) music_process = multiprocessing.Process(target=music,kwargs = {"count" : 2}) #启动进程 coding_process.start() music_process.start()
-
获取进程编号
-
获取当前进程编号
getpid() 方法
-
获取当前父进程编号
getppid() 方法
-
范例
import time import multiprocessing import os def work(): #获取当前进程的编号 print("work进程编号:",os.getpid()) #获取当前父进程的编号 print("work进程编号:",os.getppid()) #编写代码 def coding(num, name): print("coding>>%d:"%os.getpid()) print("coding_father>>%d:"%os.getppid()) for i in range(num): print(name) print("Coding...") time.sleep(0.2) def music(count): print("music>>%d:"%os.getpid()) print("music_father>>%d:"%os.getppid()) for i in range(count): print("music...") time.sleep(0.2) if __name__ == '__main__': # coding() # music() # 创建进程对象 # coding_process = multiprocessing.Process(target=coding, args = (3,"lalal")) music_process = multiprocessing.Process(target=music,kwargs = {"count" : 2}) #启动进程 coding_process.start() # music_process.start() #
-
范例中共有三个进程,程序开始创建主进程,然后创建coding与music两个子进程。参考执行结果,两个子进程的父进程编号相同。
>>> music>>30304: coding>>43616: coding_father>>32496: lalal Coding... music_father>>32496: music... lalalmusic... Coding... lalal Coding...
-
-
进程间不共享全局变量
-
实际上创建一个子进程就是把主进程的资源进行拷贝产生了一个新的进程,这里主进程和子进程是相互独立的。
-
范例:
#定义global varible import multiprocessing import time my_list = list() def write_data(): for i in range(3): my_list.append(i) print("add",i) print("write_data",my_list) def read_data(): print("read_data",my_list) if __name__ == "__main__": #创建写入数据的进程 write_process = multiprocessing.Process(target = write_data) read_process = multiprocessing.Process(target = read_data) #启动进程 write_process.start() time.sleep(1) #主进程等待写入进程执行完成以后的代码,再继续向往下执行 #write_process.join() read_process.start()
#执行结果 add 0 add 1 add 2 write_data [0, 1, 2] >>> read_data []
执行结果表明,子进程之间是相互独立的,不会共享全局变量。就好比孙悟空的两个汗毛变成的小猴子。创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比一对双胞胎,之所以进程不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过是不同进程里面的全局变量名字相同而已。
-
-
主进程和子进程结束顺序
-
例如微信:
- 主进程打开微信,开启子进程聊天窗口,关闭微信时,先关闭聊天窗口再关闭微信。
-
范例:
def work(): for i in range(10): print("工作中。。") time.sleep(0.2) if __name__ == '__main__': # 创建进程对象 work_process = multiprocessing.Process(target=work) # 启动进程 work_process.start() time.sleep(1) print("主进程结束")
#执行结果表明:主进程会等待子进程结束后,再终结程序 工作中。。 工作中。。 工作中。。 工作中。。 主进程结束 工作中。。 工作中。。 工作中。。 工作中。。 工作中。。 工作中。。
-
设置守护主进程与销毁子进程(可以保证主进程结束后子进程销毁)
-
范例:
-
设置守护主进程后,主进程退出后子进程直接销毁,不再执行子进程中的代码。
def work(): for i in range(10): print("工作中。。") time.sleep(0.2) if __name__ == '__main__': # 创建进程对象 work_process = multiprocessing.Process(target=work) # 启动守护主进程 work_process.daemon = True # 启动进程 work_process.start() time.sleep(1) print("主进程结束")
if __name__ == '__main__': # 创建进程对象 work_process = multiprocessing.Process(target=work) # 启动进程 work_process.start() time.sleep(1) # 手动销毁子进程 work_process.terminate() print("主进程结束")
#上述代码得可直接结束主进程 工作中。。 工作中。。 工作中。。 工作中。。 主进程结束
线程的介绍
进程是资源分配的最小单位,一旦创建一个进程就会分配一定的资源,就像两个人聊QQ就需要打开两个qq软件。
-
为什么使用多线程:
线程是程序执行的最小单位,实际上进程只负责分配资源,而利用这些资源执行程序的是线程,也就说进程是线程的容器,一个进程中最少有一个线程来负责执行程序,同时线程自己不拥有系统资源,只需要一点在运行中必不可少的资源,但他可与**同属一个进程的其他线程共享进程所拥有的全部资源**。这就像通过一个QQ软件(一个进程),打开多个窗口(多个线程)跟多个人聊天一样,实现多任务的同时也节省了资源。
-
多线程的作用
多线程完成多任务
-
线程的创建步骤
-
导入线程模块
import threading
-
通过线程类创建线程对象
线程对象 = threading.Thread(target = 任务名)
参数名 说明 target 执行的目标任务名,这里指的是函数名(方法名) name 进程名,一般不设置 group 进程组,目前只能使用None -
启动线程执行任务
线程对象.start()
-
-
线程创建和启动的代码
# 创建子进程 coding_process = threading.Thread(target = coding) # 创建子进程 music_process = threading.Thread(target = music) # 启动进程 coding_process.start() music_process.start()
-
案例
import time import threading import os #编写代码 def coding(): for i in range(3): print("Coding...") time.sleep(0.2) def music(): for i in range(3): print("music...") time.sleep(0.2) if __name__ == '__main__': # coding() # music() #创建子线程 coding_thread = threading.Thread(target = coding) #创建子线程 music_thread = threading.Thread(target=music) coding_thread.start() music_thread.start()
#执行结果 Coding... music... Coding... music... Coding... music...
-
线程执行带有参数的任务(同进程,不作范例)
-
主线程和子线程的结束顺序
-
主线程会等待所有的子线程完毕再结束
-
范例
import time import threading import os def work(): for i in range(10): print("工作中。。") time.sleep(0.2) if __name__ == '__main__': # coding() # music() # 创建进程对象 # work_thread = threading.Thread(target=work) #启动进程 work_thread.start() time.sleep(1) print("主线程结束")
#结果同进程范例 工作中。。 工作中。。 工作中。。 工作中。。 工作中。。 主线程结束 工作中。。 工作中。。 工作中。。 工作中。。 工作中。。
-
-
设置守护主线程 与 销毁子线程(同进程范例)
import time import threading def work(): for i in range(10): print("work..") time.sleep(0.2) if __name__ == "__main__": work_thread = threading.Thread(target = work, daemon= True) work_thread.start() time.sleep(1) print("主线程执行完毕")
import time import threading def work(): for i in range(10): print("work..") time.sleep(0.2) if __name__ == "__main__": work_thread = threading.Thread(target = work) work_thread.setDaemon(True) work_thread.start() time.sleep(1) print("主线程执行完毕")
设置守护主线程的目的是主线程退出子线销毁,不让主线程在等待子线程去执行
设置守护主线程有两种方式:
1.threading.Thread(tarhet = name, daemon = True)
2.线程对象.setDaemon(True)
-
线程间的执行顺序
-
线程执行时无序的,由CPU调度决定某线程先执行
-
获取当前的线程信息
# 通过current_thread方法获取线程对象 current_thread = threading.current_thread() # 通过打印current_thread可以知道当前的线程信息,例如被创建的顺序 print(current_thread)
import time import threading def get_info(): time.sleep(0.5) # 获取线程信息 current_thread = threading.current_thread() print(current_thread) if __name__ == '__main__': # 创建子线程 for i in range(10): sub_thread = threading.Thread(target = get_info) #注意是函数名称 sub_thread.start()
#结果 <Thread(Thread-1, started 20796)> <Thread(Thread-2, started 20740)> <Thread(Thread-3, started 38556)> <Thread(Thread-5, started 27744)> <Thread(Thread-4, started 13812)> <Thread(Thread-9, started 41968)> <Thread(Thread-6, started 26052)> <Thread(Thread-7, started 11520)> <Thread(Thread-8, started 10692)> <Thread(Thread-10, started 36928)>
-
-
线程间共享全局变量
import threading import time # 定义global varible my_list = list() def write_data(): for i in range(3): my_list.append(i) print("add", i) print("write_data", my_list) def read_data(): print("read_data", my_list) if __name__ == "__main__": # 创建写入数据的进程 write_thread = threading.Thread(target = write_data) read_thread = threading.Thread(target = read_data) # 启动进程 write_thread.start() time.sleep(1) # 主进程等待写入进程执行完成以后的代码,再继续向往下执行 # write_thread.join() read_thread.start()
# 执行结果,验证了线程间是共享全局变量的 add 0 add 1 add 2 write_data [0, 1, 2] read_data [0, 1, 2]
-
线程之间共享全局变量数据出现错误问题
-
需求
- 定义两个函数,实现100万次循环,每循环一次后给全局变量+1
- 创建两个子线程执行对应的两个函数,查看计算后的结果
glob_v = 0 # 对全局变量加1 def sum_num1(): for i in range(1000000):# 分别测试 十万 与 一百万 global glob_v glob_v += 1 print("glob_v1",glob_v) def sum_num2(): for i in range(1000000): global glob_v glob_v += 1 print("glob_v2",glob_v) if __name__ == "__main__": sum1_thread = threading.Thread(target=sum_num1) sum2_thread = threading.Thread(target=sum_num2) # 启动线程 sum1_thread.start() sum2_thread.start()
#执行结果 # 十万 glob_v1 100000 glob_v2 200000 # 百万(出现错误) glob_v1 1166860 glob_v2 1260019
线程在工作过程中,一个线程在对全局变量处理的过程中,另一个线程也可能对其进行处理,导致加1操作执行了了两次,但实际值只增加了1.
-
解决方案
- 线程同步,协调同步,按预定的先后次序进行运行,好比现实生活中的对讲机,半双工通信
- 使用线程同步:保证同一时刻只能有一个线程去操作全局变量
-
-
线程同步方式:互斥锁
-
def :对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
-
注:互斥锁是多个线程一起去抢,抢到锁的先执行,没抢到锁的线程进行等待,等所使用完释放后,其他等待的下次再去抢这个锁。
-
使用步骤
-
互斥锁的创建
mutex = thread.Lock()
-
上锁
mutex.acquire()
-
解锁
mutex.relase()
-
-
例程:
# 定义全局变量 glob_v = 0 # 对全局变量加1 def sum_num1(): # 上锁 mutex.acquire() for i in range(1000000):# 分别测试 十万 与 一百万 global glob_v glob_v += 1 # 解锁 mutex.release() print("glob_v1",glob_v) def sum_num2(): # 上锁 mutex.acquire() for i in range(1000000): # 分别测试 十万 与 一百万 global glob_v glob_v += 1 # 解锁 mutex.release() print("glob_v2",glob_v) if __name__ == "__main__": #创建锁 mutex = threading.Lock() sum1_thread = threading.Thread(target=sum_num1) sum2_thread = threading.Thread(target=sum_num2) # 启动线程 sum1_thread.start() sum2_thread.start() #执行结果 # glob_v1 1000000 # glob_v2 2000000
-
-
死锁
- 一直等待对方释放锁的情景就是死锁
- 结果:会造成应用程序的停止响应,不被再处理其他任务了!
实际工作中要注意释放锁。
-
进程与线程的对比
进程 线程 关系对比 一个进程默认一个线程,可多个 线程依附在进程,没有进程就没有线程 区别对比 1.不能共享全局变量
2.创建进程资源开销大
3.进程是系统资源的基本单位1.全局变量可互用,防止资源竞争与死锁发生,可用解决办法:互斥锁或线程同步
2.创建线程资源开销很小
3.线程是CPU调度的基本单位
4.线程不能自动运行优缺点对比 可以使用多核,资源开销大 资源开销小,不能使用多核
-
-