操作系统--实例5(实现支持异步任务的线程池)

一、背景介绍

实现支持异步任务的线程池
使用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()

八、编写测试用例

编写测试用例,测试线程池是否有问题

  1. 初始化一个线程池
  2. 生成一系列任务
  3. 给线程池提交任务行

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))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值