进程:通俗理解一个运行的程序或者软件,进程是操作系统资源分配的基本单位。
现实生活中的公司可以理解成是一个进程,公司提供办公资源(电脑、办公桌椅等),真正干活的是员工,员工可以理解成线程。
注意:
一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务.
1.2 进程的状态
工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有了不同的状态
就绪态:运行的条件都已经慢去,正在等在cpu执行
执行态:cpu正在执行其功能
等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态
一个进程默认有一个线程,进程里面可以创建线程,线程是依附在进程里面的,没有进程就没有线程。
2、进程的使用
2.1 多进程完成多任务
2.1.1 导入进程模块
import multiprocessing
from multiprocessing import Process
2.1.2 Process进程类的语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定进程组,目前只能使用None
target:执行的目标任务名
name:进程名字
args:以元组方式给执行任务传参
kwargs:以字典方式给执行任务传参
Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号)
2.1.3 多进程完成多任务代码
import multiprocessing
import time
def run_proc():
"""子进程要执行的代码"""
while True:
print("----2----")
time.sleep(1)
if __name__=='__main__':
sub_process = multiprocessing.Process(target=run_proc)
sub_process.start()
while True:
print("----1----")
time.sleep(1)
执行结果:
----1----
----2----
----1----
----2----
----1----
----2----
2.1.4 获取进程pid
import multiprocessing
import time
import os
def work():
current_process = multiprocessing.current_process()
print("work:", current_process)
print("work进程编号:", current_process.pid, os.getpid())
print("work父进程的编号:", os.getppid())
for i in range(10):
print("工作中....")
time.sleep(0.2)
os.kill(os.getpid(), 9)
if __name__ == '__main__':
current_process = multiprocessing.current_process()
print("main:", current_process)
print("main进程的编号:", current_process.pid)
sub_process = multiprocessing.Process(target=work)
sub_process.start()
for i in range(20):
print("我在主进程中执行...")
time.sleep(0.2)
执行结果:
main: <_MainProcess(MainProcess, started)>
main进程的编号: 9552
我在主进程中执行...
我在主进程中执行...
我在主进程中执行...
我在主进程中执行...
work: <Process(Process-1, started)>
work进程编号: 5056 5056
work父进程的编号: 9552
工作中....
我在主进程中执行...
我在主进程中执行...
我在主进程中执行...
我在主进程中执行...
3、进程注意点
3.1 进程之间不共享全局变量
import multiprocessing
import time
my_list = list()
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.2)
print("write_data:", my_list)
def read_data():
print(my_list)
if __name__ == '__main__':
write_process = multiprocessing.Process(target=write_data)
read_process = multiprocessing.Process(target=read_data)
write_process.start()
write_process.join()
read_process.start()
执行结果:
write_data: [0, 1, 2, 3, 4]
read_data: []
注意:创建子进程其实是对主进程进行拷贝,进程之间相互独立,访问的全局变量不是同一个,所以进程之间不共享全局变量
3.2 主进程会等待所有的子进程执行完成程序再退出
import multiprocessing
import time
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("主进程执行完成了啦")
执行结果:
工作中...
工作中...
工作中...
工作中...
工作中...
主进程执行完成了啦
工作中...
工作中...
工作中...
工作中...
工作中...
3.2.1 销毁子进程的代码
import multiprocessing
import time
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("主进程执行完成了啦")
work_process.terminate()
执行结果:
工作中...
工作中...
工作中...
工作中...
工作中...
主进程执行完成了啦
进程之间不共享全局变量
主进程会等待所有的子进程执行完成程序再退出
4、进程间通信-Queue
知道消息队列queue的放入值和获取值得操作
4.1 Queue的使用
可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序,首先用一个小实例来演示一下Queue的工作原理:
import multiprocessing
import time
if __name__ == '__main__':
queue = multiprocessing.Queue(3)
queue.put(1)
queue.put("hello")
queue.put([3,5])
if queue.qsize() == 0:
print("队列为空")
else:
print("队列不为空")
size = queue.qsize()
print(size)
value = queue.get()
print(value)
size = queue.qsize()
print(size)
value = queue.get()
print(value)
value = queue.get()
print(value)
size = queue.qsize()
print(size)
运行结果:
队列不为空
hello
[3, 5]
初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之False , 注意这个操作是不可靠的。
Queue.full():如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
2)如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
Queue.get_nowait():相当Queue.get(False);
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
2)如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
Queue.put_nowait(item):相当Queue.put(item, False);
4.2 消息队列Queue完成进程间通信的演练
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
import multiprocessing
import time
def write_data(queue):
for i in range(10):
if queue.full():
print("队列满了")
break
queue.put(i)
time.sleep(0.2)
print(i)
def read_data(queue):
while True:
if queue.qsize() == 0:
print("队列空了")
break
value = queue.get()
print(value)
if __name__ == '__main__':
queue = multiprocessing.Queue(5)
write_process = multiprocessing.Process(target=write_data, args=(queue,))
read_process = multiprocessing.Process(target=read_data, args=(queue,))
write_process.start()
write_process.join()
read_process.start()
运行结果:
从队列取值使用get方法,向队列放入值使用put方法
消息队列判断队列是否为空不可靠,可以使用延时和根据个数进行判断
5、进程池Pool
使用进程池完成多任务
5.1 进程池的概念
池子里面放的是进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务.
5.2 进程池同步执行任务
进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行
进程池同步实例代码
import multiprocessing
import time
def work():
print("复制中...", multiprocessing.current_process().pid)
time.sleep(0.5)
if __name__ == '__main__':
pool = multiprocessing.Pool(3)
for i in range(5):
pool.apply(work)
运行结果:
复制中... 100512
复制中... 68128
复制中... 98924
复制中... 100512
复制中... 68128
5.3 进程池异步执行任务
进程池异步执行任务表示进程池中的进程同时执行任务,进程之间不会等待
进程池异步实例代码
import multiprocessing
import time
def work():
print("复制中...", multiprocessing.current_process().pid)
time.sleep(0.5)
if __name__ == '__main__':
pool = multiprocessing.Pool(3)
for i in range(5):
pool.apply_async(work)
pool.close()
pool.join()
执行结果:
复制中... 122872
复制中... 61772
复制中... 114636
复制中... 122872
复制中... 114636
multiprocessing.Pool常用函数解析:
apply(func[, args[, kwds]]): 阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用函数,args表示以元组方式给函数传参,kwds表示以字典方式给函数传参
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
6、进程、线程对比
知道进程和线程关系及优缺点
6.1 功能对比
进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
线程,能够完成多任务,比如 一个QQ中的多个聊天窗口
6.2 定义对比
进程是系统进行资源分配基本单位,每启动一个进程操作系统都需要为其分配运行资源。
线程是运行程序中的一个执行分支,是CPU调度基本单位。
总结:进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
6.3 关系对比
线程是依附在进程里面的,没有进程就没有线程
一个进程默认提供一条线程,进程可以创建多个线程
6.4 区别
进程之间不共享全局变量
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
创建进程的资源开销要比创建线程的资源开销要大
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
线程不能够独立执行,必须依存在进程中
多进程开发比单进程多线程开发稳定性要强
优点:可以用多核
缺点:资源开销大
优点:资源开销小
缺点:不能使用多核
文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,文章中难免会有错误之处,欢迎小伙伴们反馈指正。
如果觉得文章对你有帮助,麻烦 点赞、评论、收藏
你的支持是我最大的动力!!!