CH13. 进程与线程
初识程序与进程
程序(Program), 是指一系列有序指令的集合,使用编程语言所编写,用于实现一定的功能 进程,进程则是指启动后的程序,系统会为进程分配内存空间
函数式创建子进程
创建进程的方式
第一种创建进程的语法结构:函数式
Process(group=None, target, name, args, kwargs)
参数说明:
group
: 表示分组,实际上不适用,值默认为None即可target
: 表示子进程要执行的任务,支持函数名name
: 表示子进程的名称args
:表示调用函数的位置函数,以元组的形式进行传递kwargs
:表示调用函数的关键字参数,以字典的形式进行传递
import os
import time
from multiprocessing import Process
def test ( ) :
print ( f'我是子进程,我的PID是: { os. getpid( ) } , 我的父进程是: { os. getppid( ) } ' )
time. sleep( 1 )
if __name__ == '__main__' :
print ( '主进程开始执行' )
lst = [ ]
for i in range ( 5 ) :
p = Process( target= test)
p. start( )
lst. append( p)
for item in lst:
item. join( )
print ( '主进程执行结束' )
'''
PS D:\2_Demolight\python\study-plan\single-files> & C:/Python312/python.exe d:/2_Demolight/python/study-plan/single-files/test_process.py
主进程开始执行
主进程执行结束
我是子进程,我的PID是:29996, 我的父进程是:28144
我是子进程,我的PID是:30228, 我的父进程是:28144
我是子进程,我的PID是:17140, 我的父进程是:28144
我是子进程,我的PID是:26652, 我的父进程是:28144
我是子进程,我的PID是:9616, 我的父进程是:28144
PS D:\2_Demolight\python\study-plan\single-files> & C:/Python312/python.exe d:/2_Demolight/python/study-plan/single-files/test_process.py
主进程开始执行
我是子进程,我的PID是:2828, 我的父进程是:2456
我是子进程,我的PID是:27192, 我的父进程是:2456
我是子进程,我的PID是:15064, 我的父进程是:2456
我是子进程,我的PID是:18972, 我的父进程是:2456
我是子进程,我的PID是:12156, 我的父进程是:2456
主进程执行结束
'''
Process类中常用的属性和方法
方法/属性名称 功能描述 name
当前进程实例别名,默认为Process-N pid
当前进程对象的PID值 is_alive()
进程是否执行完,没执行完结果为True,否则为False join(timeout)
等待结束或等待timeout秒 start()
启动进程 run()
如果没有指定target参数,则启动进程后,会调用父类中的run方法 terminate()
强制终止进程
import os
import time
from multiprocessing import Process
def sub_process ( name) :
print ( f'子进程PID是: { os. getpid( ) } , 父进程PID是: { os. getppid( ) } , ----- { name} ' )
time. sleep( 1 )
def sub_process2 ( name) :
print ( f'子进程PID是: { os. getpid( ) } , 父进程PID是: { os. getppid( ) } , ----- { name} ' )
time. sleep( 1 )
if __name__ == '__main__' :
print ( '父进程开始执行...' )
for i in range ( 5 ) :
p1 = Process( target= sub_process, args= ( 'Mike' , ) )
p2 = Process( target= sub_process2, args= ( 18 , ) )
p1. start( )
p2. start( )
print ( p1. name, '是否执行完毕:' , p1. is_alive( ) )
print ( p2. name, '是否执行完毕:' , p2. is_alive( ) )
print ( p1. name, 'pid是: ' , p1. pid)
print ( p2. name, 'pid是: ' , p2. pid)
p1. join( )
p2. join( )
print ( p1. name, '是否执行完毕:' , p1. is_alive( ) )
print ( p2. name, '是否执行完毕:' , p2. is_alive( ) )
print ( '父进程执行完毕' )
继承式创建子进程
import os
import time
from multiprocessing import Process
class SubProcess ( Process) :
def __init__ ( self, name) :
super ( ) . __init__( )
self. name = name
def run ( self) :
print ( f'子进程名称: { self. name} , PID: { os. getpid( ) } , 父进程的PID: { os. getppid( ) } ' )
if __name__ == '__main__' :
print ( '父进程开始执行' )
lst = [ ]
for i in range ( 1 , 6 ) :
p1 = SubProcess( f'进程: { i} ' )
p1. start( )
lst. append( p1)
for item in lst:
item. join( )
print ( '父进程执行结束' )
'''
父进程开始执行
子进程名称:进程:1, PID:3020, 父进程的PID:15580
子进程名称:进程:2, PID:28284, 父进程的PID:15580
子进程名称:进程:3, PID:16548, 父进程的PID:15580
子进程名称:进程:4, PID:25132, 父进程的PID:15580
子进程名称:进程:5, PID:28692, 父进程的PID:15580
父进程执行结束
'''
Pool进程池
进程池的原理是:创建一个进程池,并设置进程池中最大的进程数量。假设进程池中最大的进程数为3,现在有10个任务需要执行,那么进程池一次可以执行3个任务,4次即可完成全部任务的执行。 创建进程池的语法结构:进程池对象=Pool(N)
方法名 功能描述 apply_async(func,args,kwargs)
使用非阻塞方式调用函数func
apply(func,args,kwargs)
使用阻塞方式调用函数func
close()
关闭进程池,不再接收新任务 terminate()
不管任务是否完成,立即终止 join()
阻塞主进程,必须在terminate()
或close()
之后调用
非阻塞方式
import os
import time
from multiprocessing import Pool
def sub_process ( name) :
print ( f'子进程PID是: { os. getpid( ) } , ----- { name} ' )
time. sleep( 1 )
if __name__ == '__main__' :
start = time. time( )
print ( '父进程开始执行' )
p = Pool( 3 )
for i in range ( 10 ) :
p. apply_async( func= sub_process, args= ( i, ) )
p. close( )
p. join( )
print ( '所有子进程执行完毕, 父进程执行完毕' )
print ( '耗时:' , time. time( ) - start)
'''
父进程开始执行
子进程PID是:6980, -----0
子进程PID是:9660, -----1
子进程PID是:26692, -----2
子进程PID是:6980, -----3
子进程PID是:9660, -----4
子进程PID是:26692, -----5
子进程PID是:6980, -----6
子进程PID是:9660, -----7
子进程PID是:26692, -----8
子进程PID是:6980, -----9
所有子进程执行完毕, 父进程执行完毕
耗时: 4.210505723953247
'''
阻塞方式
import os
import time
from multiprocessing import Pool
def sub_process ( name) :
print ( f'子进程PID是: { os. getpid( ) } , ----- { name} ' )
time. sleep( 1 )
if __name__ == '__main__' :
start = time. time( )
print ( '父进程开始执行' )
p = Pool( 3 )
for i in range ( 10 ) :
p. apply ( func= sub_process, args= ( i, ) )
p. close( )
p. join( )
print ( '所有子进程执行完毕, 父进程执行完毕' )
print ( '耗时:' , time. time( ) - start)
'''
父进程开始执行
子进程PID是:22824, -----0
子进程PID是:20980, -----1
子进程PID是:28148, -----2
子进程PID是:22824, -----3
子进程PID是:20980, -----4
子进程PID是:28148, -----5
子进程PID是:22824, -----6
子进程PID是:20980, -----7
子进程PID是:28148, -----8
子进程PID是:22824, -----9
所有子进程执行完毕, 父进程执行完毕
耗时: 10.160722494125366
'''
并发和并行
并发
是指两个或多个事件同一时间间隔发生,多个任务被交替轮换着执行,比如A事件是吃苹果,在吃苹果的过程中有快递员敲门让你收下快递,收快递就是B事件,那么收完宽邸继续吃没吃完的苹果。这就是并发 。
并行
多个进程之间的通信
import os
import time
from multiprocessing import Process
a = 100
def add ( ) :
print ( '子进程1开始执行' )
global a
a += 30
print ( 'a=' , a)
print ( '子进程1执行结束' )
def sub ( ) :
print ( '子进程2开始执行' )
global a
a -= 50
print ( 'a=' , a)
print ( '子进程2执行结束' )
if __name__ == '__main__' :
print ( '主进程开始执行' )
print ( 'a = ' , a)
p1 = Process( target= add)
p2 = Process( target= sub)
p1. start( )
p2. start( )
p1. join( )
p2. join( )
print ( 'a = ' , a)
print ( '主进程执行结束' )
'''
主进程开始执行
a = 100
子进程1开始执行
a= 130
子进程1执行结束
子进程2开始执行
a= 50
子进程2执行结束
a = 100
主进程执行结束
'''
进程之间可以通过队列(Queue)进行通信,队列是一种先进先出(FIFO)的数据结构
创建队列的语法结构:队列对象 = Queue(N)
方法 功能 qsize()
获取当前队列包含的消息数量 empty()
判断队列是否为空,为空结果为True,否则为False full()
判断队列是否满了,满了结果为True,否则为False get(block=True)
获取队列中的一条消息,然后从队列中移除,block默认值为True;消息队列为空时,不报错,但是会一直等待 get_nowait()
相当于get(block=False)
,消息队列为空时,抛出异常 put(item,block=True)
将item消息放入队列,block默认为True,消息队列为满时,不报错,但是会一直等待 put_nowait(item)
相当于put(item,block=False)
,消息队列为满时,抛出异常
from multiprocessing import Queue
if __name__ == '__main__' :
q = Queue( 3 )
print ( '队列是否为空:' , q. empty( ) )
print ( '队列是否为满:' , q. full( ) )
q. put( 'hello' )
q. put( 'world' )
print ( '队列是否为空:' , q. empty( ) )
print ( '队列是否为满:' , q. full( ) )
q. put( 'python' )
print ( '队列是否为空:' , q. empty( ) )
print ( '队列是否为满:' , q. full( ) )
print ( '队列中有多少个消息:' , q. qsize( ) )
print ( q. get( ) )
print ( '队列中有多少个消息:' , q. qsize( ) )
q. put( 'Java' )
print ( '队列中有多少个消息:' , q. qsize( ) )
if not q. empty( ) :
for i in range ( q. qsize( ) ) :
print ( q. get_nowait( ) )
print ( '队列是否为空:' , q. empty( ) )
print ( '队列是否为满:' , q. full( ) )
print ( '队列中有多少个消息:' , q. qsize( ) )
'''
队列是否为空: True
队列是否为满: False
队列是否为空: False
队列是否为满: False
队列是否为空: False
队列是否为满: True
队列中有多少个消息: 3
hello
队列中有多少个消息: 2
队列中有多少个消息: 3
world
python
Java
队列是否为空: True
队列是否为满: False
队列中有多少个消息: 0
'''
import time
from multiprocessing import Process, Queue
a = 100
def write_msg ( q) :
global a
if not q. full( ) :
for i in range ( 6 ) :
a -= 10
q. put( a)
print ( 'a入队时的值:' , a)
def read_msg ( q) :
time. sleep( 1 )
while not q. empty( ) :
print ( '出队时a的值:' , q. get( ) )
if __name__ == '__main__' :
print ( '父进程开始执行' )
q = Queue( )
p1 = Process( target= write_msg, args= ( q, ) )
p2 = Process( target= read_msg, args= ( q, ) )
p1. start( )
p2. start( )
p1. join( )
p2. join( )
print ( '父进程执行完成' )
'''
父进程开始执行
a入队时的值: 90
a入队时的值: 80
a入队时的值: 70
a入队时的值: 60
a入队时的值: 50
a入队时的值: 40
出队时a的值: 90
出队时a的值: 80
出队时a的值: 70
出队时a的值: 60
出队时a的值: 50
出队时a的值: 40
父进程执行完成
'''
线程
线程是CPU可调度的最小单位,被包含在进程中,是进程中实际的运作单位。 一个进程中可以拥有N多个线程并发执行,而每个线程并行执行不同的任务。
创建线程的方式
函数式
创建线程的语法结构:
t=Thread(group,target,name,args,kwargs)
函数说明:
group:创建线程对象的进程组 target:创建的线程对象所要执行的目标函数 name:创建线程对象的名称,默认为“Thread-n" args:用元组以位置参数的形式传入target对应函数的参数 kwargs:用字典以关键字参数的形式传入target对应函数的参数
import threading
from threading import Thread
import time
def test ( ) :
for i in range ( 3 ) :
time. sleep( 1 )
print ( f'线程: { threading. current_thread( ) . name} 正在执行 { i} ' )
if __name__ == '__main__' :
start = time. time( )
print ( '主进程开始执行' )
lst = [ Thread( target= test) for i in range ( 2 ) ]
for item in lst:
item. start( )
for item in lst:
item. join( )
print ( f'执行结束,一共耗时 { time. time( ) - start} ' )
'''
主进程开始执行
线程:Thread-2 (test)正在执行0
线程:Thread-1 (test)正在执行0
线程:Thread-1 (test)正在执行1
线程:Thread-2 (test)正在执行1
线程:Thread-2 (test)正在执行2
线程:Thread-1 (test)正在执行2
执行结束,一共耗时3.003098249435425
'''
继承式
使用Thread子类创建线程 操作步骤:
自定义类继承threading模块下的Thread类 实现run方法
import threading
import time
from threading import Thread
class SubThread ( Thread) :
def run ( self) :
for i in range ( 3 ) :
time. sleep( 1 )
print ( f'线程: { threading. current_thread( ) . name} 正在执行 { i} ' )
if __name__ == '__main__' :
print ( '主进程开始执行' )
start = time. time( )
lst = [ SubThread( ) for i in range ( 2 ) ]
for item in lst:
item. start( )
for item in lst:
item. join( )
print ( f'执行结束,一共耗时 { time. time( ) - start} ' )
'''
主进程开始执行
线程:Thread-2正在执行0
线程:Thread-1正在执行0
线程:Thread-2正在执行1
线程:Thread-1正在执行1
线程:Thread-2正在执行2
线程:Thread-1正在执行2
执行结束,一共耗时3.002912759780884
'''
线程之间数据共享
from threading import Thread
a = 100
def add ( ) :
print ( '加进程开始执行' )
global a
a+= 30
print ( f'a的值为: { a} ' )
print ( '加进程执行结束' )
def sub ( ) :
print ( '减进程开始执行' )
global a
a-= 50
print ( f'a的值为: { a} ' )
print ( '减进程执行结束' )
if __name__ == '__main__' :
print ( '主进程开始执行' )
print ( f'a = { a} ' )
t1 = Thread( target= add)
t2 = Thread( target= sub)
t1. start( )
t2. start( )
t1. join( )
t2. join( )
print ( '主进程执行结束' )
'''
主进程开始执行
a = 100
加进程开始执行
a的值为:130
减进程开始执行
加进程执行结束
a的值为:80
减进程执行结束
主进程执行结束
'''
线程操作共享数据的安全性问题
import threading
import time
from threading import Thread
ticket = 50
def sale_ticket ( ) :
global ticket
for i in range ( 20 ) :
if ticket> 0 :
print ( f'线程: { threading. current_thread( ) . name} 正在出售第 { ticket} 张票' )
ticket -= 1
time. sleep( 1 )
if __name__ == '__main__' :
for i in range ( 3 ) :
t = Thread( target= sale_ticket)
t. start( )
'''
线程:Thread-1 (sale_ticket)正在出售第50张票
线程:Thread-2 (sale_ticket)正在出售第49张票
线程:Thread-3 (sale_ticket)正在出售第48张票
线程:Thread-3 (sale_ticket)正在出售第47张票
线程:Thread-2 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第44张票
线程:Thread-2 (sale_ticket)正在出售第44张票
线程:Thread-3 (sale_ticket)正在出售第44张票
'''
出现多个线程同时出售同一张票 问题原因:线程1判断完if ticket>0:
之后,后续代码还未执行,其他线程就开始进行判断
Lock
锁定状态:acquire()
非锁定状态:release()
import threading
import time
from threading import Lock, Thread
ticket = 50
lock_obj = Lock( )
def sale_ticket ( ) :
global ticket
for i in range ( 20 ) :
lock_obj. acquire( )
if ticket> 0 :
print ( f'线程: { threading. current_thread( ) . name} 正在出售第 { ticket} 张票' )
ticket -= 1
time. sleep( 1 )
lock_obj. release( )
if __name__ == '__main__' :
for i in range ( 3 ) :
t = Thread( target= sale_ticket)
t. start( )
'''
线程:Thread-1 (sale_ticket)正在出售第50张票
线程:Thread-2 (sale_ticket)正在出售第49张票
线程:Thread-2 (sale_ticket)正在出售第48张票
线程:Thread-1 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第46张票
线程:Thread-1 (sale_ticket)正在出售第45张票
线程:Thread-1 (sale_ticket)正在出售第44张票
线程:Thread-1 (sale_ticket)正在出售第43张票
线程:Thread-3 (sale_ticket)正在出售第42张票
线程:Thread-3 (sale_ticket)正在出售第41张票
线程:Thread-3 (sale_ticket)正在出售第40张票
线程:Thread-3 (sale_ticket)正在出售第39张票
线程:Thread-3 (sale_ticket)正在出售第38张票
线程:Thread-3 (sale_ticket)正在出售第37张票
线程:Thread-3 (sale_ticket)正在出售第36张票
线程:Thread-3 (sale_ticket)正在出售第35张票
线程:Thread-3 (sale_ticket)正在出售第34张票
线程:Thread-3 (sale_ticket)正在出售第33张票
线程:Thread-1 (sale_ticket)正在出售第32张票
线程:Thread-1 (sale_ticket)正在出售第31张票
线程:Thread-1 (sale_ticket)正在出售第30张票
线程:Thread-2 (sale_ticket)正在出售第29张票
线程:Thread-2 (sale_ticket)正在出售第28张票
线程:Thread-1 (sale_ticket)正在出售第27张票
线程:Thread-3 (sale_ticket)正在出售第26张票
线程:Thread-2 (sale_ticket)正在出售第25张票
线程:Thread-2 (sale_ticket)正在出售第24张票
线程:Thread-2 (sale_ticket)正在出售第23张票
线程:Thread-1 (sale_ticket)正在出售第22张票
线程:Thread-1 (sale_ticket)正在出售第21张票
线程:Thread-1 (sale_ticket)正在出售第20张票
线程:Thread-1 (sale_ticket)正在出售第19张票
线程:Thread-1 (sale_ticket)正在出售第18张票
线程:Thread-3 (sale_ticket)正在出售第17张票
线程:Thread-3 (sale_ticket)正在出售第16张票
线程:Thread-3 (sale_ticket)正在出售第15张票
线程:Thread-3 (sale_ticket)正在出售第14张票
线程:Thread-3 (sale_ticket)正在出售第13张票
线程:Thread-2 (sale_ticket)正在出售第12张票
线程:Thread-2 (sale_ticket)正在出售第11张票
线程:Thread-2 (sale_ticket)正在出售第10张票
线程:Thread-2 (sale_ticket)正在出售第9张票
线程:Thread-2 (sale_ticket)正在出售第8张票
线程:Thread-1 (sale_ticket)正在出售第7张票
线程:Thread-1 (sale_ticket)正在出售第6张票
线程:Thread-1 (sale_ticket)正在出售第5张票
线程:Thread-1 (sale_ticket)正在出售第4张票
线程:Thread-1 (sale_ticket)正在出售第3张票
线程:Thread-3 (sale_ticket)正在出售第2张票
线程:Thread-3 (sale_ticket)正在出售第1张票
'''
生产者与消费者模式
是线程模型中的经典问题,与编程语言无关。当程序中出现了明确的两类任务,一个任务负责生产数据,一个任务负责处理生产的数据时就可以使用该模式。
生产数据放入仓库
从仓库取出数据
生产者线程
中间仓库
消费者线程
方法 功能 put(item) 向队列中放置数据,如果队列为满,则阻塞 get() 从队列中取走数据,如果队列为空,则阻塞 join() 如果队列不为空,则等待队列变为空 task_done() 消费者从队列中取走一项数据,当队列变为空时,唤醒调用join()的线程
import time
from queue import Queue
from threading import Thread
class Producer ( Thread) :
def __init__ ( self, name, queue) :
Thread. __init__( self, name= name)
self. queue = queue
def run ( self) :
for i in range ( 1 , 6 ) :
print ( f' { self. name} 将产品 { i} 放入队列' )
self. queue. put( i)
time. sleep( 1 )
print ( '生产者完成了所有数据的存放' )
class Consumer ( Thread) :
def __init__ ( self, name, queue) :
Thread. __init__( self, name= name)
self. queue = queue
def run ( self) :
for _ in range ( 5 ) :
value = self. queue. get( )
print ( f' { self. name} 将产品 { value} 取出' )
time. sleep( 1 )
print ( '消费完成了所有数据的取出' )
if __name__ == '__main__' :
queue = Queue( )
p = Producer( 'Producer' , queue)
c = Consumer( 'Consumer' , queue)
p. start( )
c. start( )
p. join( )
c. join( )
print ( '主线程执行结束' )
'''
Producer将产品1放入队列
Consumer将产品1取出
Producer将产品2放入队列
Consumer将产品2取出
Producer将产品3放入队列
Consumer将产品3取出
Producer将产品4放入队列
Consumer将产品4取出
Producer将产品5放入队列
Consumer将产品5取出
生产者完成了所有数据的存放
消费完成了所有数据的取出
主线程执行结束
'''