python并发编程
目录
python并发编程
术语介绍
1. 进程、线程、协程
进程:
计算机中资源分配和调度的最小工作单元(一个运行程序)
一个进程可以存在多个线程 进程间数据不共享
进程状态-创建、就绪、运行、阻塞、退出
进程间调度通过上下文(CPU寄存器中的值、进程状态、堆栈中的数据)切换
应用于CPU密集型计算的业务(大量复杂运算等)
操作系统内核控制分配
线程:
计算机中的程序执行中的最小执行单元
同个进程中多个线程共享数据
线程间调度切换快于进程
应用于IO密集型的业务(文件读写、网络IO)
协程:
微线程 协程调度由程序业务层控制切换
没有线程上下文切换的资源开销
应用于IO密集型的业务或CPU密集型计算(进程+协程模式)
2. 并发、并行、串行
并发:
依次执行
某个时间段存在多个任务执行
某个时刻有且仅有一个任务在执行
在同一个CPU上执行
并行:
同时执行
在多个CPU上执行(多核CPU、单核会保持进程切换调度于多核运行模式一致)
串行:
依次顺序执行
需等待上个任务执行完毕再执行下个任务
3. 同步、异步、互斥
同步:
串行 依次有顺序执行 需要等待上个任务完成才执行下个任务
异步:
并发、并行 多个任务同时在一个时间段同时执行 无需等待其他任务完成 各自执行对应的任务
互斥:
保证某个时间段有且仅有一个任务在处理(共享)资源
4. 进程p、线程t 同步和通信相关支持
Lock(RLock): p t
Event: p t
Condition: p t
Semaphore: p t
Queue: p t
Pipe: p
进(线)程
线程创建
'''
python并发编程
'''
import os
import random
import time
from threading import Thread
def task(task_name):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
def thread_create_v1():
"""
线程创建方式1
from threading import Thread
:return:
"""
for i in range(2):
Thread(target=task, args=(f"t{i}",)).start()
class MyThread(Thread):
def __init__(self, task_name):
super().__init__()
self.name = task_name
def run(self) -> None:
"""
任务处理业务流程
:return:
"""
task(task_name=self.name)
def thread_create_v2():
"""
创建线程方式2
继承Thread并实现run方法
:return:
"""
for i in range(2):
MyThread(f"mt{i}").start()
if __name__ == '__main__':
thread_create_v2()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

进程创建
import os
import random
import time
from multiprocessing import Process
def task(task_name):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
def process_create_v1():
"""
进程创建方式1
from multiprocessing import Process
:return:
"""
'''
Process.__init__(
group=None,
target=None,
name=None,
args=(),
kwargs={},
*,
daemon=None)
'''
p1 = Process(target=task, args=("a",))
p2 = Process(target=task, args=("b",))
p1.start() # 任务执行开始
# print(f"进程id={p1.pid};进程名={p1.name}-进程是否在运行={p1.is_alive()}")
# p1.terminate() # 需要放到start后 则会直接终止任务执行
p2.start()
p1.join() # 等待任务执行完毕(可设置超时时间timeout)
p2.join()
class MyProcess(Process):
def __init__(self, task_name):
super().__init__()
self.name = task_name
def run(self) -> None:
"""
任务处理业务流程
:return:
"""
task(task_name=self.name)
def process_create_v2():
"""
进程创建方式2
from multiprocessing import Process
继承Process父类并重写run方法
:return:
"""
p1 = MyProcess("a")
p2 = MyProcess("b")
p1.start()
p2.start()
p1.join()
p2.join()
def process_daemon_true():
"""
守护进程
主进程结束会终止未等待执行的守护进程
:return:
"""
# p = Process(target=task, args=("c", ), daemon=True)
p1 = Process(target=task, args=("c", ))
p2 = Process(target=task, args=("d", ))
p1.daemon = True # 设置为守护进程
p1.start()
p2.start()
# p2.join() // 若不等待则主进程结束 守护进程会直接被终止执行
if __name__ == '__main__':
process_create_v1()
print("="*30)
process_create_v2()
print("=" * 30)
process_daemon_true()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

