并发
Python实现并发有3种方式:多进程、多线程、多进程+多线程
重点:多线程和多进程的比较。
以下情况需要使用多线程:
- 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
- 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。
以下情况需要使用多进程:
- 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
- 程序的输入可以并行的分成块,并且可以将运算结果合并。
- 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
重点:异步I/O与多进程的比较。
当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,
asyncio
就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑asyncio
,它很适合编写没有实时数据处理需求的Web应用服务器。
多进程
创建进程的时候,子进程复制父进程及其所有的数据结构 每个子进程都有独自的外部变量 不会共享修改后的结果等
from multiprocessing import Process
# 普通的函数
def 函数名(参数1, 参数2):
# 代码
p1 = Process(target=函数名, args=(参数1, 参数2))
p1.start() # 开始
p2 = Process(target=函数名, args=(参数1, 参数2))
p2.start() # 开始
p1.join() # 等等结束 结束后向下执行
p2.join() # 等等结束 结束后向下执行
多线程
线程可以共享进程的内存空间 多个线程可以共享全局变量 多个线程共享同一个变量(我们通常称之为“资源”)如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源” 对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。
Python的多线程并不能发挥CPU的多核特性
线程函数
from threading import Thread
# 普通的函数
def 函数名(参数1, 参数2):
# 代码
t1 = Thread(target=函数名, args=(参数1, 参数2))
t1.start() # 开始
t2 = Thread(target=函数名, args=(参数1, 参数2))
t2.start() # 开始
t1.join() # 等等结束 结束后向下执行
t2.join() # 等等结束 结束后向下执行
线程类
from threading import Thread
class 类名(Thread):
def __init__(self, 参数1, 参数2):
super().__init__()
# 开始时执行
def run(self):
# 代码
t1 = 类名(参数1, 参数2)
t1.start() # 开始
t2 = 类名(参数1, 参数2)
t2.start() # 开始
t1.join() # 等等结束 结束后向下执行
t2.join() # 等等结束 结束后向下执行
锁
在资源类上加锁
“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源” 而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁” 其他线程才有机会获得“锁”
代码
from threading import Thread, Lock
from time import sleep
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
new_balance = self._balance + money
sleep(0.01)
self._balance = new_balance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
for _ in range(100):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
for t in threads:
t.join()
print('账户余额为: ¥%d元' % account.balance)
if __name__ == '__main__':
main()
多线程+队列
import queue
import threading
# 创建一个队列对象,把数组值放进去
q = queue.Queue(maxsize=4000)
for i in range(100):
q.put(i)
# 定义实际操作
def do_something(i):
print(i)
# 从队列中取出值,并调用实际操作
def f(queue):
while not queue.empty():
i = queue.get()
do_something(i)
# 起10个线程,线程target去执行从队列中取值并进行操作的动作
threads = []
for t in range(10):
thread = threading.Thread(target=f, args=(q,))
threads.append(thread)
thread.start()
for t in threads:
t.join()
协程
在Python语言中,单线程+异步I/O的编程模型称为协程
import asyncio
async def say_after():
await asyncio.sleep(2) # 等待执行完成后向下执行
# 代码
async def main():
task1 = asyncio.create_task(say_after()) # 执行 并不等待 向下执行
task2 = asyncio.create_task(say_after()) # 执行 并不等待 向下执行
await task1 # 等待执行完成后向下执行
await task2 # 等待执行完成后向下执行
asyncio.run(main()) # 执行