文章目录
- 进程
- 概念
- 先看个多进程的小案例
- 上下文和启动方式(重点)
- 在进程之间交换对象
- 同步原语
- 进程`multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)`
更多关于python线程
进程
概念
1. 什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,即正在执行的一个过程,是对正在运行程序的一个抽象
进程是计算机中最小的资源分配单位
进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一,操作系统的其他所有内容都是围绕进程的概念展开的
进程需要占用资源,需要操作系统调度
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是[操作系统](https://baike.baidu.com/item/操作系统/192)动态执行的[基本单元](https://baike.baidu.com/item/基本单元),在传统的操作系统中,进程既是基本的[分配单元](https://baike.baidu.com/item/分配单元),也是基本的执行单元
####2.进程的特点
- 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
- 并发性:任何进程都可以同其他进程一起并发执行
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
- 结构特征:进程由程序、数据和进程控制块三部分组成。
- 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变
3.进程的属性
进程的名称
pid
能够唯一表示一个进程
4.程序和进程的区别
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
进程的调度算法
1.短作业优先:
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,
该算法既可用于作业调度,也可用于进程调度
但其对长作业不利;
不能保证紧迫性作业(进程)被及时处理;
作业的长短只是被估算出来的
2. 先来先服务:
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度
FCFS算法比较有利于长作业(进程),而不利于短作业(进程)
由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)
3. 时间片轮转法
时间片轮转法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例:
在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒
如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度
同时,进程调度程序又去调度当前就绪队列中的第一个进程
显然,轮转法只能用来调度分配一些可以抢占的资源(可以随时被剥夺,也可以将它们再分配给别的进程)
CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法
在轮转法中,时间片长度的选取非常重要(原因):
首先,时间片长度的选择会直接影响到系统的开销和响应时间
如果时间片长度过短,则调度程序抢占处理机的次数增多
这将使进程上下文切换次数也大大增加,从而加重系统开销
反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务
时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
4.多级反馈算法
多级反馈队列调度算法不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述:
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则(先来先服务)排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高**(优先级越高时间片越短,存在短作业优先)**的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
1.进程的开启和关闭
进程之间的通讯(IPC,Inter Process Communication)
处理进程之间的通讯的工具叫做__通讯中间件__
2. 进程的三状态图
就绪 | 运行 | 阻塞 三种状态
1. 就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态
2. 执行/运行(Running)状态
状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态
3. 阻塞(Blocked)状态
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等
先看个多进程的小案例
import multiprocessing
def f(x):
return x*x
def main():
with multiprocessing.Pool(5) as p:
print(p.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
if __name__ == "__main__":
main()
上下文和启动方式(重点)
根据不同的平台,multiprocessing
支持三种启动进程的方法:
模式 | 说明 |
---|---|
spawn | 父进程启动一个新的Python解释器进程。子进程只会继承那些运行进程对象的 run() 方法所需的资源。特别是父进程中非必须的文件描述符和句柄不会被继承。相对于使用 fork 或者 forkserver,使用这个方法启动进程相当慢 |
fork | 父进程使用 os.fork() 来产生 Python 解释器分叉。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全分叉多线程进程是棘手的 |
forkserver | 程序启动并选择* forkserver * 启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。分叉服务器进程是单线程的,因此使用 os.fork() 是安全的 |
-
选择一个启动方式:在
if __name__ == '__main__'
中调用**multiprocessing.set_start_method(mode)
**来获取上下文 -
也可以使用**
multiprocessing.get_context()
**来获取上下文请尽量避免
multiprocessing.set_start_method()
的多次调用
import multiprocessing
import os
def info(title):
print(title)
print("module name", __name__)
print("parent process", os.getppid())
print("process id", os.getpid())
def f(name):
info("function f")
print("hello", name)
def foo(q):
q.put("hello")
if __name__ == '__main__':
multiprocessing.set_start_method('spawn')
q = multiprocessing.Queue()
p = multiprocessing.Process(target=foo, args=(q, ))
p.start()
print(q.get())
p.join()
在进程之间交换对象
1.队列
Queue类是一个近似queue.Queue的克隆
import multiprocessing
def f(q):
q.put([42, None, 'hello'])
if __name__ == "__main__":
q = multiprocessing.Queue()
p = multiprocessing.Process(target=f, args=(q, ))
p.start()
print(q.get())
p.join()
队列可以保证线程和进程数据流通是安全的
警告:如果一个进程在尝试使用 Queue
期间被 Process.terminate()
或 os.kill()
调用终止了,那么队列中的数据很可能被破坏。 这可能导致其他进程在尝试使用该队列时发生异常
使用多进程时,一般使用消息机制实现进程间通信,尽可能避免使用同步原语,例如锁
1.multiprocessing.SimpleQueue
是一个简化的Queue类(先进先出)
multiprocessing.SimpleQueue
方法:
-
multiprocessing.SimpleQueue().close()
关闭队列:释放内存
-
multiprocessing.SimpleQueue().empty()
判断队列是否为空
-
multiprocessing.SimpleQueue().get()
从队列中移除并返回一个对象
-
multiprocessing.SimpleQueue().put()
将item放入队列
import multiprocessing
if __name__ == '__main__':
base_pipe = multiprocessing.SimpleQueue()
print(base_pipe.empty())
base_pipe.put("你半生浮沉")
print(base_pipe.empty())
base_pipe.put("依旧芙蓉出水")
base_pipe.put("我爱你")
base_pipe.put("而你已不在")
print(base_pipe.get())
print(base_pipe.get())
# python3.9版本新增功能 base_pipe.close()
2.multiprocessing.Queue([maxsize])
返回一个使用一个管道和少量锁和信号量实现的共享队列实例(先进先出)
multiprocessing.Queue
方法:
-
multiprocessing.Queue().qsize()
返回队列的大致长度(由于多线程或多进程的上下文,所以不准确)
-
`multiprocessing.Queue().empty()
返回队列是否为空
-
multiprocessing.Queue().full()
返回队列是否为满
-
multiprocessing.Queue().get([block[,timeout]])
从队列中取出并返回对象,
-
multiprocessing.Queue().get_nowait()
相当于get(False)
-
multiprocessing.Queue().put(obj[, block[, timeout]])
将obj放入队列
-
multiprocessing.Queue().put_nowait(obj)
相当于put(obj, False)
-
multiprocessing.Queue().close()
指示当前进度将不再往队列中放入对象
-
multiprocessing.Queue().join_thread()
等待后台线程,这个方法仅在调用了close()方法之后可用,这会阻塞当前进程,直到后台线程退出,确保所有缓冲区中的数据都被写入管道中
-
multiprocessing.Queue().cancel_join_thread()
防止join_thread()方法阻塞当前进程
import multiprocessing
def make_queue():
q = multiprocessing.Queue(5)
print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
q.put({'name': 'Mike', 'age': 10})
print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
print("当前获获取数据的为{}".format(q.get()))
print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
q.put({'name': 'Mike', 'age': 10})
q.put({'name': 'John', 'age': 10})
q.put({'name': 'Alice', 'age': 10})
q.put({'name': 'Google', 'age': 10})
q.put({'name': 'FireFox', 'age': 10})
print("当前队列是否已满{}".format(q.full()))
print(q.get())
print("当前队列是否已满{}".format(q.full()))
q.close()
q.join_thread()
q.cancel_join_thread()
if __name__ == "__main__":
make_queue()
3.multiprocessing.JoinableQueue([maxsize])
类,Queue的子类
额外添加了 task_done()
和 join()
方法
如果你使用了 JoinableQueue
,那么你必须对每个已经移出队列的任务调用 JoinableQueue.task_done()
。不然的话用于统计未完成任务的信号量最终会溢出并抛出异常
函数 | 说明 |
---|---|
task_done () | 指出之前进入队列的任务已经完成,由队列的消费者进程使用。于每次调用 get() 获取的任务,执行完成后调用 task_done() 告诉队列该任务已经处理完成。如果 join() 方法正在阻塞之中,该方法会在所有对象都被处理完的时候返回 (即对之前使用 put() 放进队列中的所有对象都已经返回了对应的 task_done() ) 。如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 异常 |
join () | 阻塞至队列中所有的元素都被接收和处理完毕。当条目添加到队列的时候,未完成任务的计数就会增加。每当消费者进程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。当未完成计数降到零的时候, join() 阻塞被解除 |
Queue队列
1.queue.Queue(maxsize=0)
先进先出
import queue
if __name__ == '__main__':
q = queue.Queue()
q.put('first')
q.put('second')
q.put('third')
print(q.get())
print(q.get())
print(q.get())
2.queue.LifoQueue(maxsize=0)
后进先出
import queue
if __name__ == '__main__':
q = queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')
print(q.get())
print(q.get())
print(q.get())
3.queue.PriorityQueue(maxsize=0)
可设置数据优先值
import queue
if __name__ == '__main__':
q = queue.PriorityQueue()
# put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20, 'a'))
q.put((10, 'b'))
q.put((30, 'c'))
print(q.get())
print(q.get())
print(q.get())
2.管道
multiprocessing.Pipe([duplex])
返回一对Connection对象(conn1, conn2),分别表示管道两端
如果duplex被设置为True(默认值),那么该管道是双向。如果duplex被设置为False,那么该管道是单向的
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv())
p.join()
同步原语
通常来说同步原语在多进程环境中并不像它们在多线程环境中那么必要,详细参考 threading
模块的文档
-
class
multiprocessing.Barrier
(parties[, action[, timeout]])类似
threading.Barrier
的栅栏对象
- class
multiprocessing.BoundedSemaphore
([value])
非常类似 threading.BoundedSemaphore
的有界信号量对象
一个小小的不同在于,它的 acquire
方法的第一个参数名是和 Lock.acquire()
一样的 block
-
class
multiprocessing.Condition
([lock])条件变量:
threading.Condition
的别名。指定的 lock 参数应该是multiprocessing
模块中的Lock
或者RLock
对象 -
class
multiprocessing.``Event
threading.Event
.的别名(克隆) -
class
multiprocessing.Lock
原始锁(非递归锁)对象,类似于
threading.Lock
-
class multiprocessing.RLock
递归锁对象: 类似于
threading.RLock
-
class
multiprocessing.``Semaphore
([value])一种信号量对象: 类似于
threading.Semaphore
.一个小小的不同在于,它的
acquire
方法的第一个参数名是和Lock.acquire()
一样的 block
进程multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
进程对象表示在单独进程中运行的活动, Process
类拥有和 threading.Thread
等价的大部分方法
multiprocessing.Pross
方法:
-
multiprocessing.Process().run()
表示进程活动的方法
-
multiprocessing.Process().start()
启动进程活动,会将对象的run()方向安排在一个单独的进程中调用
-
multiprocessing.Process().name
获取进程的名称,该名称是一个字符串,及用于识别目的
-
multiprocessing.Process().is_alive()
返回进程是否还活着
-
multiprocessing.Process().daemon
判断进程是否是守护进程
-
multiprocessing.Process().join([timeout])
是当前进程进入堵塞状态,进程无法join自身,这会导致死锁
-
multiprocessing.Process().pid
返回进程ID
-
multiprocessing.Process().authkey
进程的身份验证密钥(字节字符串)
-
multiprocessing.Process().sentinel
系统对象的数字句柄,当前进程结束时将变为"ready"
-
multiprocessing.Process().kill()
终止进程
-
multiprocessing.Process().terminate()
终止进程
-
multiprocessing.Process().close()
关闭Process对象,释放与之关联的所有资源
-
multiprocessing.Process().exitcode
子进程退出的代码,如果进程尚未中止,这将是None。负值-N表示子进程被信号N中止
建立一个线程任务
通过函数构建线程任务
import multiprocessing
import os
import time
def f(name):
print("hello,{}".format(name))
print("the ID of Process is --> {}".format(os.getpid()))
if __name__ == '__main__':
print("the ID of Process is --> {}".format(os.getpid()))
p = multiprocessing.Process(target=f, args=("Mike", ))
p.start()
time.sleep(1)
p.join()
print("执行了主线程的内容")
通过类(继承自multiprincessing.Process
)构建线程任务
import multiprocessing
import time
class MyTask(multiprocessing.Process):
def __init__(self, num):
super(MyTask, self).__init__()
self.num = num
def run(self) -> None:
for i in range(self.num):
print("current number value is {}".format(i))
if __name__ == '__main__':
t1 = MyTask(5)
t1.start()
t1.join()
time.sleep(2)
print("主线程执行了")
多个进程任务
import multiprocessing
import os
import time
def f(name):
print("hello,{}".format(name))
print("the ID of Process is --> {}".format(os.getpid()))
if __name__ == '__main__':
print("the ID of Process is --> {}".format(os.getpid()))
p_s = list()
name_s = ['Bob', 'John', 'Mike', 'Alice']
for name in name_s:
p = multiprocessing.Process(target=f, args=(name, ))
p.start()
p_s.append(p)
time.sleep(1)
for p in p_s:
p.join()
print("执行了主线程的内容")
进程之间数据隔离
import multiprocessing
import time
g_num = 0
def test():
global g_num
for i in range(10):
g_num += 1
if __name__ == '__main__':
t1 = multiprocessing.Process(target=test)
t1.start()
time.sleep(1)
t1.join()
print("the g_num's value is {}".format(g_num))
守护进程(重点)
概念
守护进程会随着主进程的结束而结束,不会因为子进程没有结束而不结束
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
守护进程程序示例
import multiprocessing
import time
class MyTask(multiprocessing.Process):
def __init__(self, num):
super(MyTask, self).__init__()
self.num = num
def run(self) -> None:
for i in range(self.num):
print("current number value is {}".format(i))
if __name__ == '__main__':
t1 = MyTask(5)
t1.daemon = True
t1.start()
# time.sleep(2)
time.sleep(0.1)
print("主线程执行了")
其他类属性:
-
exception
multiprocessing.ProcessError
所有
multiprocessing
异常的基类。 -
exception
multiprocessing.BufferTooShort
当提供的缓冲区对象太小而无法读取消息时,
Connection.recv_bytes_into()
引发的异常。如果e
是一个BufferTooShort
实例,那么e.args[0]
将把消息作为字节字符串给出。 -
exception
multiprocessing.AuthenticationError
出现身份验证错误时引发。
-
exception
multiprocessing.TimeoutError
有超时的方法超时时引发
其它类方法:
-
multiprocessing.active_children()
返回当前进程存活的子进程的列表
-
multiprocessing.cpu_count()
返回当前系统的CPU数量
-
multiprocessing.current_process()
返回当前进程相对应的Process对象
-
multiprocessing.parent_process()
返回父进程Prograss对象
-
multiprocessing.get_all_start_method()
返回支持的启动上下文的方法列表
-
multipricessing.freeze_support()
提供冻结以产生Windows可执行文件的支持
-
multipricessing.get_all_start_methods()
返回支持的启动方法的列表
-
multipricessing.get_start_method()
返回进程时使用的启动方法名
-
multipricessing.get_context(method=None)
返回一个Context对象,进程支持的上下文启动方法
-
multipricessing.set_start_method(method)
设置启动子进程的方法(最多只能调用1次)
rror`
有超时的方法超时时引发
其它类方法:
-
multiprocessing.active_children()
返回当前进程存活的子进程的列表
-
multiprocessing.cpu_count()
返回当前系统的CPU数量
-
multiprocessing.current_process()
返回当前进程相对应的Process对象
-
multiprocessing.parent_process()
返回父进程Prograss对象
-
multiprocessing.get_all_start_method()
返回支持的启动上下文的方法列表
-
multipricessing.freeze_support()
提供冻结以产生Windows可执行文件的支持
-
multipricessing.get_all_start_methods()
返回支持的启动方法的列表
-
multipricessing.get_start_method()
返回进程时使用的启动方法名
-
multipricessing.get_context(method=None)
返回一个Context对象,进程支持的上下文启动方法
-
multipricessing.set_start_method(method)
设置启动子进程的方法(最多只能调用1次)