文章目录
一、背景介绍
实现支持异步任务的线程池
使用python3
- 很多语言都提供了线程池
- Java:ThreadPoolExecutor
- Python3:ThreadPoolExecutor
本篇的主要内容:
二、python的同步原语
1、互斥量
- 申请:lock=threading.Lock()
- 加锁:lock.acquire()
- 解锁:lock.release()
2、条件变量
- 申请条件变量:condition=threading.Condition()
- 加锁:condition.acquire()
- 解锁:condition.release()
- 等待:condition.wait()
- 唤醒:condition.notify()
- python中不需要将条件变量和互斥量自己结合,threading库直接将互斥量放入条件变量中
三、实现线程安全的队列Queue
1、队列
- 队列用于存放多个元素,是存放各种元素的池
- 存放的元素可以是线程,可以是任务,也可以是别的
2、实现线程安全的队列的功能
- 获取当前队元素数量
- 往队列放入元素
- 从队列取出元素
3、什么会影响线程安全
队列可能有多个线程同时操作,因此需要保证线程安全
比如:
- 场景: 多个线程同时访问队列元素
- 目的: 为了保证多个线程获取的串行
- 方法: 使用“锁”保护队列
- 场景: 队列元素为空时获取队列元素,
- 目的: 此时阻塞线程,等待队列到队列不为空,
- 方法: 使用条件变量等待队列元素不为空
4、编程实现
queue.py
import threading
# 线程安全的队列
import time
class ThreadSafeQueueException(Exception):
pass
class ThreadSafeQueue(object):
# 构造函数
def __init__(self,max_size=0):
# max_size表示队列长度,0表示无限大
self.queue=[]
self.max_size=max_size
self.lock=threading.Lock() # 互斥量
self.condition=threading.Condition() # 条件变量
# 获取当前元素的数量,通过获取queue的长度
# 为了防止在获取过程中队列被调用,在获取之前之后对队列加锁解锁
def size(self):
self.lock.acquire()
size=len(self.queue)
self.lock.release()
return size
# 给队列中放入元素
def put(self,item):
if self.max_size!=0 and self.size()>self.max_size:
return ThreadSafeQueueException()
else:
self.lock.acquire()
self.queue.append(item)
self.lock.release()
self.condition.acquire()
self.condition.notify()
self.condition.release()
pass
def batcjh_put(self,item_list):
if not isinstance(item_list,list):
item_list=list(item_list)
for item in item_list:
self.put(item)
pass
# 从队列中取出元素,block参数用来在队列中无元素时阻塞进程,timeout用来在阻塞时设置阻塞时间
def pop(self,block=False,timeout=0):
# 从队列头部取出
if self.size==0:
if block:
self.condition.acquire()
self.condition.wait(timeout=timeout)
self.condition.release()
else:
return None
self.lock.acquire()
item=None
if len(self.queue)>0:
item=self.queue.pop()
self.lock.release()
return item
def get(self,index):
self.lock.acquire()
item=self.queue[index]
self.lock.release()
return item
if __name__ == '__main__':
queue=ThreadSafeQueue(max_size=100)
def producer():
while True:
queue.put(1)
time.sleep(1)
def consumer():
while True:
item=queue.pop(1)
print('get item from queue:',item)
time.sleep(1)
thread1=threading.Thread(target=producer)
thread2=threading.Thread(target=consumer)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
四、实现基本任务对象Task
1、概念
指放入队列中的任务对象
- 实现基本任务对象需要
- 任务参数
- 任务唯一标记(uuid)
- 任务具体的执行逻辑
2、编程实现
task.py
# uuid是一个标准库
import uuid
# 基本任务对象
class Task:
def __init__(self,func,*args,**kwargs):
# 任务具体逻辑,通过函数引用传递进来
self.callable=func
self.id=uuid.uuid4()
self.args=args
self.kwargs=kwargs
def __str__(self):
return 'Task id:'+str(self.id)
def my_function():
print('this is a task test.')
if __name__ == '__main__':
task=Task(func=my_function())
print(task)
五、线程池简介
1、什么是线程池
- 线程池是存放多个线程的容器
- CPU调度线程执行后不会销毁线程
- 将线程放回线程池重复利用
2、为什么使用线程池
- 线程是稀缺资源,不应该频繁创建和销毁
- 架构解耦,线程创建和业务处理解耦,更加优雅
- 线程池是使用线程的最佳实践
六、实现任务处理线程Process Thread
1、功能、必备属性
- 任务处理线程需要不断的从任务队列里取任务执行
- 任务处理线程需要有一个标记,标记线程什么时候应该停止
- 实现任务处理线程
- 基本属性(任务队列、标记)
- 线程执行的逻辑
- 线程停止(stop)
2、编程实现
pool.py
import threading
from operate_system.task import Task
# 任务处理线程
class ProcessThread(threading.Thread):
def __init__(self,task_queue,*args,**kwargs):
# 继承父类构造方法
threading.Thread.__init__(self,*args,**kwargs)
# 线程停止标记
self.dismiss_flag=threading.Event()
# 任务队列(处理线程不断从队列取出元素处理)
self.task_queue=task_queue
self.args=args
self.kwargs=kwargs
def run(self):
while True:
# 判断线程是否被要求停止
if self.dismiss_flag.is_set():
break
task=self.task_queue.pop()
if not isinstance(task,Task):
continue
# 执行task实际逻辑(是通过函数func(callable)调用引进)
result=task.callable(*task.args,**task.kwargs)
def dismiss(self):
self.dismiss_flag.set()
def stop(self):
self.dismiss()
七、实现任务处理线程池Pool
1、线程池基本功能
- 存放多个任务处理线程
- 负责多个线程的启停
- 管理向线程池提交的任务,下发给线程去执行
2、实现任务处理线程池
- 基本属性
- 提交任务(put,batch_put)
- 线程启停(start,join)
- 线程池大小(size)
3、编程实现
pool.py
class TaskTypeErrorException(Exception):
pass
# 线程池
class ThreadPool:
def __init__(self,size=0):
if not size:
size=psutil.cpu_count()*2
# psutil.cpu_count()返回cpu核数
self.pool=ThreadSafeQueue(size)
# 任务队列
self.task_queue=ThreadSafeQueue()
for i in range(size):
self.pool.put(ProcessThread(self.task_queue))
# 启动线程池
def start(self):
for i in range(self.pool.size()):
thread=self.pool.get(i)
thread.start()
# 停止线程池
def join(self):
for i in range(self.pool.size()):
thread=self.pool.get(i)
thread.stop()
while self.pool.size():
thread=self.pool.pop()
thread.join()
# 给线程池提交任务
def put(self,item):
if not isinstance(item,Task):
raise TaskTypeErrorException()
self.task_queue.put(item)
# 批量提交
def batch_put(self,item_list):
if not isinstance(item_list,list):
item_list=list(item_list)
for item in item_list:
self.put(item)
#
def size(self):
return self.pool.size()
八、编写测试用例
编写测试用例,测试线程池是否有问题
- 初始化一个线程池
- 生成一系列任务
- 给线程池提交任务行
test.py
import time
from operate_system import task,pool
# 定义一个自己的任务
class SimpleTask(task.Task):
def __init__(self,callable):
super(SimpleTask,self).__init__(callable)
def process():
print('This is a SimpleTask callable function.')
time.sleep(1)
def test():
# 1、初始化一个线程池
test_pool=pool.ThreadPool()
test_pool.start()
# 2、生成一系列任务
for i in range(10):
simple_task=SimpleTask(process)
# 3、给线程池提交任务执行
test_pool.put(simple_task)
if __name__ == '__main__':
test()
九、实现异步任务处理AsyncTask
- 不知道任务什么时候执行
- 不知道任务什么时候执行完成
使用条件变量解决
- 给任务添加一个标记,任务完成后,则标记为已完成
- 任务完成时,可直接获取任务运行结果
- 任务未完成时,获取任务结果,会阻塞获取线程
实现AsyncTask
- 设置运行结果(set_result)
- 获取运行结果(get_result)
task.py
# 异步任务对象
class AsyncTask(Task):
def __init__(self,func,*args,**kwargs):
self.result=None
self.condition=threading.Condition()
super().__init__(func,*args,**kwargs)
# 设置运行结果
def set_result(self,result):
self.condition.acquire()
self.result=result
self.condition.notify()
self.condition.release()
# 获取任务结果
def get_result(self):
self.condition.acquire()
if not self.result:
self.condition.wait()
result=self.result
self.condition.release()
return result
pool.py中run()后面添加
result=task.callable(*task.args,**task.kwargs)
if isinstance(task,AsyncTask):
task.set_result(result)
test.py中添加异步任务测试用例
# 异步任务测试用例
def test_async_task():
def async_process():
num=0
for i in range(100):
num += 1
return num
# 1、初始化一个线程池
test_pool = pool.ThreadPool()
test_pool.start()
# 2、生成一系列任务
for i in range(10):
async_task = task.AsyncTask(async_process)
# 3、给线程池提交任务执行
test_pool.put(async_task)
result=async_task.get_result()
print('Get result:%d'%(result))
test.py中条件测试用例2
# 测试是否可以真正的等待(wait)
def test_async_task2():
def async_process():
num=0
for i in range(100):
num += 1
time.sleep(1)
return num
# 1、初始化一个线程池
test_pool = pool.ThreadPool()
test_pool.start()
# 2、生成一系列任务
for i in range(10):
async_task = task.AsyncTask(async_process)
# 3、给线程池提交任务执行
test_pool.put(async_task)
print('get reusult in timestamp:%d'%time.time())
result=async_task.get_result()
print('Get result in timestamp:%d,result:%d'%(time.time(),result))
test.py中添加测试用例3
# 测试没有等待是否可以真正获取结果
def test_async_task3():
def async_process():
num=0
for i in range(100):
num += 1
return num
# 1、初始化一个线程池
test_pool = pool.ThreadPool()
test_pool.start()
# 2、生成一系列任务
for i in range(10):
async_task = task.AsyncTask(async_process)
# 3、给线程池提交任务执行
test_pool.put(async_task)
print('get reusult in timestamp:%d'%time.time())
# 转而去处理其他线程,这里为处理sleep
time.sleep(5)
result=async_task.get_result()
print('Get result in timestamp:%d,result:%d'%(time.time(),result))