进(线)程通信
互斥锁Lock
import os
import time
from multiprocessing import Process, Lock
def task_lock(task_name, lock=None):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
if not lock:
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
else:
with lock:
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
def process_lock():
"""
Lock: 加锁 整体任务业务加锁<=>Process().join() 对一个进程(线程)可一次加锁
RLock: 递归锁 对一个进程(线程)可多次加锁 同一进程(线程)可多次获取锁 获取多少次就需要释放多少次
:return:
"""
lock = Lock()
# 加锁
for i in range(2):
Process(target=task_lock, args=(i, lock)).start()
# 未加锁
for i in range(3, 5):
Process(target=task_lock, args=(f">>>{i}", )).start()
if __name__ == '__main__':
process_lock()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

事件Event
import os
import random
import time
from multiprocessing import Process, Event
def event_task(task_name, evt):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
evt.wait() # 阻塞进程执行 直到调用evt.set() <=> Event的标识为True(evt.is_set()=True)则会唤醒所有等待该Event对象的进程
print(f"{task_name}-event_task-evt.is_set()=", evt.is_set())
# evt.clear() # event事件重置为False 会导致使用该Event事件的进程一直阻塞
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
def process_event():
"""
Event对象事件(最好单次使用 若一个进程需要多次使用建议用Condition对象替代)
from multiprocessing import Event
多进程间通信: 通过Event事件控制进程间执行流程、监听进程状态
Event.wait(): 阻塞所有等待Event对象的进程 直到调用Event.set()且Event.is_set()为True时 会被唤醒
Event.set(): 设置Event.is_set()为True 唤醒所有等待Event对象阻塞的进程
Event.is_set(): 事件标识 True:唤醒执行 False:阻塞等待
Event.clear(): 重置Event对象事件为False
:return:
"""
evt = Event()
p1 = Process(target=event_task, args=("e", evt))
p2 = Process(target=event_task, args=("f", evt))
p1.start()
p2.start()
print("process_event-evt.is_set()=", evt.is_set())
evt.set() # 唤醒所有等待Event对象的阻塞进程
if __name__ == '__main__':
process_event()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

条件变量Condition
'''
python并发编程
'''
import os
import random
import time
from multiprocessing import Process, Condition
def condition_task(task_name, cond, flag):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-flag({flag})-任务执行...")
count = 0
with cond:
while 1:
if flag >= 1: # 第一层条件控制
while 1:
if count < 3:
time.sleep(2)
count += 1
else:
break
print(f"flag={flag};count={count}")
if flag >= 1 and count < 3: # 第二层条件控制
cond.wait() # 阻塞当前进程(线程)
else:
cond.notify() # 唤醒当前等待进程(线程)
# cond.notify_all() # 唤醒所有等待进程(线程)
break
else:
break
print(f"{task_name}-{os.getpid()}-{os.getppid()}-flag({flag})-任务结束...")
def process_condition():
"""
condition条件变量(synchronize.Condition)
进程间通信: 通过condition条件变量控制进程间执行流程、监听进程状态
from multiprocessing import Condition
Condition.acquire():获取锁
Condition.release():释放锁
Condition.wait(timeout=None): 释放锁并阻塞当前进程(调用该方法前先获取锁否则会抛出RuntimeError)直到超时或直到其它进程相同Condition对象调用了notify|notify_all方法被唤醒 之后当前进程会重新尝试获取锁并在成功获取锁返回True 若超时则返回False
Condition.wait_for(predicate, timeout=None): 阻塞当前进程直到超时返回False或者直到条件变量Condition(predicate)返回True
Condition.notify(n=1): 唤醒等待该Condition条件变量的一个或n个进程(调用该方法需要优先获取锁且被唤醒的进程不会马上从wait()返回唤醒 直到重新获取到锁 notify不会释放锁 需要进程本身调用wait或release释放)
Condition.notify_all(): 唤醒等待该Condition对象的所有线程
:return:
"""
cond = Condition()
for i in range(3):
Process(target=condition_task, args=(f"c-{i}", cond, i)).start()
if __name__ == '__main__':
process_condition()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

信号量Semaphore
import os
import random
import time
from multiprocessing import Process, Semaphore
def task_semaphore(task_name, sema):
sema.acquire()
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{sema.get_value()}-任务执行...")
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{sema.get_value()}-任务结束...")
sema.release()
# with sema:
# print(f"{task_name}-{os.getpid()}-{os.getppid()}-{sema.get_value()}-任务执行...")
# time.sleep(random.randrange(1, 3))
# print(f"{task_name}-{os.getpid()}-{os.getppid()}-{sema.get_value()}-任务结束...")
def process_semaphore(n=1):
"""
Semaphore信号量:
Semaphore(1)<=>互斥锁<=>串行
Semaphore(n:int)可同时允许n个数量的进程(线程)执行
Semaphore.acquire(): Semaphore信号量大于0则Semaphore信号量-1 反之异常 加锁阻塞
Semaphore.release(): Semaphore信号量+1 释放锁唤醒
:return:
"""
sema = Semaphore(n) # n决定同时执行的进程(线程)数 同时也决定达到该信号量值后续任务均需要等待
for i in range(n+1):
Process(target=task_semaphore, args=(f"s{i}", sema)).start()
if __name__ == '__main__':
process_semaphore(n=2)
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

