Python中的进程和线程
一.进程的概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
注意:
一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程.
并行:多个cpu同时处理多个程序
并发:一个cpu在一个很小的时间段之内在多个程序之间来回切换执行,切换的时间由操作系统决定,但时间比较小
进程的作用:
单进程效果图:
多进程效果图:
进程是操作系统分配资源的基本单位
进程是python实现多重任务的一种方式
二.多进程的使用
1.Process功能
用于直接定义进程
from multiprocessing import Process
p = Process(target=function,args,kwargs)
Process的参数组成:
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定进程组,目前只能使用None
target:执行的目标任务名,后面一般接的是进程的函数名
name:进程函数名字
args:以元组方式给执行任务传参
kwargs: 以字典方式给执行任务传参
# 1.导入多进程的包
import os
import time
from multiprocessing import Process
# 2.定义两个函数,用多进程的方式进行处理
def run1(name):
# 获取子进程的进程编号
print('run1--',name)
def run2(name,age=0):
print('run2--',name,age)
if __name__ == '__main__':
# 1.导包
import time
from multiprocessing import Process
# 3.通过process创建,target后面接的是函数名称
# 给进程传递非关键字参数,需要定义args
# 给进程传递关键字参数,需要定义kwargs
p1 = Process(target=run1,args=('a',)) # args可以是元组或者列表,但一定是这两个形式之一
p2 = Process(target=run2,args=('b',),kwargs={'age':20}) # 给进程的关键字传参,kwargs,类型是字典型
# 4.执行进程
p1.start()
p2.start()
print('main')
# join阻塞进程:上面进程执行完之后才会执行下面进程
p1.join()
p2.join()
print('main')
2.获取进程编号
目的:验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。
方法:
import os
# 1.获取子进程的编号
pid = os.getpid()
# 2.获取父进程的编号
ppid = os.getppid()
3.守护进程
目的:验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。(主进程结束,子进程就结束了)
设置方式:子进程对象.daemon = True
p.daemon = True
4.进程之间的数据共享
原则上,各进程之间的数据是不共享的,但可以创建参数共享池来进行个进程之间的共享.
三种方式:manager,queue(队列),pipe(管道)
manager:
from multiprocessing import Process, Manager,Queue,Pipe
# 1. manager方式
with Manager() as manager:
lis = manager.list() # 使用Manger来对参数进行管理
p1 = Process(target=run1, args=(lis,))
p2 = Process(target=run2, args=(lis,))
# 2.queue方式
def run1(q):
q.put('run1') # 调用queue的方法输出数据
time.sleep(1)
print(q.get()) # 获取数据
def run2(q):
print(q.get()) # 获取数据
q.put('run2') # 出数据
if __name__ == '__main__':
# 实例化队列,队列的存储方式是一头进一头出,先进先出,后进后出
q = Queue()
# 3,通过Process创建进程
p1 = Process(target=run1, args=(q,))
p2 = Process(target=run2, args=(q,))
# 4,执行进程
p1.start()
p2.start()
p1.join()
p2.join()
# 3.pipe方式
def run1(pi1):
pi1.send('run1')
time.sleep(1)
print(pi1.recv())
def run2(pi2):
print(pi2.recv()) # 获取数据,如果此时管道中没有数据,那么pi2会一直等待,会使得管道堵塞,相当于一直占用管道
pi2.send('run2') # 发送数据
if __name__ == '__main__':
# 实例化管道
pi1, pi2 = Pipe()
# 3,通过Process创建进程
p1 = Process(target=run1, args=(pi1,))
p2 = Process(target=run2, args=(pi2,))
# 4,执行进程
p1.start()
p2.start()
p1.join()
p2.join()
5.进程池
目的:当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,而且频繁的创建和销毁进程,会消耗非常大的资源。所以就可以用到multiprocessing模块提供的Pool方法
# 创建进程池
# 设置进程池内的进程个数
pool = Pool(3)
for i in range(10):
pool.apply_async(run1) # 异步将进程传入进程池中
# 必须关闭进程池
pool.close()
# 4.必须等待所有的请求全部执行完成时候,再结束主进程
pool.join()
三.线程
1.概念
线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度 ,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。
由于python解释器中存在GIL锁,所以python中的多线程是伪多线程,也就是cpu不能并行执行,只能并发执行
2.线程的使用
使用Threading模块,其用法跟Process是一样的
if __name__ == '__main__':
# 扩展: 获取当前线程
# print("当前执行的线程为:", threading.current_thread())
# target: 线程执行的函数名
sing_thread = threading.Thread(target=sing,args,kwargs)
# 创建跳舞的线程
dance_thread = threading.Thread(target=dance)
t1.setDaemon(True) # 守护进程
t2.setDaemon(True)
# 开启线程
sing_thread.start()
dance_thread.start()
3.线程间的数据是共享的
准确地说,线程间大的全局变量是共享的,而且多个线程之间是并发关系,即一个CPU在多个线程之中会来回切换运行,因此也可能带来数据的错乱,这时候需要加上锁来防止数据错乱:
from threading import Thread,Lock
import time
lock = Lock()
a = 0
def run1():
global a
for i in range(1000000):
# 加锁
lock.acquire()
a += 1
# 释放锁
lock.release()
def run2():
global a
for i in range(1000000):
lock.acquire()
a += 1
lock.release()
if __name__ == '__main__':
t1 = Thread(target=run1)
t2 = Thread(target=run2)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
# 如果没有加锁,则a的值应该小于2000000
总结
-
进程和线程的对比的三个方向
关系对比
区别对比
优缺点对比 -
关系对比
线程是依附在进程里面的,没有进程就没有线程。
一个进程默认提供一条线程,进程可以创建多个线程。 -
区别对比
进程之间不共享全局变量
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 加锁
创建进程的资源开销要比创建线程的资源开销要大
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
线程不能够独立执行,必须依存在进程中
多进程开发比单进程多线程开发稳定性要强 -
优缺点对比
进程优缺点:
优点:可以用多核
缺点:资源开销大 # 每个进程都需要不同的内存
线程优缺点:
优点:资源开销小 # 线程可以共享内存,共享数据
缺点:不能使用多核 -
使用场景
计算密集型:CPU 长时间满负荷运行, 如图像处理、大数据运算、科学运算等。使用多进程
I/O 密集型:网络 IO, 文件 IO, 设备 IO 等。使用多线程