并发编程(concurrent programming)
- 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行
- 改善用户体验 - 让耗时间的操作不会造成程序的假死
Python中实现并发编程的三种手段:
- 多线程 - 多个线程可以共享操作系统分配给进程的内存 - 线程通信相对简单
- 多进程 - 进程之间的内存是不共享的 - 管道 / 套接字 / 中间件
- 异步I/O - 非阻塞式的I/O操作,通过回调函数对操作结果进行处理
- Redis / Node.js - 多路I/O复用(I/O操作是多路并发)
面试题:进程和线程的区别和联系?
进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程
线程 - 操作系统分配CPU的基本单位
分别使用多线程和多进程生成图片缩率图
import threading
import multiprocessing
from PIL import Image
import glob, os
PREFIX = 'thumbnails'
def generate_thumbnail(infile, size, format='JPEG'):
file, ext = os.path.splitext(infile)
file = file[file.rfind('/') - 1:]
outfile = f'./{PREFIX}/{file}_{size[0]}_{size[1]}{ext}'
im = Image.open(infile)
im.thumbnail(size, Image.ANTIALIAS)
im.save(outfile, format)
def main():
if not os.path.exists(PREFIX):
os.mkdir(PREFIX)
for infile in glob.glob('images/*.jpg'):
for size in (32, 64, 128):
# 多线程(IO密集型任务)
# threading.Thread(
# target=generate_thumbnail,
# args=(infile, (size, size))
# ).start()
# 多进程(计算密集型任务)
multiprocessing.Process(
target=generate_thumbnail,
args=(infile, (size, size))
).start()
if __name__ == '__main__':
main()
解决多线程引起的资源竞争问题
多线程程序如果没有竞争资源的场景那么通常会比较简单
临界资源 - 被多个线程竞争的资源
当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱
import threading
import time
from concurrent.futures import ThreadPoolExecutor
class Account(object):
def __init__(self):
self.balance = 0.0
self.lock = threading.Lock()
def deposit(self, money):
# 添加线程锁进行保护
# self.lock.acquire()
# try:
# new_balance = self.balance + money
# time.sleep(0.01)
# self.balance = new_balance
# finally:
# self.lock.release()
# 上下文语法优化
with self.lock:
new_balance = self.balance + money
time.sleep(0.01)
self.balance = new_balance
def add_money(account, money):
account.deposit(money)
class AddMoneyThread(threading.Thread):
def __init__(self, account, money):
# 自定义线程的初始化方法中必须调用父类的初始化方法
super().__init__()
self.account = account
self.money = money
# run方法是线程启动之后要执行的回调方法(钩子函数)
# 所以启动线程不能够直接调用run方法而是通过start方法启动线程
# 什么时候需要使用回调式编程?
# 你知道要做什么但不知道什么时候会做这件事情
def run(self):
self.account.deposit(self.money)
def main():
account = Account()
# 创建线程池
pool = ThreadPoolExecutor(max_workers=10)
futures = []
for _ in range(100):
# 创建线程的第1种方式
# threading.Thread(
# target=add_money, args=(account, 1)
# ).start()
# 创建线程的第2种方式
# AddMoneyThread(account, 1).start()
# 创建线程的第3种方式
# 调用线程池中的线程来执行特定的任务
future = pool.submit(add_money, account, 1)
futures.append(future)
# 关闭线程池
pool.shutdown()
for future in futures:
future.result()
print(account.balance)
if __name__ == '__main__':
main()
守护线程/守护进程
- daemon
mysqld / httpd / firewalld / systemd
如果主线程结束了守护线程也不再保留即使守护线程还在执行(没有结束)
Lock - 多个线程竞争临界资源(资源只有1个) - 获得锁才能操作资源
Condition - 基于Lock对象可以创建它 - wait() / notify_all() -
实现线程调度
Semaphore - 多个线程竞争资源(资源有多个,但线程数量大于资源数量)
多个线程通信比较简单因为可以共享内存
多个进程通信相对比较困难,可以使用multiprocessing.Queue
通过多个进程共享一个队列来实现进程间的通信
生产者消费者模型 / 哲学家进餐模型 - 多线程编程模型
import threading
def show_message(content):
while True:
print(content, end=' ')
def main():
threading.Thread(
target=show_message,
args=('Ping',),
daemon=True
).start()
threading.Thread(
target=show_message,
args=('Pong',),
daemon=True
).start()
if __name__ == '__main__':
main()