队列Queue
import os
import random
import time
def task_queue(task_name, q, flag):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{q.qsize()}-任务执行...")
if flag == 'in': # 2种工作模式
for i in range(3):
if not q.full():
q.put(f"data{i}",) # 生产(写)数据
print(f"{task_name}-写入数据({q.qsize()}):data{i}")
elif flag == 'out':
while 1:
if not q.empty():
data = q.get() # 消费(读)数据
print(f"{task_name}-读取数据({q.qsize()}):{data}")
else:
break
else:
return
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{q.qsize()}-任务结束...")
def process_queue():
"""
from multiprocessing import Queue
JoinableQueue、SimpleQueue(<=>locked pipe)
put: 发送消息
get: 接收消息
qsize: 队列大小
empty: 队列是否为空
full: 队列是否已满
:return:
"""
from multiprocessing import Process, Queue
q = Queue() # maxsize 可设置队列最大值
Process(target=task_queue, args=("in", q, "in")).start()
for i in range(2):
Process(target=task_queue, args=(f"out{i}", q, "out")).start()
def task_tqueue(task_name, q, flag):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{q.qsize()}-任务执行...")
if flag == 'in': # 2种工作模式
for i in range(3):
if not q.full():
q.put(f"data{i}",) # 生产(写)数据
print(f"{task_name}-写入数据({q.qsize()}):data{i}")
elif flag == 'out':
while 1:
if not q.empty():
data = q.get() # 消费(读)数据
q.task_done()
print(f"{task_name}-读取数据({q.qsize()}):{data}")
else:
break
else:
return
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{q.qsize()}-任务结束...")
def thread_queue():
"""
from queue import Queue(只支持线程 进程会有问题)
Queue,LifoQueue,PriorityQueue,SimpleQueue(fifo)
Queue.task_done(): 标识任务为完成 线程任务每次调用get建议加上该方法
Queue.join(): 阻塞所有任务 直到所有未完成的任务标识降为0
Queue.qsize(): 队列大小
Queue.empty(): 队列是否为空
Queue.full(): 队列是否已满
Queue.put(): 写数据 可设置block是否阻塞写 timeout超时时间
Queue.get(): 读数据 可设置block是否阻塞读 timeout超时时间
:return:
"""
from queue import Queue as Q
from threading import Thread
q = Q() # maxsize 可设置队列最大值
Thread(target=task_tqueue, args=("in", q, "in")).start()
for i in range(2):
Thread(target=task_tqueue, args=(f"out{i}", q, "out")).start()
if __name__ == '__main__':
# process_queue()
thread_queue()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

管道Pipe
import os
import random
import time
from multiprocessing import Process, Pipe
def task_pipe(task_name, p, flag):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{flag}-任务执行...")
if flag == "in": # 可写
for i in range(5):
p.send(i)
print(f"{task_name}-发送消息:{i}")
p.close()
elif flag == "out": # 可读
while 1:
try:
data = p.recv()
print(f"{task_name}-接收消息:{data}")
except EOFError:
p.close()
break
else:
p.close()
return
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{flag}-任务结束...")
def process_pipe():
"""
Pipe管道:
多进程间的数据交互
from multiprocessing import Pipe
Pipe.send(): 发送数据
Pipe.send_bytes(buf,offset=0,size=None): 从特定偏移量offset发送特定字节大小size的数据
Pipe.recv(): 接收数据
Pipe.recv_bytes(maxlength=None): 接收特定长度字节的数据
Pipe.poll(): 轮询是否有可读的连接通道
Pipe.close(): 关闭连接通道
:return:
"""
p = Pipe(duplex=False) # 返回元组(con1, con2) 参数duplex=True 可读可写-默认True
# c1, c2 = p
# print(p, type(p))
# """
# duplex=False
# out:
# c1: True False 只可读
# c2: False True 只可写
# """
# print("c1:", c1.readable, c1.writable)
# print("c2:", c2.readable, c2.writable)
rp, wp = p
Process(target=task_pipe, args=("wp", wp, "in")).start()
Process(target=task_pipe, args=("rp", rp, "out")).start()
if __name__ == '__main__':
process_pipe()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

