# -*- coding: utf-8 -*-
'''
Py并发编程及应用.py
(多线程:创建、同步锁、线程通信、线程池、线程局部变量、线程定时器、任务调度器)
(多进程:创建、进程池、进程通信、并发及异步并发爬虫、全局解释器锁、垃圾回收机制)
深入:
1、线程锁、线程通信的概念和运用
2、任务调度器的运用
3、asyncio模块 异步并发模块(爬虫应用)
4、aiohttp模块 异步的requests(爬虫应用)
注意:
1、并发和并行是两个概念,
并行指在同一时刻有多条指令在多个处理器上同时执行;
并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
2、默认情况下,主线程的名字为 MainThread,用户启动的多个线程的名字依次为 Thread-1、Thread-2、Thread-3、...、Thread-n 等。
3、注意线程执行完成后已经死亡状态,再次执行将引发 RuntimeError异常。另外对处于新建状态的线程两次调用start()方法也会引发异常。
4、如果要将某个线程设置为后台线程,则必须在该线程启动之前进行设置。也就是说,将 daemon 属性设为 True,必须在 start() 方法调用之前进行,否则会引发 RuntimeError 异常。
5、加锁操作RLock较为常用,注意加锁方式一般为 先加锁,然后try操作,最后finally解锁。
6、注意解决死锁问题的方式有:1、避免多次加锁 2、相同的加锁顺序 3、使用定时锁 即调用acquire()方法加锁时候指定timeout参数 4、死锁检测
7、注意线程池返回的Future对象方法result()会阻塞当前主线程;另外线程池类方法map()后可循环迭代每一个线程
8、Timer定时器只能控制函数在指定时间内执行一次。
如果要使用 Timer 控制函数多次重复执行,则需要函数内嵌套定时器 来循环执行下一次调度。
9、创建子进程时,multiprocessing.Process类通过实例化对象指定target参数执行函数 在IDLE里不显示函数执行内容。
通过在控制台通过执行文件可显示target参数执行函数的内容。
10、通过 multiprocessing.Process 来创建并启动进程时,程序必须先判断if __name__=='__main__':,否则可能引发异常。
11、进程通信中,使用实例化创建multiprocessing.Pipe类管道对象返回两个PipeConnection对象,
其中前一个PipeConnection对象用于接受数据,后一个PipeConnection对象用于发送数据
12、GIL 不能绝对保证线程安全
因为即便 GIL 仅允许一个 Python 线程执行,但Python 还有 check interval 这样的抢占机制。
使用:
第一部分:多线程相关
threading模块 线程封装
一、Python 主要通过两种方式来创建线程:
1、使用 threading 模块中的 Thread 类的构造器创建线程。
即直接对类 threading.Thread 进行实例化,并调用实例化对象的 start 方法创建线程。
2、继承 threading 模块中的 Thread 类创建线程类。
即用 threading.Thread 派生出一个新的子类,将新建类实例化,并调用其 start 方法创建线程。
二、Lock类 和 Rlock类 互斥锁 用来解决数据不同步的问题
threading模块提供了 Lock 和 RLock 两个类,都各自提供两个方法来 加互斥锁 和 解除互斥锁
三、线程通信
当线程在系统中运行时,线程的调度具有一定的透明性,通常程序无法准确控制线程的轮换执行,如果有需要,Python 可通过线程通信来保证线程协调运行。
线程通信的实现方式:
1、Condition类 来自threading模块
2、queue模块 队列
3、Event类 来自threading模块
四、线程池 ThreadPoolExecutor类
Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,
其中 ThreadPoolExecutor类 用于创建线程池,而 ProcessPoolExecutor类 用于创建进程池。
1、线程池类方法(submit()、map()、shudown())
2、submit()类方法提交线程池后返回的Future对象方法
Future对象用result()方法来获取线程任务的运回值,但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。
可通过 Future 的 add_done_callback() 方法来添加回调函数来避免直接调用result()方法阻塞线程
另外线程池类方法map()后可循环迭代每一个线程
五、线程局部变量 threading.local()
六、定时器 Timer子类 来自threading模块
七、任务调度器 sched模块 任务调度器模块
1、实例化任务调度器 sched.scheduler()
2、实例化任务调度器方法:绝对时间执行函数任务、相对时间执行函数任务、取消任务等
第二部分:多进程相关
multiprocessing模块 进程封装
八、multiprocessing.Process类 进程黄建
Python 在 multiprocessing模块下提供了 Process类 来创建新进程。
与 Thread 类似的是,使用 Process 创建新进程也有两种方式:
1、使用 multiprocessing模块 中的 Process类 来实例化对象创建新进程,并指定函数作为target参数函数
2、继承 Process类 ,并重写他的 run() 方法来创建进程类,程序创建Process子类的实例作为进程
九、进程启动的三种方式 spawn(windows仅支持此方式)、fork、forkserver
multiprocessing.set_start_method(' ')函数用于设置启动进程的方式,设置代码必须放在多进程代码之前。
十、进程池 multiprocessing.Pool类
通过 multiprocessing 模块的 Pool类来实例化创建进程池,然后通过进程池方法提交进程池、关闭进程池、等待进程池等
十一、进程通信 multiprocessing.Queue类 和 multiprocessing.Pipe类
Python 为进程通信提供了两种机制:
Queue:一个进程向 Queue 中放入数据,另一个进程从 Queue 中读取数据。
Pipe:Pipe 代表连接两个进程的管道。程序在调用 Pipe() 函数时会产生两个连接端,分别交给通信的两个进程,接下来进程既可从该连接端读取数据,也可向该连接端写入数据。
十二、异步并发模块的性能提升需要后期单独重点深入
asyncio模块 异步并发模块
aiohttp模块 异步的requests
十三、垃圾回收机制 垃圾回收机制引用计数机制
引用计数机制 即:对象的引用计数值为 0 时,说明这个对象永不再用,自然它就变成了垃圾,需要被回收。
gc模块 手动启动垃圾回收机制
'''
# =============================================================================
# #threading模块 线程封装
# #threading模块 Python 3 之后的线程模块,提供了功能丰富的多线程支持
# =============================================================================
#Python 主要通过两种方式来创建线程:
#1、使用 threading 模块中的 Thread 类的构造器创建线程。
#即直接对类 threading.Thread 进行实例化,并调用实例化对象的 start 方法创建线程。
#2、继承 threading 模块中的 Thread 类创建线程类。
#即用 threading.Thread 派生出一个新的子类,将新建类实例化,并调用其 start 方法创建线程。
import threading
help(threading)
threading.__doc__
threading.__file__
threading.__all__
dir(threading)
#使用 threading 模块中的 Thread 类的构造器创建线程。
#__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)
#构造器涉及如下几个参数:
#group: 指定该线程所属的线程组。目前该参数还未实现,因此它只能设为 None。
#target: 指定该线程要调度的目标方法。
#args: 指定一个元组,以位置参数的形式为 target 指定的函数传入参数。元组的第一个元素传给 target 函数的第一个参数,元组的第二个元素传给 target 函数的第二个参数……依此类推。
#kwargs: 指定一个字典,以关键字参数的形式为 target 指定的函数传入参数。
#daemon: 指定所构建的线程是否为后台线程 即 守护线程。
help(threading.Thread)
threading.Thread.__doc__
dir(threading.Thread)
help(threading.Thread.start)
help(threading.Thread.run)
help(threading.Thread.daemon)
help(threading.Thread.join)
####################
#创建线程方法一: 通过Thread类的构造器创建并启动多线程的步骤
#1、调用 Thread 类的构造器创建线程对象。在创建线程对象时,target参数 指定的函数将作为线程执行体。
#2、调用线程对象的 start() 方法启动该线程。
import threading
#先定义一个普通的action函数,准备作为线程执行体 即:target参数函数
def action(max):
for i in range(max):
#threading.current_thread()获取当前线程
#.getName()获取名称
print(threading.current_thread().getName() + ' ' + str(i)) #current_thread()获取当前线程;#getName()获取当前线程的名字
#主线程的执行体
for i in range(100):
#输出当前线程的名称
print(threading.current_thread().getName() + ' ' + str(i))
if i == 20:
#创建并启动第一个线程
#target参数 指定函数作为线程执行体,
#args参数 元组形式位置参数,用于指定为target参数的函数的参数。
t1=threading.Thread(target = action, args=(100,))
t1.start()
#创建并启动第二个线程
t2=threading.Thread(target = action, args=(100,))
t2.start()
#注意:
#默认情况下,主线程的名字为 MainThread,用户启动的多个线程的名字依次为 Thread-1、Thread-2、Thread-3、...、Thread-n 等。
print('主线程执行完成!')
#Threading 模块中,除了 current_thread() 函数外,还经常使用如下 2 个函数:
#threading.enumerate(): 返回一个正运行线程的 list。“正运行”是指线程处于“启动后,且在结束前”状态,不包括“启动前”和“终止后”状态。
#threading.activeCount(): 返回正在运行的线程数量。与 len(threading.enumerate()) 有相同的结果
####################
#创建线程方法二: 通过继承 Thread 类来创建并启动线程的步骤
#1、定义 Thread 类的子类,并重写该类的 run() 方法。
#run() 方法的方法体就代表了线程需要完成的任务,因此把 run() 方法称为线程执行体。
#2、创建 Thread 子类的实例,即创建线程对象。
#3、调用线程对象的 start() 方法来启动线程。
import threading
#先定义类继承Thread类,来准备实例化创建和启动线程
class FkThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.i = 0
#重写run()方法,run()方法代表线程需要完成的任务,成为线程执行体
def run(self):
while self.i < 100:
print(threading.current_thread().getName() + ' ' + str(self.i))
self.i += 1
for i in range(100):
print(threading.current_thread().getName() + ' ' + str(i))
if i == 20:
#实例化来创建第一个线程 然后 启动线程
ft1 = FkThread()
ft1.start()
#实例化来创建第二条线程 然后 启动线程
ft2 = FkThread()
ft2.start()
print('主线程执行完成!')
####################
#线程的状态
##########
#线程的新建状态 和 线程的就绪状态
#启动线程使用start()方法,而不是run()方法
#调用 start() 方法来启动线程,系统会把该 run() 方法当成线程执行体来处理;
#在调用线程对象的 start() 方法之后,该线程立即进入就绪状态(相当于“等待执行”),但该线程并未真正进入运行状态。
#但如果直接调用线程对象的 run() 方法,则 run() 方法立即就会被执行,而且在该方法返回之前其他线程无法并发执行。
import threading
# 先定义函数 准备作为线程执行体的target参数函数
def action(max):
for i in range(max):
#直接调用run()方法时,Thread的name属性返回的是该对象的名字
#而不是当前线程的名字
#使用threading.current_thread().name总是获取当前线程的名字
print(threading.current_thread().name + " " + str(i))
for i in range(100):
#调用Thread的currentThread()方法获取当前线程
print(threading.current_thread().name + " " + str(i))
if i == 20:
# 直接调用线程对象的run()方法
# 系统会把线程对象当成普通对象,把run()方法当成普通方法
# 所以下面两行代码并不会启动两个线程,而是依次执行两个run()方法
threading.Thread(target=action,args=(100,)).run()
threading.Thread(target=action,args=(100,)).run()
##########
#线程的运行状态
#如果处于就绪状态的线程获得了 CPU,开始执行 run() 方法的线程执行体,则该线程处于运行状态。
##########
#线程的阻塞状态
#线程将会进入阻塞状态的几种情况:
#1、线程调用 sleep() 方法主动放弃其所占用的处理器资源。
#2、线程调用了一个阻塞式 I/O 方法,在该方法返回之前,该线程被阻塞。
#3、线程试图获得一个锁对象,但该锁对象正被其他线程所持有。关于锁对象的知识,后面将有更深入的介绍。
#4、线程在等待某个通知(Notify)。
##########
#线程的死亡状态
#线程结束后就处于死亡状态。线程结束有两种方式:一种是run()方法或代表线程执行体的targe函数执行完成。另一种是出现异常。
#is_alive()方法 测试某个显示是否已经死亡。
#当线程处于就绪、运行、阻塞三种状态时,该方法将返回 True;当线程处于新建、死亡两种状态时,该方法将返回 False。
import threading
#先定义一个函数 用于准备作为线程执行体target参数函数来使用
def action(max):
'''
先定义一个函数 用于准备作为线程执行体target参数函数来使用
循环输出当前线程名称100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
#创建线程对象 进入新建状态
sd=threading.Thread(target= action, args=(100,))
for i in range(300):
print(threading.current_thread().name + ' ' + str(i))
if i == 20:
#启动线程 进入就绪状态
sd.start()
print(sd.is_alive()) #当线程处于就绪、运行、阻塞三种状态时,该方法将返回 True;当线程处于新建、死亡两种状态时,该方法将返回 False。
# if i > 20:
# #注意线程执行完成后已经死亡状态,再次执行将引发 RuntimeError异常。另外对处于新建状态的线程两次调用start()方法也会引发异常。
# sd.start()
####################
#join()方法
#join()方法让一个线程等待另一个线程完成。
#当某个程序执行流中调用其他线程的 join() 方法时候,调用线程将被阻塞,直到被join()方法加入的join线程执行完成。
import threading
#先定义一个函数 用于准备作为线程执行体target参数函数来使用
def action(max):
'''
先定义一个函数 用于准备作为线程执行体target参数函数来使用
循环输出当前线程名称100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
#直接新建并启动 子线程
threading.Thread(target=action,args=(100,),name='新线程').start()
#演示join()方法
for i in range(100):
if i == 20:
#演示阻塞第20次执行线程,等运行完 被join的线程后 继续运行20次以后的线程
jt=threading.Thread(target=action,args=(100,),name='被join的线程')
jt.start()
#join(timeout=None)方法 还可以指定一个 timeout 参数,该参数指定等待被 join 的线程的时间最长为 timeout 秒。如果在 timeout 秒内被 join 的线程还没有执行结束,则不再等待
jt.join()
print(threading.current_thread().name + ' ' + str(i))
####################
#后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程。调用 Thread 对象的 daemon 属性可以将指定线程设置成后台线程
#创建后台线程有两种方式:
#1、主动将线程的 daemon属性 设置为 True
#2、也可在创建Thread对象时通过daemon参数将其设为后台线程 即 守护线程。
#后台线程有一个特征,如果所有的前台线程都死亡了,那么后台线程会自动死亡。
#Python 解释器的垃圾回收线程就是典型的后台线程。
import threading
#先定义一个函数 用于准备作为线程执行体target参数函数来使用
def action(max):
'''
先定义一个函数 用于准备作为线程执行体target参数函数来使用
循环输出当前线程名称100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
t=threading.Thread(target=action,args=(100,),name='后台线程')
# 将此线程设置成后台线程,通过设置线程的daemon属性为 True
# 也可在创建Thread对象时通过daemon参数将其设为后台线程
#注意:如果要将某个线程设置为后台线程,则必须在该线程启动之前进行设置。
#也就是说,将 daemon 属性设为 True,必须在 start() 方法调用之前进行,否则会引发 RuntimeError 异常。
t.daemon=True
t.start()
for i in range(10):
print(threading.current_thread().name + ' ' +str(i))
# =============================================================================
# #Lock类 和 Rlock类 互斥锁 用来解决数据不同步的问题
# #threading模块提供了 Lock 和 RLock 两个类,都各自提供两个方法来 加互斥锁 和 解除互斥锁
# =============================================================================
##########
#银行取钱 数据不同步 问题示例
#先定义一个账户类,并且封装账户编号 和 账户余额
class Account:
'''封装账户编号 和 余额 这两个成员属性'''
def __init__(self,account_no,balance):
self.account_no = account_no
self.balance = balance
import threading
import time
#定义一个函数 来准备作为线程的执行函数 来模拟取钱操作
def draw(account,draw_amount):
'''传入参数 实例化账户 和 取钱金额;来实现取钱操作,如果实例化账户余额足够则取款成功,否则取款失败'''
if account.balance >= draw_amount:
print(threading.current_thread().name + '取钱成功,吐出钞票:' + str(draw_amount))
#time.sleep(0.2)
account.balance -= draw_amount
print('余额为:' + str(account.balance))
else:
print(threading.current_thread().name + '取钱失败,余额不足!')
#创建实例化 账户对象
acct= Account('1234567',1000)
#启动两个线程模拟对同一个实例化 的账户对象进行取钱操作
#多次运行,则会出现 余额不足但仍取款成功 的问题
#原由线程的 run()方法不具有线程安全性
threading.Thread(name='甲', target=draw, args=(acct,800)).start()
threading.Thread(name='乙', target=draw, args=(acct,800)).start()
##########
#Lock类 和 RLock类 的相同: 加锁和解锁方法
#1、acquire(blocking=True, timeout=-1): 请求对 Lock 或 RLock 加锁,其中 timeout 参数指定加锁多少秒。
#2、release(): 释放锁。
#Lock 和 RLock 的不同:
#1、threading.Lock: #它是一个基本的锁对象,每次只能锁定一次,其余的锁请求,需等待锁释放后才能获取。
#2、threading.RLock: #它代表可重入锁(Reentrant Lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。
#如果使用 RLock,那么 acquire() 和 release() 方法必须成对出现。
#如果调用了 n 次 acquire() 加锁,则必须调用 n 次 release() 才能释放锁。
import threading
help(threading.Lock)
help(threading.RLock)
#示例
#修改Account类,加锁,来让其线程安全
#修改的该类中,定义了一个 RLock 对象。在程序中实现 draw() 方法时,进入该方法开始执行后立即请求对 RLock 对象加锁,
#当执行完 draw() 方法的取钱逻辑之后,程序使用 finally 块来确保释放锁。
class Account:
def __init__(self,account_no, balance):
self.account_no = account_no
self._balance = balance
self.lock = threading.RLock()
def getBalance(self):
return self._balance
def draw(self,draw_amount):
#加锁操作
#加锁操作RLock较为常用,注意加锁方式一般为 先加锁,然后try操作,最后finally解锁。
self.lock.acquire()
try:
if self._balance >= draw_amount:
print(threading.current_thread().name + '取钱成功,吐出钞票:' + str(draw_amount))
time.sleep(0.001)
#取钱成功后修改余额
self._balance -= draw_amount
print('\t余额为:' + str(self._balance))
else:
print(threading.current_thread().name + '取钱失败,余额不足!')
finally:
#解锁操作
self.lock.release()
#定义函数 准备作为线程的target参数执行函数 来模拟取钱操作
def draw(account,draw_amount):
account.draw(draw_amount)
acct=Account('123456789',1000)
threading.Thread(name='甲', target=draw, args=(acct,800)).start()
threading.Thread(name='乙', target=draw, args=(acct,800)).start()
##########
#死锁
#当两个线程相互等待对方释放同步监视器时,就会发生死锁。
#Py解释器没有监测,也没有采取措施来处理死锁情况,所以在进行多线程时应该采取措施避免出现死锁。
#注意:一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。
#死锁示例
#在系统中出现多个同步监视器的情况下,很容易发生死锁
import threading
import time
class A:
def __init__(self):
self.lock=threading.RLock()
def foo(self,b):
try:
#加锁操作
self.lock.acquire()
print('当前线程名:' + threading.current_thread().name + '进入A实例的foo()方法')
time.sleep(0.02)
print('当前线程名:' + threading.current_thread().name + '企图调用B实例的last()方法')
#调用另一个类实例方法中也有加锁解锁操作,容易发生死锁问题
b.last()
finally:
#解锁操作
self.lock.release()
def last(self):
try:
self.lock.acquire()
print('进入了A类的last()方法内部')
finally:
self.lock.release()
class B:
def __init__(self):
self.lock=threading.RLock()
def bar(self,a):
try:
self.lock.acquire()
print('当前线程名:' + threading.current_thread().name + '进入了B实例的bar()方法')
time.sleep(0.2)
print('当前线程名:' + threading.current_thread().name + '企图调用A实例的last()方法')
a.last()
finally:
self.lock.release()
def last(self):
try:
self.lock.acquire()
print('进入了B类的last()方法内部')
finally:
self.lock.release()
a=A()
b=B()
def init():
threading.current_thread().name='主线程'
a.foo(b)
print('进入了主线程之后')
def action():
threading.current_thread().name='副线程'
b.bar(a)
print('进入了副线程之后')
#以action()为target参数函数启动新线程
#两个线程开启 即出现死锁问题。
#注意解决死锁问题的方式有:1、避免多次加锁 2、相同的加锁顺序 3、使用定时锁 即调用acquire()方法加锁时候指定timeout参数 4、死锁检测
threading.Thread(target=action).start()
#init() #若开启则出现死锁问题。
# =============================================================================
# #线程通信
# #当线程在系统中运行时,线程的调度具有一定的透明性,通常程序无法准确控制线程的轮换执行,如果有需要,Python 可通过线程通信来保证线程协调运行。
# #线程通信的实现方式:1、threading模块的Condition类 2、queue队列模块 3、threading模块的Event类
# =============================================================================
####################
#Condition类
#来自threading线程模块
#Condition对象可以让那些己经得到 Lock 对象却无法继续执行的线程释放 Lock 对象,
#Condition对象也可以唤醒其他处于等待状态的线程
import threading
help(threading.Condition)
threading.Condition.__doc__
#Condition 类提供了如下几个方法:
#acquire([timeout]) / release(): 调用 Condition 关联的 Lock 的 acquire() 或 release() 方法。
#wait([timeout]): 导致当前线程进入 Condition 的等待池等待通知并释放锁,直到其他线程调用该 Condition 的 notify() 或 notify_all() 方法来唤醒该线程。在调用该 wait() 方法时可传入一个 timeout 参数,指定该线程最多等待多少秒。
#notify(): 唤醒在该 Condition 等待池中的单个线程并通知它,收到通知的线程将自动调用 acquire() 方法尝试加锁。如果所有线程都在该 Condition 等待池中等待,则会选择唤醒其中一个线程,选择是任意性的。
#notify_all(): 唤醒在该 Condition 等待池中等待的所有线程并通知它们。
#示例 使用Condition()对象方法实现线程通信
import threading
class Account:
'''
定义一个Account 类,提供 draw() 和 deposit() 两个方法,分别对应于该账户的取钱和存款操作。
因为这两个方法可能需要并发修改 Account 类的 self.balance 成员变量的值,所以它们都使用 Lock 来控制线程安全。
除此之外,这两个方法还使用了 Condition 的 wait() 和 notify_all() 来控制线程通信。
实现要求存款者和取钱者不断地重复存款、取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。
不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。
'''
def __init__(self,account_no, balance):
#封装账户编号 和 账户余额 的两个变量
self.account_no = account_no
self._balance = balance
self.cond = threading.Condition()
self._flag = False #定义代表是否已经存钱的旗帜
def getBalance(self):
#因为账户余额不允许随便修改,所以只为self._balance提供getter方法
return self._balance
def draw(self,draw_amount):
#加锁操作 Condition类对象加锁操作,
#Condition类对象的加锁方法 相当于调用 Condition 绑定的Lock的acquire()
self.cond.acquire()
try:
if not self._flag:
#Condition类对象阻塞操作,将当前线程放入等待池 等待通知
self.cond.wait() #如果没有存钱,则将取钱方法阻塞 并放入等待池等待通知
else:
print(threading.current_thread().name + '取钱:' + str(draw_amount))
self._balance -= draw_amount #取钱 并 余额变动
print('账户余额为:' + str(self._balance))
self._flag = False #将标识账户是否已有存款的旗帜设为 False
#Condition类对象唤醒操作, 唤醒等待池中的其他所有线程 并 通知他们
self.cond.notify_all() #唤醒等待池中的其他所有线程 并 通知他们
#解锁操作 Condition类对象解锁操作
finally:
self.cond.release()
def deposit(self, deposit_amount):
#加锁操作 Condition类对象加锁操作,
#Condition类对象的加锁方法 相当于调用 Condition 绑定的Lock的acquire() self.cond.acquire()
self.cond.acquire()
try:
if self._flag:
#Condition类对象阻塞操作,将当前线程放入等待池 等待通知
self.cond.wait() #如果已经存钱,则将存钱方法阻塞 并放入等待吃等待通知
else:
print(threading.current_thread().name + '存款:' + str(deposit_amount))
self._balance += deposit_amount #存钱 并 余额变动
print('账户余额为:' + str(self._balance))
self._flag = True #将标识账户是否已有存款的旗帜设为 True
#Condition类对象唤醒操作, 唤醒等待池中的其他所有线程 并 通知他们
self.cond.notify_all() #唤醒等待池中的其他所有线程 并 通知他们
#解锁操作 Condition类对象解锁操作
finally:
self.cond.release()
'''
程序使用 Condition 的 wait() 和 notify_all() 方法进行控制,
对存款者线程而言,当程序进入 deposit() 方法后,如果 self._flag 为 True,则表明账户中已有存款,
程序调用 Condition 的 wait() 方法被阻塞;否则,程序向下执行存款操作,
当存款操作执行完成后,系统将 self._flag 设为 True,然后调用 notify_all() 来唤醒其他被阻塞的线程。
如果系统中有存款者线程,存款者线程也会被唤醒,但该存款者线程执行到 ① 号代码处时再次进入阻塞状态,
只有执行 draw() 方法的取钱者线程才可以向下执行。
同理,取钱者线程的运行流程也是如此
'''
#定义一个函数,作为线程的target参数执行函数。 模拟重复max次执行取钱操作
def draw_many(account, draw_amount,max):
for i in range(max):
account.draw(draw_amount)
#定义一个函数,作为线程的target参数执行函数。 模拟重复max次执行存钱操作
def deposit_many(account,deposit_amount,max):
for i in range(max):
account.deposit(deposit_amount)
acct=Account('123456789',0) #实例化创建一个账户
threading.Thread(name='取钱者 X ', target=draw_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 甲 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 乙 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 丙 ', target=deposit_many, args=(acct,800,100)).start()
####################
#queue队列模块
#掌握Queue阻塞队列的特性后,利用Queue来实现线程通信
import queue
help(queue)
queue.__doc__
queue.__all__
dir(queue)
#queue模块的三个队列类的简单介绍如下:
#queue.Queue(maxsize=0): 代表 FIFO(先进先出)的常规队列,maxsize 可以限制队列的大小。如果队列的大小达到队列的上限,就会加锁,再次加入元素时就会被阻塞,直到队列中的元素被消费。如果将 maxsize 设置为 0 或负数,则该队列的大小就是无限制的。
#queue.LifoQueue(maxsize=0): 代表 LIFO(后进先出)的队列,与 Queue 的区别就是出队列的顺序不同。
#PriorityQueue(maxsize=0): 代表优先级队列,优先级最小的元素先出队列。
#这三个队列类的属性和方法基本相同, 它们都提供了如下属性和方法:
#
#Queue.qsize(): 返回队列的实际大小,也就是该队列中包含几个元素。
#Queue.empty(): 判断队列是否为空。
#Queue.full(): 判断队列是否已满。
#
#Queue.put(item, block=True, timeout=None):向队列中放入元素。
#如果队列己满,且 block 参数为 True(阻塞),当前线程被阻塞,
#timeout 指定阻塞时间,如果将 timeout 设置为 None,则代表一直阻塞,直到该队列的元素被消费;
#如果队列己满,且 block 参数为 False(不阻塞),则直接引发 queue.FULL 异常。
#
#Queue.put_nowait(item): 向队列中放入元素,不阻塞。相当于在上一个方法中将 block 参数设置为 False。
#
#Queue.get(item, block=True, timeout=None):从队列中取出元素(消费元素)。
#如果队列已满,且 block 参数为 True(阻塞),当前线程被阻塞,
#timeout 指定阻塞时间,如果将 timeout 设置为 None,则代表一直阻塞,直到有元素被放入队列中;
#如果队列己空,且 block 参数为 False(不阻塞),则直接引发 queue.EMPTY 异常。
#
#Queue.get_nowait(item): 从队列中取出元素,不阻塞。相当于在上一个方法中将 block 参数设置为 False。
#示例 阻塞队列线程
import queue
#先定义了一个大小为 2 的 Queue,程序先向该队列中放入两个元素,此时队列还没有满,两个元素都可以被放入。
#当程序试图放入第三个元素时,如果使用 put() 方法尝试放入元素将会阻塞线程
bq=queue.Queue(2) #实例化创建一个长度为2的先进先出常规队列
bq.put('Python')
bq.put('python')
print('11111111')
#bq.put('Python') #阻塞线程
print('22222222')
#示例 掌握Queue阻塞队列的特性后,利用Queue来实现线程通信
import threading
import time
import queue
def product(bq):
#定义函数 用于线程的target参数执行函数
#参数Queue队列对象,函数内实现队列的放入元素功能
str_tuple=('Python','Kotlin','Swift')
for i in range(9):
print(threading.current_thread().name + '生产者准备生产元组元素...')
time.sleep(0.2)
#Queue.put()方法 向队列中放入元素 如果队列已满则阻塞,直到元素被Queue.get()方法消费
bq.put(str_tuple[i % 3]) #%求余操作
print(threading.current_thread().name + '生产者生产元组元素完成!')
def consume(bq):
#定义函数 用于线程target参数执行函数
#参数Queue队列对象,函数内实现队列的取出元素消费功能
while True:
print(threading.current_thread().name + '消费者准备消费元组元素...')
time.sleep(0.2)
#Queue.get()方法 从队列中取出元素 如果队列已空则阻塞,直到元素被Queue.put()方法存入
t=bq.get()
print(threading.current_thread().name + '消费者消费 [%s] 元素完成!!!'%t)
#实例化创建一个容量为1的常规队列 先进先出 用作线程target参数执行函数的参数。
bq=queue.Queue(maxsize=1) #创建一个容量为1的队列
#启动3个生产者 线程
threading.Thread(target=product, args=(bq,)).start()
threading.Thread(target=product, args=(bq,)).start()
threading.Thread(target=product, args=(bq,)).start()
#Queue 队列的大小为 1,因此三个生产者线程无法连续放入元素,必须等待消费者线程取出一个元素后,其中的一个生产者线程才能放入一个元素。
#三个生产者线程都想向 Queue 中放入元素,但只要其中一个生产者线程向该队列中放入元素之后,其他生产者线程就必须等待,等待消费者线程取出 Queue 队列中的元素。
#启动1个消费 线程
threading.Thread(target=consume, args=(bq,)).start()
####################
#Event类 来自threading线程模块
#Event类 是一种非常简单的线程通信机制,一个线程发出一个 Event,另一个线程可通过该 Event 被触发。
#Event类 本身管理一个内部旗标,
#程序可以通过 Event 的 set() 方法将该旗标设置为 True,也可以调用 clear() 方法将该旗标设置为 False。
#程序可以调用 wait() 方法来阻塞当前线程,直到 Event 的内部旗标被设置为 True。
import threading
help(threading.Event)
threading.Event.__doc__
dir(threading.Event)
#Event 提供了如下方法:
#is_set(): 该方法返回 Event 的内部旗标是否为True。
#set(): 该方法将会把 Event 的内部旗标设置为 True,并唤醒所有处于等待状态的线程。
#clear(): 该方法将 Event 的内部旗标设置为 False,通常接下来会调用 wait() 方法来阻塞当前线程。
#wait(timeout=None): 该方法会阻塞当前线程。
#示例 Event的简单用法
import threading
import time
event=threading.Event()
def cal(name):
#定义函数 准备用于线程target参数执行函数
#函数内实现 等待事件,进入等待阻塞状态
print('%s 启动\t' % threading.current_thread().getName())
print('%s 准备开始计算状态' % name)
event.wait() #wait方法阻塞当前线程进入等待状态
#收到事件后才能继续进入运行状态
print('%s 收到通知了\t' % threading.current_thread().getName())
print('%s 正式开始计算!' % name)
#创建并启动2条线程,他们都会在启动、准备开始计算状态处 阻塞,等待。
threading.Thread(target=cal, args=('甲',)).start()
threading.Thread(target=cal, args=('乙',)).start()
time.sleep(2)
print('--------------------')
#发出事件 直到set方法设置Event内部旗帜为True 并 唤醒等待线程开始继续往下执行
print('主线程发出事件')
event.set() #set方法唤醒所有处于等待状态的线程
#示例
#结合 Event 的内部旗标,同样实现前面的 Account 的生产者-消费者效果:
#存钱线程(生产者)存钱之后,必须等取钱线程(消费者)取钱之后才能继续向下执行。
#Event 实际上类似于 Condition 和旗标的结合体,但 Event 本身并不带 Lock 对象,因此如果要实现线程同步,还需要额外的 Lock 对象。
import threading
class Account:
'''
定义一个Account 类,提供 draw() 和 deposit() 两个方法,分别对应于该账户的取钱和存款操作。
实现要求存款者和取钱者不断地重复存款、取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。
不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。
'''
#定义构造器 封装账户编号、账户余额两个类属性
def __init__(self,account_no,balance):
self.accout_no = account_no
self._balance = balance
self.lock = threading.Lock()
self.event = threading.Event()
#因为账户余额不允许随便修改,所以只为self._balance提供getter方法
def getBalance(self):
return self._balance
#定义取钱操作 线程加锁的安全方式
def draw(self,draw_amount):
#加锁操作
self.lock.acquire()
if self.event.is_set(): #is_set()方法返回 Event 的内部旗标是否为True。
print(threading.current_thread().name + '取钱:' + str(draw_amount))
self._balance -= draw_amount
print('账户余额为:' + str(self._balance))
#clear方法设置Event内部旗帜为False,通常接下来会调用 wait() 方法来阻塞当前线程。
self.event.clear() #clear方法设置Event内部旗帜为False,通常接下来会调用 wait() 方法来阻塞当前线程。
self.lock.release() #解锁操作
self.event.wait() #阻塞当前线程 进入等待状态
else:
self.lock.release() #解锁操作
self.event.wait() #阻塞当前线程 进入等待状态
#定义存钱操作 线程加锁的安全方式
def deposit(self,deposit_amount):
#加锁操作
self.lock.acquire()
if not self.event.is_set():
print(threading.current_thread().name + '存款:' + str(deposit_amount))
self._balance += deposit_amount
print('账户余额为:' + str(self._balance))
#set方法将内部旗标设置为 True,并唤醒所有处于等待状态的线程
self.event.set()
self.lock.release() #解锁操作
self.event.wait() #阻塞当前线程 进入等待状态
else:
self.lock.release() #解锁操作
self.event.wait() #阻塞当前线程 进入等待状态
#定义一个函数,模拟重复max次执行取钱操作
def draw_many(account, draw_amount,max):
for i in range(max):
account.draw(draw_amount)
#定义一个函数,模拟重复max次执行存钱操作
def deposit_many(account,deposit_amount,max):
for i in range(max):
account.deposit(deposit_amount)
acct=Account('123456789',0) #实例化创建一个账户
threading.Thread(name='取钱者 X ', target=draw_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 甲 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 乙 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 丙 ', target=deposit_many, args=(acct,800,100)).start()
# =============================================================================
# #线程池 ThreadPoolExecutor类
# #线程池的基类是 concurrent.futures 模块中的 Executor,
# #Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,
# #其中 ThreadPoolExecutor类 用于创建线程池,而 ProcessPoolExecutor类 用于创建进程池。
# =============================================================================
'''
注意:
1、submit()线程池类方法提交线程池后返回的Future对象,用result()方法来获取线程任务的运回值,
但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。
可通过 Future 的 add_done_callback() 方法来添加回调函数来避免直接调用result()方法阻塞线程
另外线程池类方法map()后可循环迭代每一个线程
'''
from concurrent.futures import ThreadPoolExecutor
help(ThreadPoolExecutor)
ThreadPoolExecutor.__doc__
dir(ThreadPoolExecutor)
#Exectuor类提供了如下常用方法:
#1、submit(fn, *args, **kwargs): 将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。
#2、map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),
#只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
#3、shutdown(wait=True): 关闭线程池
#程序将 fn 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,
#Future类主要用于获取线程任务函数的返回值。
#由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
#Future对象提供了如下方法:
#1、cancel(): 取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
#2、cancelled(): 返回 Future 代表的线程任务是否被成功取消。
#3、running(): 如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。
#4、done(): 如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
#5、result(timeout=None): 获取该 Future 代表的线程任务最后返回的结果。
#如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,
#其中 timeout 参数指定最多阻塞多少秒。
#6、exception(timeout=None): 获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
#7、add_done_callback(fn): 为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
'''
使用线程池来执行线程任务的步骤如下:
1、调用 ThreadPoolExecutor 类的构造器创建一个线程池。
2、定义一个普通函数作为线程任务。
3、调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
4、当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
'''
###################
#示例
#示例如何使用线程池来执行线程任务
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定义一个函数 准备用作线程池fn参数函数
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += 1
return my_sum
#实例化创建线程池 参数max_workers指定工作线程数量为2条
pool=ThreadPoolExecutor(max_workers=2) #创建一个包含2条线程的线程池
#类方法submit() 将fn参数函数提交给线程池
future1=pool.submit(action,50) #向线程池提交一个fn参数函数action,50为函数参数
future2=pool.submit(action,100) #继续向线程池提交一个fn参数函数action,100为函数参数
type(future1) #查看 实例化提交线程池以后的类型
#类方法done() 判断提交线程池后的future对象 即线程任务 是否运行完成
time.sleep(1)
print(future1.done()) #输出future对象是否运行完毕
time.sleep(1)
print(future2.done()) #输出future对象是否运行完毕
#类方法result() 返回提交线程池后的future对象 即线程任务 的程序运行结果
#注意提交线程池后返回的Future对象,用result()方法来获取线程任务的运回值,
#但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。
#可通过 Future 的 add_done_callback() 方法来添加回调函数来避免直接调用result()方法阻塞线程
print(future1.result()) #输出future对象的程序运行结果
print(future2.result()) #输出future对象的程序运行结果
#类方法shutdown() 关闭线程池
pool.shutdown() #线程池关闭
####################
#示例 add_done_callback() 提交线程池后的Future对象方法 添加回调函数
#通过 Future 的 add_done_callback() 方法来添加回调函数来避免直接调用result()方法阻塞线程
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定义一个函数 准备用作线程池fn参数函数
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += i
return my_sum
with ThreadPoolExecutor(max_workers=2) as pool:
future1 = pool.submit(action,50)
future2 = pool.submit(action,100)
def get_result(future):
print(future.result())
future1.add_done_callback(get_result)
future2.add_done_callback(get_result)
print('--------------------')
'''
由于程序并未直接调用 future1、future2 的 result() 方法,因此主线程不会被阻塞,
可以立即看到输出主线程打印出的横线。
接下来将会看到两个新线程并发执行,当线程任务执行完成后,get_result() 函数被触发,输出线程任务的返回值。
'''
####################
#示例 线程池类方法 map(func, *iterables, timeout=None, chunksize=1)
#该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。
#这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定义一个函数 准备用作线程池fn参数函数
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += i
return my_sum
with ThreadPoolExecutor(max_workers=4) as pool:
results = pool.map(action,(50,100,150)) #启动3个线程
print('---------------')
for r in results: #for循环迭代每个线程及结果
print(r)
# =============================================================================
# #线程局部变量
# #线程局部变量为每一个使用该变量的线程都提供一个变量的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
# #从线程的角度看,就好像每一个线程都完全拥有该变量一样。
# =============================================================================
#示例 threading.local()来赋值声明线程局部变量
#作用好像每个线程都完成拥有该变量亦一样
from concurrent.futures import ThreadPoolExecutor
import threading
mydata = threading.local() #定义线程局部变量
def action(max):
for i in range(max):
try:
mydata.x += i
except:
mydata.x = i
print('%s mydata.x 的值为: %d' %(threading.current_thread().name, mydata.x))
with ThreadPoolExecutor(max_workers=2) as pool:
pool.submit(action,10)
pool.submit(action,10)
'''
程序中作为线程执行体的 action 函数使用 mydata.x 记录 0~10 的累加值,
如果两个线程共享同一个 mydata 变量,将会看到 mydata.x 最后会累加到 90(0~9 的累加值是 45,但两次累加会得到 90)。
但由于 mydata 是 threading.local 变量,因此程序会为每个线程都创建一个该变量的副本,所以将会看到两个线程的 mydata.x 最后都累加到 45。
'''
# =============================================================================
# #线程定时器
# #线程定时器是线程类threading类的Timer子类,该子类可用于控制指定函数在特定时间内执行一次
# =============================================================================
'''
注意:
#Timer定时器只能控制函数在指定时间内执行一次,
#如果要使用 Timer 控制函数多次重复执行,则需要函数内嵌套定时器 来循环执行下一次调度。
'''
from threading import Timer
help(Timer)
Timer.__doc__
dir(Timer)
###################
#示例 定时器Timer 控制 10s 后执行 hello 函数。
from threading import Timer
def hello():
print('hello,world')
#创建一个定时器线程
t=Timer(10,hello) #定时10秒以后执行hello函数
t.start() #定时器线程启动
###################
#示例 循环调用定时器
#Timer定时器只能控制函数在指定时间内执行一次,
#如果要使用 Timer 控制函数多次重复执行,则需要函数内嵌套定时器 来循环执行下一次调度。
from threading import Timer
import time
count=0
def print_time():
'''
定义函数,用于Timer()定时器中参数函数
函数中嵌套Timer()定时器,可循环判断,如果count<10,则循环调用10次定时器输出当前时间
'''
print('当前时间:%s' % time.ctime())
global t,count
count+=1
if count<10:
t=Timer(1, print_time)
t.start()
t = Timer(1,print_time)
t.start()
# =============================================================================
# #sched模块 任务调度模块
# #sched模块 可以比 Timer线程定时器 执行更复杂的任务调度
# ##sched模块提供了 sched.scheduler 类,该类代表一个任务调度器。
# =============================================================================
'''
使用:
1、实例化任务调度器 sched.scheduler()
2、实例化任务调度器方法:绝对时间执行函数任务、相对时间执行函数任务、取消任务等
'''
import sched
help(sched)
sched.__doc__
sched.__all__ #只有一个方法 scheduler() 定义线程调度器
dir(sched)
#sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep) 构造器支持两个参数:
#1、timefunc: 该参数指定生成时间戳的时间函数,默认使用 time.monotonic 来生成时间戳。
#2、delayfunc: 该参数指定阻塞程序的函数,默认使用 time.sleep 函数来阻塞程序。
help(sched.scheduler)
dir(sched.scheduler)
help(sched.scheduler.enterabs) #绝对时间执行函数任务
help(sched.scheduler.enter) #相对时间执行函数任务
help(sched.scheduler.run) #运行所有需要调度的任务
help(sched.scheduler.queue) #返回调度器的调度队列,根据任务优先级
#sched.scheduler 调度器支持如下常用属性和方法:
#1、scheduler.enterabs(time, priority, action, argument=(), kwargs={}):
#指定在 time 时间点执行 action 函数,argument 和 kwargs 都用于向 action 函数传入参数,其中 argument 使用位置参数的形式传入参数,kwargs 使用关键字参数的形式传入参数。
#该方法返回一个 event,它可作为 cancel() 方法的参数用于取消该调度。
#priority 参数指定该任务的优先级,当在同一个时间点有多个任务需要执行时,优先级高(值越小代表优先级越高)的任务会优先执行。
#2、scheduler.enter(delay, priority, action, argument=(),kwargs={}):
#该方法与上一个方法基本相同,只是 delay 参数用于指定多少秒之后执行 action 任务。
#3、scheduler.cancel(event): 取消任务。如果传入的 event 参数不是当前调度队列中的 event,程序将会引发 ValueError 异常。
#4、scheduler.empty(): 判断当前该调度器的调度队列是否为空。
#5、scheduler.run(blocking=True): 运行所有需要调度的任务。
#如果调用该方法的 blocking 参数为 True,该方法将会阻塞线程,直到所有被调度的任务都执行完成。
#6、scheduler.queue: 该只读属性返回该调度器的调度队列。
###################
#示例 sched.scheduler执行任务调度
import sched
import time
import threading
def print_time(name='default'):
print('%s的时间:%s' % (name,time.ctime()))
print('主线程开始时间:',time.ctime())
#实例化创建 线程调度器
s=sched.scheduler()
#enter()方法 相对时间 任务调度
s.enter(10,1,print_time) #参数1指定10秒后开始运行,参数2位运行优先等级为1,参数3为调度运行函数
s.enter(5,2,print_time, argument=('位置参数',)) #参数1指定10秒后开始运行,参数2位运行优先等级为1,参数3为调度运行函数,参数4为参数3函数的元组参数
s.enter(5,1,print_time, kwargs={'name':'字典参数'}) #参数1指定10秒后开始运行,参数2位运行优先等级为1,参数3为调度运行函数,参数3为参数3函数的字典参数
#run()方法 运行所有需要调度的任务
s.run()
print('主线程结束时间:',time.ctime())
# =============================================================================
# #multiprocessding模块 进程封装
# =============================================================================
'''
注意:
1、multiprocessing.Process类通过实例化对象指定target参数执行函数 在IDLE里不显示函数执行内容。
通过在控制台通过执行文件可显示target参数执行函数的内容。
2、通过 multiprocessing.Process 来创建并启动进程时,程序必须先判断if __name__=='__main__':,否则可能引发异常。
'''
#探索模块
import multiprocessing
help(multiprocessing)
multiprocessing.__doc__
multiprocessing.__all__
dir(multiprocessing)
####################
#multiprocessing.Process类 进程创建
#Python 在 multiprocessing模块下提供了 Process 来创建新进程。
#与 Thread 类似的是,使用 Process 创建新进程也有两种方式:
#1、使用 multiprocessing模块 中的 Process类 来实例化对象创建新进程,并指定函数作为target参数函数
#2、继承 Process类 ,并重写他的 run() 方法来创建进程类,程序创建Process子类的实例作为进程
import multiprocessing
help(multiprocessing.Process)
dir(multiprocessing.Process)
#Process 类也有如下类似的方法和属性:
#run(): 重写该方法可实现进程的执行体。
#start(): 该方法用于启动进程。
#join([timeout]): 该方法类似于线程的 join() 方法,当前进程必须等待被 join 的进程执行完成才能向下执行。
#name: 该属性用于设置或访问进程的名字。
#is_alive(): 判断进程是否还活着。
#daemon: 该属性用于判断或设置进程的后台状态。
#pid: 返回进程的 ID。
#authkey: 返回进程的授权 key。
#terminate(): 中断该进程。
help(multiprocessing.Process.start)
help(multiprocessing.Process.run)
help(multiprocessing.Process.daemon)
help(multiprocessing.Process.join)
#示例 通过实例化创建进程 指定target参数执行函数
#注意:
#1、multiprocessing.Process类通过实例化对象指定target参数执行函数 在IDLE里不显示函数执行内容。
#通过在控制台通过执行文件可显示target参数执行函数的内容。
import multiprocessing
import os
def action(max):
for i in range(max):
print('(%s)子进程 (父进程:(%s)):%d' %(os.getpid(), os.getppid(), i))
if __name__ == '__main__':
for i in range(100):
print('(%s)主进程:%d' %(os.getpid(), i))
if i == 20:
np1=multiprocessing.Process(target=action, args=(100,))
np1.start()
np2=multiprocessing.Process(target=action, args=(100,))
np2.start()
np2.join()
print('主进程执行完毕')
#示例 继承Process类来创建子进程
#注意:
#1、multiprocessing.Process类通过实例化对象指定target参数执行函数 在IDLE里不显示函数执行内容。
#通过在控制台通过执行文件可显示target参数执行函数的内容。
import multiprocessing
import os
class MyProcess(multiprocessing.Process):
def __init__(self,max):
self.max=max
super().__init__()
#重写run方法作为进程执行体
def run(self):
for i in range(self.max):
print('(%s)子进程(父进程:(%s)):%d' %(os.getpid(), os.getppid(), i))
if __name__ == '__main__':
for i in range(100):
print('(%s)主进程:%d' %(os.getpid(),i))
if i == 20:
mp1=MyProcess(100)
mp1.start()
mp2=MyProcess(100)
mp2.start()
mp2.join()
print('主进程执行完毕!')
# =============================================================================
# #进程启动的三种方式 spawn(windows仅支持此方式)、fork、forkserver
# #spawn: 父进程启动一个全新的python解释器(windows平台仅支持此方式启动进程)
# #fork: 父进程使用 os.fork() 来启动一个 Python 解释器进程。
# #forkserver: 如果使用这种方式来启动进程,程序将会启动一个服务器进程。
# =============================================================================
'''
注意:
1、multiprocessing.set_start_method(' ')函数用于设置启动进程的方式,设置代码必须放在多进程代码之前。
2、window只支持spawn方式启动进程
'''
#示例
import multiprocessing
import os
#help(multiprocessing.set_start_method)
def foo(q):
'''参数q为一个队列,可在队列内存取元素'''
print('被启动的新进程: (%s)' % os.getpid())
q.put('Python')
if __name__ == '__main__':
#设置使用fork方式启动进程
multiprocessing.set_start_method('spawn')
#创建一个进程的队列
q = multiprocessing.Queue()
#创建并启动子进程
mp = multiprocessing.Process(target=foo, args=(q, ))
mp.start()
#获取队列中的消息
print(q.get())
mp.join()
# =============================================================================
# #进程池 multiprocessing.Pool类
# #通过 multiprocessing 模块的 Pool类来实例化创建进程池,然后通过进程池方法提交进程池、关闭进程池、等待进程池等
# =============================================================================
import multiprocessing
help(multiprocessing.Pool)
multiprocessing.Pool.__doc__
dir(multiprocessing.Pool)
#进程池具有如下常用方法:
#1、apply(func[, args[, kwds]]): 将 func 函数提交给进程池处理。其中 args 代表传给 func 的位置参数,kwds 代表传给 func 的关键字参数。
#该方法会被阻塞直到 func 函数执行完成。
#2、apply_async(func[, args[, kwds[, callback[, error_callback]]]]):
#这是 apply() 方法的异步版本,该方法不会被阻塞。其中 callback 指定 func 函数完成后的回调函数,error_callback 指定 func 函数出错后的回调函数。
#3、map(func, iterable[, chunksize]): 类似于 Python 的 map() 全局函数,只不过此处使用新进程对 iterable 的每一个元素执行 func 函数。
#4、map_async(func, iterable[, chunksize[, callback[, error_callback]]]):
#这是 map() 方法的异步版本,该方法不会被阻塞。其中 callback 指定 func 函数完成后的回调函数,error_callback 指定 func 函数出错后的回调函数。
#5、imap(func, iterable[, chunksize]): 这是 map() 方法的延迟版本。
#6、imap_unordered(func, iterable[, chunksize]):
#功能类似于 imap() 方法,但该方法不能保证所生成的结果(包含多个元素)与原 iterable 中的元素顺序一致。
#7、starmap(func, iterable[,chunksize]): 功能类似于 map() 方法,但该方法要求 iterable 的元素也是 iterable 对象,程序会将每一个元素解包之后作为 func 函数的参数。
#8、close(): 关闭进程池。在调用该方法之后,该进程池不能再接收新任务,它会把当前进程池中的所有任务执行完成后再关闭自己。
#9、terminate(): 立即中止进程池。
#10、join(): 等待所有进程完成。
#示例 实例化创建进程池
#使用 apply_async() 方法启动进程:
import multiprocessing
import time
import os
#定义一个函数,准备用作进程池func参数函数
def action(name='default'):
print('(%s)进程正在执行,参数为:%s'%(os.getpid(), name))
time.sleep(3)
#通过 multiprocessing.Process 来创建并启动进程时,程序必须先判断if __name__=='__main__':,否则可能引发异常。
if __name__ == '__main__':
#实例化创建进程池
pool=multiprocessing.Pool(processes=4)
#异步提交进程池
pool.apply_async(action)
pool.apply_async(action,args=('位置参数',))
pool.apply_async(action,kwds={'name':'字典参数'})
#关闭进程池,不再接受新任务,会等待进程池中所有任务完成后关闭
pool.close()
#等待所有进程完成
pool.join()
#示例 实例化创建进程池 通过上下文管理协议创建
#使用map()方法来启动进程
import multiprocessing
import time
import os
#定义一个函数,准备用作进程池func参数函数
def action(max):
my_sum=0
for i in range(max):
print('(%s)进程正在执行:%d'%(os.getpid(), i))
my_sum+=i
return my_sum
#通过 multiprocessing.Process 来创建并启动进程时,程序必须先判断if __name__=='__main__':,否则可能引发异常。
if __name__ == '__main__':
#上下文管理器创建进程池 进程数量为4
with multiprocessing.Pool(processes=4) as pool:
#提交并启动三个进程
results=pool.map(action,(50,100,150))
print('-----------------')
#for循环迭代每个进程及结果
for r in results:
print(r)
# =============================================================================
# #进程通信 multiprocessing.Queue类 和 multiprocessing.Pipe类
#
# #Python 为进程通信提供了两种机制:
# #Queue:一个进程向 Queue 中放入数据,另一个进程从 Queue 中读取数据。
# #Pipe:Pipe 代表连接两个进程的管道。程序在调用 Pipe() 函数时会产生两个连接端,分别交给通信的两个进程,接下来进程既可从该连接端读取数据,也可向该连接端写入数据。
# =============================================================================
'''
注意:
进程通信中,使用实例化创建multiprocessing.Pipe类管道对象返回两个PipeConnection对象,
其中前一个PipeConnection对象用于接受数据,后一个PipeConnection对象用于发送数据
'''
import multiprocessing
help(multiprocessing.Queue)
help(multiprocessing.Pipe)
##########
#Queue:一个进程向 Queue 中放入数据,另一个进程从 Queue 中读取数据。
dir(multiprocessing.Queue)
#multiprocessing 模块下的 Queue 和 queue 模块下的 Queue 基本类似,
#它们都提供了 qsize()、empty()、full()、put()、put_nowait()、get()、get_nowait() 等方法。
#区别只是 multiprocessing 模块下的 Queue 为进程提供服务,而 queue 模块下的 Queue 为线程提供服务。
#示例 使用multiprocessing.Queue类来实现进程之间的通信
import multiprocessing
#
def f(q):
'''
创建一个函数,用作进程target参数的执行函数
参数q为一个实例化创建的multiprocessing.Queue类对象
'''
print('(%s)进程开始放入数据...'%multiprocessing.current_process().pid)
#向队列中放入元素
q.put('血皇敖天')
if __name__ == "__main__":
#实例化创建进程通信的队列
q=multiprocessing.Queue()
#实例化创建并启动子进程运行函数
p=multiprocessing.Process(target=f, args=(q,))
p.start()
print('(%s)进程开始取出数据...'%multiprocessing.current_process().pid)
#从进程通信的队列中取出元素
print(q.get())
#插入实现子进程
p.join()
##########
#Pipe:Pipe 代表连接两个进程的管道。程序在调用 Pipe() 函数时会产生两个连接端,分别交给通信的两个进程,接下来进程既可从该连接端读取数据,也可向该连接端写入数据。
dir(multiprocessing.Pipe)
#使用 Pipe 实现进程通信,程序会调用 multiprocessing.Pipe() 函数来创建一个管道,
#该函数会返回两个 PipeConnection 对象,代表管道的两个连接端(一个管道有两个连接端,分别用于连接通信的两个进程)。
#PipeConnection 对象包含如下常用方法:
#1、send(obj): 发送一个 obj 给管道的另一端,另一端使用 recv() 方法接收。需要说明的是,该 obj 必须是可 picklable 的(Python 的序列化机制),如果该对象序列化之后超过 32MB,则很可能会引发 ValueError 异常。
#2、recv(): 接收另一端通过 send() 方法发送过来的数据。
#3、fileno(): 关于连接所使用的文件描述器。
#4、close(): 关闭连接。
#5、poll([timeout]): 返回连接中是否还有数据可以读取。
#6、send_bytes(buffer[, offset[, size]]):
#发送字节数据。如果没有指定 offset、size 参数,则默认发送 buffer 字节串的全部数据;如果指定了 offset 和 size 参数,则只发送 buffer 字节串中从 offset 开始、长度为 size 的字节数据。通过该方法发送的数据,应该使用 recv_bytes() 或 recv_bytes_into 方法接收。
#7、recv_bytes([maxlength]):
#接收通过 send_bytes() 方法发迭的数据,maxlength 指定最多接收的字节数。该方法返回接收到的字节数据。
#8、recv_bytes_into(buffer[, offset]):
#功能与 recv_bytes() 方法类似,只是该方法将接收到的数据放在 buffer 中。
#示例 使用multiprocessing.Pipe类来实现进程之间的通信
import multiprocessing
def f(conn):
'''
创建一个函数,用作进程target参数的执行函数
参数conn为实例化创建的multiprocessing.Pipe类管道对象返回的两个PipeConnection对象中的后一个用于发送数据的对象
'''
print('(%s)进程开始发送数据...'%multiprocessing.current_process().pid)
conn.send('血皇敖天')
if __name__ =='__main__':
#实例化创建进程通信的pipe管道
#返回两个PipeConnection对象,前一个用于接收数据,后一个用于发送数据
parent_conn, child_conn = multiprocessing.Pipe()
#实例化创建并启动子进程 来执行函数 从而发送管道数据
p=multiprocessing.Process(target=f, args=(child_conn,))
p.start()
print('(%s)进程开始接收数据...'%multiprocessing.current_process().pid)
#前一个生成返回的实例化对象用于接受数据
print(parent_conn.recv())
p.join()
# =============================================================================
# #并发及异步并发爬虫
# #concurrent.futures.ThreadPoolExecutor 线程池类、
# #concurrent.futures.ProcessPoolExecutor 进程池类
# #asyncio模块 异步并发模块
# #aiohttp模块 异步的requests
# =============================================================================
'''
Py并发编程Futures并发爬虫.py
Py并发编程asyncio模块异步并发爬虫.py
深入:
asyncio模块 异步并发模块
aiohttp模块 异步的requests
'''
#示例 单线程简易爬虫
import requests
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
for site in sites:
download_one(site)
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
#示例 多线程/多进程 简易爬虫
import concurrent.futures
import requests
import threading
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
# '''多线程版本'''
# with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# executor.map(download_one, sites)
'''多进程版本'''
# ProcessPoolExecutor() 表示类实例化创建进程池,使用多个进程并行的执行程序。
#通常省略参数 workers,因为系统会自动返回 CPU 的数量作为可以调用的进程数。
with concurrent.futures.ProcessPoolExecutor() as executor:
executor.map(download_one, sites)
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
#示例 多线程迭代器 简易爬虫
import concurrent.futures
import requests
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
to_do = []
for site in sites:
future = executor.submit(download_one, site)
to_do.append(future)
#as_completed(fs) 针对给定的 future 迭代器 fs,在其完成后返回完成后的迭代器。
for future in concurrent.futures.as_completed(to_do):
future.result()
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
'''
异步并发爬虫 需后期深入:
asyncio模块 异步并发模块
aiohttp模块 异步的requests
'''
# =============================================================================
# #GIL 全局解释器锁
# #GIL 限制了 Python 多线程的性能,其本质上类似操作系统的 Mutex。
# #GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
# =============================================================================
'''
#注意:
GIL 不能绝对保证线程安全
因为即便 GIL 仅允许一个 Python 线程执行,但Python 还有 check interval 这样的抢占机制。
'''
#示例 单线程 测速
import time
start = time.time()
def CountDown(n):
while n > 0:
n -= 1
CountDown(100000)
print("Time used:",(time.time() - start))
#示例 多线程 是否会加速
#结果显示并 比单线程计算 没有 提速;缘由是CPython 解释器存在Gil全局解释器锁,
#GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
import time
from threading import Thread
start = time.time()
def CountDown(n):
while n > 0:
n -= 1
t1 = Thread(target=CountDown, args=[100000 // 2])
t2 = Thread(target=CountDown, args=[100000 // 2])
t1.start()
t2.start()
t1.join()
t2.join()
print("Time used:",(time.time() - start))
#CPython 使用引用计数来管理内容 即: 垃圾回收机制
#所有 Python 脚本中创建的实例,都会配备一个引用计数,来记录有多少个指针来指向它。
#当实例的引用计数的值为 0 时,会自动释放其所占的内存。
#示例
import sys
a=[]
b=a
print(sys.getrefcount(a)) #输出引用计数值,此处为3
#注意:GIL 不能绝对保证线程安全
#因为即便 GIL 仅允许一个 Python 线程执行,但Python 还有 check interval 这样的抢占机制。
#示例:
import threading
n = 0
def foo():
global n
n += 1
threads = []
for i in range(100):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(n)
'''
执行此代码会发现,其大部分时候会打印 100,但有时也会打印 99 或者 98,
原因在于 n+=1 这一句代码让线程并不安全。
'''
# =============================================================================
# #垃圾回收机制 gc模块 手动启动垃圾回收机制
# #垃圾回收机制引用计数机制
# #即:对象的引用计数值为 0 时,说明这个对象永不再用,自然它就变成了垃圾,需要被回收。
# =============================================================================
import psutil #获取系统信息模块
import os
help(psutil)
psutil.__doc__
psutil.__all__
dir(psutil)
help(psutil.Process)
psutil.Process.__doc__
dir(psutil.Process)
help(psutil.Process.memory_full_info)
psutil.Process.memory_full_info.__doc__
####################
#示例 通过函数内部局部变量是否垃圾回收 来查看内存占用空间
import os
import psutil #获取系统信息模块
#显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print('{} memory used: {} MB'.format(hint, memory))
def func():
show_memory_info('initial') #输出较小内存占用空间
#global a #如果将局部变量声明成全局变量,则持续占用较大内存空间
a = [i for i in range(10000000)] #临时变量占用较大内存空间。 函数运行完毕后即垃圾回收机制清除临时变量
show_memory_info('after a created') #输出垃圾回收机制后的较小内存占用空间
#return a #如果函数返回局部变量,则也会持续占用较大内存空间
#输出占用内存空间
func()
show_memory_info('finished')
####################
#示例 python内部的引用计数机制
import sys
a = []
#sys.getrefcount() 函数用于查看一个变量的引用次数
print(sys.getrefcount(a)) # 两次引用,一次来自a,一次来自 getrefcount
def func(a):
print(sys.getrefcount(a)) # 四次引用,a,python的函数调用栈,函数参数,和 getrefcount
func(a)
print(sys.getrefcount(a)) # 两次引用,一次来自a,一次来自getrefcount。函数func调用已经不存在被垃圾回收了
#####################
#示例 手动启动垃圾回收
import gc #gc(garbage collector)模块的引用计数技术来进行垃圾回收
import psutil #获取系统信息模块
#显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print('{} memory used: {} MB'.format(hint, memory))
def func():
show_memory_info('initial') #输出较小内存占用空间
#global a #如果将局部变量声明成全局变量,则持续占用较大内存空间
a = [i for i in range(10000000)] #临时变量占用较大内存空间。 函数运行完毕后即垃圾回收机制清除临时变量
show_memory_info('after a created') #输出垃圾回收机制后的较小内存占用空间
#return a #如果函数返回局部变量,则也会持续占用较大内存空间
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')
del a
gc.collect() #gc(garbage collector)模块的引用计数技术来进行垃圾回收
show_memory_info('finish')
print(a)
####################
#示例 手动启动垃圾回收 循环调用嵌套示例
#Python使用标记清除(mark-sweep)算法和分代收集(generational),来启用针对循环引用的自动垃圾回收。
import gc
#循环引用嵌套,导致其a,b的引用计数不为0,从而垃圾会机制不成立,需要手动显示启动垃圾回收机制。
def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info('after a, b created')
a.append(b)
b.append(a)
func()
gc.collect() #这里如果不进行手动启动垃圾回收,则内存暂用空间依然高企
show_memory_info('finished')
Py并发编程及应用(多线程、多进程;异步、全局锁等)
最新推荐文章于 2024-06-26 16:43:52 发布