进(线)程池
import os
import random
import time
from multiprocessing import Process
def task_process_pool(task_name, n):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
return n * 2
def process_pool():
"""
进程池Pool
from multiprocessing import Pool
Pool.apply(): 同步阻塞直到获取结果 任务函数入参元组 结果无序
Pool.apply_async(): 异步非阻塞 可携带参数正常回到函数和异常回到函数 正常返回则回调正常回调函数 反之回调异常回调函数 结果无序
Pool.map(): 同步阻塞直到获取结果 任务函数入参可迭代对象 结果有序
Pool.map_async():异步非阻塞 可携带参数正常回到函数和异常回到函数 结果有序
Pool.starmap_async(): 异步非阻塞 结果有序 针对任务函数传递多个参数 使用该方法
Pool.close(): 关闭连接池 会等到任务执行完成
Pool.terminate(): 停止连接池中的任务 无论是否已完成任务
Pool.join(): 等待连接池中的任务完成
线程池Pool
from multiprocessing.pool import ThreadPool as tp
或者
from multiprocessing.dummy import Pool as tp
以下是源码且ThreadPool继承Pool 相关方法一样
def Pool(processes=None, initializer=None, initargs=()):
from ..pool import ThreadPool
return ThreadPool(processes, initializer, initargs)
:return:
"""
from multiprocessing import Pool
p = Pool()
for i in range(3):
p.apply_async(task_process_pool,
args=(f"p{i}", i),
callback=lambda x: print(f"{x}"),
error_callback=lambda x: print(f"error={x}"))
# res = p.apply_async(task_process_pool,
# args=(f"p{i}", i),
# callback=lambda x: print(f"{x}"),
# error_callback=lambda x: print(f"error={x}"))
# print(res.get(), res.ready(), res.successful())
# res = p.apply(task_process_pool,
# args=(f"p{i}", i))
# print(res) # 结果
pass
# p.starmap_async(task_process_pool,
# ((f"p{i}", i) for i in range(3)),
# callback=lambda x: print(f"{x}"),
# error_callback=lambda x: print(f"error={x}")
# )
"""
out: [0,2,4] 结果集
"""
p.close()
p.join()
pass
if __name__ == '__main__':
process_pool()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

并发池
import os
import random
import time
def task_pool(task_name, n):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
time.sleep(random.randrange(1, 3))
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
return n * 2
def my_pool_executor():
"""
并发池ThreadPoolExecutor| ProcessPoolExecutor(方法类似)
submit():返回一个Future对象 有序结果
map(): 返回一个迭代器 有序结果
shutdown(): wait=True 等待返回结果Future返回结束
:return:
"""
from concurrent.futures import ThreadPoolExecutor
# 方式1
with ThreadPoolExecutor(max_workers=3) as te:
res_li = te.map(lambda x: task_pool(*x), ((f"p{i}", i) for i in range(3)))
# print(type(res_li), res_li) # 迭代器对象
for rs in res_li:
print("return_res=", rs)
# 方式2
# with ThreadPoolExecutor(max_workers=3) as te:
# for i in range(3):
# res = te.submit(lambda x: task_pool(*x), (f"p{i}", i))
# # res = te.submit(lambda x: task_pool(*x), (f"p{i}", i)).add_done_callback(lambda futureX: futureX)
# # from concurrent.futures import Future
# # print(type(res), res) # Future对象
# print("return_res=", res.result()) # 可设置超时时间
if __name__ == '__main__':
my_pool_executor()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

协程
send+yield
def task_yield():
n = 1
while n <= 3:
d = yield n # 暂停并获取值
if not d:
return
print("recv<<<", d)
n += 1
def task_send(g):
g.send(None) # <=>g.__next__() 唤醒或启动生成器g
n = 1
while n <= 3:
try:
print("send>>>", n)
r = g.send(n) # 发送值给yield表达式
print("send(nextVal)>>>", r)
except StopIteration:
return
n += 1
def send_and_yield():
"""
send + yield 协程
模拟任务切换执行
:return:
"""
g = task_yield()
task_send(g)
if __name__ == '__main__':
send_and_yield()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

@asyncio.coroutine+yield from
@asyncio.coroutine
def task_asyncio(task_name):
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务执行...")
yield from asyncio.sleep(1) # 切换到其他任务执行
print(f"{task_name}-{os.getpid()}-{os.getppid()}-任务结束...")
def asyncio_loop():
"""
@asyncio.coroutine + yield from 模拟协程任务交替执行
@asyncio.coroutine装饰的函数称为协程
yield from 用于将一个生成器部分操作指派给另一个生成器
eg: a()是生成器(asyncio.Future()对象)
def t():
yield from a()
:return:
"""
print("是否是协程对象=", asyncio.iscoroutine(task_asyncio("a")))
print("是否是协程函数=", asyncio.iscoroutinefunction(task_asyncio))
lp = asyncio.get_event_loop() # 创建一个事件循环
lp.run_until_complete(asyncio.wait([task_asyncio("in"), task_asyncio("out")])) # 执行协程任务直到完成
lp.close() # 关闭事件循环
if __name__ == '__main__':
asyncio_loop()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

async+await
import asyncio
async def async_task_1(af):
"""
协程任务1
:return:
"""
print("开始写作业...")
await asyncio.sleep(1)
print(f"写作业中...")
await asyncio.sleep(1)
print(f"写作业中...")
af.set_result(True) # 设置值
print("结束写作业...")
# 获取结果时用到
return "作业写完了"
async def async_task_2(af):
"""
协程任务2
:return:
"""
print(f"未到时间睡觉...")
# n = await af
await asyncio.sleep(1)
print(f"想睡觉...")
# n = await af
await asyncio.sleep(1)
print(f"想睡觉...")
# n = await af # 获取值
await asyncio.sleep(1)
print(f"到点睡觉...")
return "睡觉了"
async def run(af):
f1, f2 = async_task_1(af), async_task_2(af)
# await asyncio.gather(f1, f2) # 方式1
task_li = [f1, f2]
rsl = await asyncio.gather(*task_li) # 方式2
# print("rsl=", rsl) # rsl= ['作业写完了', '睡觉了']
# done, no_done = await asyncio.wait(task_li) # 方式3
# for r in done:
# print("res=", r.result())
def async_await():
"""
@asyncio.coroutine <=>async
yield from <=> await
async 修饰的函数为异步函数 :
async def a():
pass
异步函数的实例化对象a()即为一个asyncio.Future() 协程对象
python3.7:
asyncio.ensure_future(a())
<=>
loop = asyncio.get_event_loop()
loop.create_task(a())
<=>
asyncio.create_task(a())
res = asyncio.gather(): 返回结果有序
a, b = asyncio.wait([a(), b()]): 返回结果无序
:return:
"""
af = asyncio.Future() # 新建一个Future()对象
# 方式1
# lp = asyncio.get_event_loop()
# # lp.run_until_complete(run(af))
# lp.run_until_complete(asyncio.wait([async_task_1(af), async_task_2(af)]))
# lp.close()
# 方式2
# asyncio.run(asyncio.wait([async_task_1(af), async_task_2(af)]))
asyncio.run(run(af))
if __name__ == '__main__':
async_await()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

gevent
pip install gevent
def task_gevent(task_name, n):
"""
gevent任务
:return:
"""
import gevent,random,os
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{gevent.getcurrent()}-任务执行...")
gevent.sleep(random.randrange(1, 3)) # 模拟耗时
print(f"{task_name}-{os.getpid()}-{os.getppid()}-{gevent.getcurrent()}-任务结束...")
return n * 2
def gevent_run():
"""
pip install gevent
greenlet封装
gevent.joinall(greenlets,timeout): 等待greenlets完成
gevent.spawn(): 创建greenlet协程并与运行(g.start() return g)
:return:
"""
import gevent
from gevent import monkey
monkey.patch_all() # 自动定位耗时的业务并切换协程任务处理 阻塞->非阻塞
# # 方式1-串行
# for i in range(3):
# g = gevent.spawn(lambda x: task_gevent(*x), (f"g{i}", i))
# g.join()
# print("returnRes=", g.value)
# 方式2-并发
gl = [gevent.spawn(lambda x: task_gevent(*x), (f"g{i}", i)) for i in range(3)]
gevent.joinall(gl)
# 获取return返回结果
for s in gl:
print("returnRes=", s.value)
if __name__ == '__main__':
gevent_run()
print(f"{os.getpid()}-{os.getppid()}-程序结束...")

参考文档
https://python3-cookbook.readthedocs.io/zh_CN/latest/

浙公网安备 33010602011771号