Python3实现支持异步任务的线程池

1.Python API

在这边文章中使用python3实现,下面是所需要用到的api
互斥锁
申请:lock = threading.Lock()
加锁:lock.acquire()
解锁:lock.release()

条件变量
条件变量需要和互斥量配合使用,在Python里简化了流程,申请condition的时候,在底层是condition和lock一起工作的。
定义:condition = threading.Condition()
加锁:condition.acquire()
解锁:condition.release()
等待:condition.wait()
唤醒:condition.notify()

2.实现线程安全队列

队列用于存放多个元素,或是多个任务,是存放各种元素的“池”。当队列里面存放元素是线程的时候,这个队列就是一个线程池。这里实际存储线程的数据结构是列表

如果需要实现线程安全队列,队列需要实现哪些功能?

  1. 需要获取当前队列元素数量
  2. 往队列里面放入元素
  3. 从队列里取出元素

需要注意的是,队列很有可能被多个线程同时操作,那么就需要保证线程的安全。

  • 多个线程同时访问队列元素,要保证多个线程获取的串行。这时就需要借助互斥锁来保护队列。
  • 如果队列为空时,有线程获取队列元素。此时应该阻塞,并且需要借助条件变量等待队列不为空。
# -*- encoding=utf-8 -*-

import time
import threading

class ThreadSafeQueueException(Exception):
    pass

# 线程安全的队列
class ThreadSafeQueue(object):

    def __init__(self, max_size=0):
        self.queue = []  # 实际存储结构是列表
        self.max_size = max_size
        self.lock = threading.Lock()  # 互斥量
        self.condition = threading.Condition()  # 条件变量

    # 当前队列元素的数量
    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()
        self.lock.acquire()
        self.queue.append(item)
        self.lock.release()
        self.condition.acquire()
        self.condition.notify()
        self.condition.release()

    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 pop(self, block=True, timeout=None):
        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(3)

    def consumer():
        while True:
            item = queue.pop(block=True, timeout=-1)
            print('get item from queue: %d' % item)
            time.sleep(1)

    thread1 = threading.Thread(target=producer)
    thread2 = threading.Thread(target=consumer)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

3.实现基本任务对象

保存用户的任务处理逻辑

  1. 定义任务的基本参数
  2. 为任务生成唯一标记(uuid)
  3. 任务具体执行逻辑
# -*- encoding=utf-8 -*-


import uuid
import threading


# 基本任务对象
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)

返回唯一id

4.线程池相关概念

什么是线程池?

  • 线程池可以理解为存放多个线程的容器。比如鱼塘,鱼就是线程。
  • 当CPU需要调度的时候,就把线程从线程池里面取出来调度
  • 执行之后并不会销毁线程,而是放回线程池重复利用

为什么使用线程池?

  • 线程和进程一样,都属于稀缺资源。不应该被被频繁创建和销毁。线程也有上下文,创建和销毁时都需要额外的资源控制。
  • 进行架构解耦,线程创建和业务处理解耦,更加优雅。不应该在业务处理时需要使用线程的时候才创建线程,而是提前使用线程池创建了所有要使用的线程。
  • 线程池是使用线程的最佳实践。

【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

5.实现任务处理线程ProcessThread

任务处理线程具备的元素、属性、功能

  1. 任务处理线程需要不断的从任务列表里取任务执行
  2. 需要有一个标记,标记进程什么时候应该停止。如果用户从外部去关闭线程的话,需要通过这个标记通知线程关闭。

实现任务处理线程

  • 定义基本的属性,包括任务队列,标记等
  • 线程的处理逻辑(run)
  • 线程停止的方法(stop)通过标记实现停止
import threading
from ThreadPool.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实际逻辑(是通过函数调用引进来的)
            result = task.callable(*task.args, **task.kwargs)
           

    def dismiss(self):
        self.dismiss_flag.set()

    def stop(self):
        self.dismiss()

6.实现任务处理线程池

线程池Pool的基本功能

  • 存放一个或多个任务处理线程
  • 负责多个线程的启动和停止
  • 管理向线程池的提交的任务,并把他们下发给线程执行

实现线程池的基本过程

  1. 定义基本属性
  2. 提交任务(put,batch_put)
  3. 线程启动与停止(start,join)
  4. 线程池大小(size)
# 线程池
class ThreadPool:

    def __init__(self, size=0):
        if not size:
            # 约定线程池的大小为CPU核数的两倍(最佳实践)
            size = psutil.cpu_count() * 2
        # 线程池
        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()  # 不为空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()

7.编写测试用例测试线程池

# -*- encoding=utf-8 -*-

import time

from ThreadPool import task, pool


# 定义自己的任务
class SimpleTask(task.Task):
    def __init__(self, callable):
        super(SimpleTask, self).__init__(callable)

# 处理逻辑
def process():
    time.sleep(1)
    print('This is a SimpleTask callable function 1.')
    time.sleep(1)
    print('This is a SimpleTask callable function 2.')


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)

8.实现异步任务处理对象

使用条件变量完成
从异步里面取出结果,并知道任务什么时候执行完成。给任务添加一个标记,任务完成后,标记结束。任务完成时,直接获取任务的结果。任务未完成时,获取任务结果,会阻塞获取线程。

实现AsyncTask

  1. 设置运行结果(set_result)
  2. 获取运行结果(get_result)

异步任务继承基本对象

# 异步任务对象
class AsyncTask(Task):

    def __init__(self, func, *args, **kwargs):
    	# 保存任务对象结果默认为None
        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

修改任务处理线程run函数

def run(self):
    while True:
        # 判断线程是否被要求停止
        if self.dismiss_flag.is_set():
            break
        task = self.task_queue.pop()
        # 不是定义的任务对象
        if not isinstance(task, Task):
            continue
        # 执行task实际逻辑(是通过函数调用引进来的)
        result = task.callable(*task.args, **task.kwargs)
        # 判断当前执行任务是否为AsyncTask
        if isinstance(task, AsyncTask):
            task.set_result(result)

完善测试函数

# -*- encoding=utf-8 -*-

import time

from ThreadPool import task, pool


# 定义自己的任务
class SimpleTask(task.Task):
    def __init__(self, callable):
        super(SimpleTask, self).__init__(callable)

# 处理逻辑
def process():
    time.sleep(1)
    print('This is a SimpleTask callable function 1.')
    time.sleep(1)
    print('This is a SimpleTask callable function 2.')


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)


def test_async_task():
    def async_process():
        num = 0
        for i in range(100):
            num += i
        return num

    # 1. 初始化一个线程池
    test_pool = pool.ThreadPool()
    test_pool.start()
    # 2. 生成一系列的任务
    for i in range(10):
        async_task = task.AsyncTask(func=async_process)
        test_pool.put(async_task)
        result = async_task.get_result()
        print('Get result: %d' % result)


# 测试是否可以正在的等待(wait)
def test_async_task2():
    def async_process():
        num = 0
        for i in range(100):
            num += i
        time.sleep(5)
        return num

    # 1. 初始化一个线程池
    test_pool = pool.ThreadPool()
    test_pool.start()
    # 2. 生成一系列的任务
    for i in range(1):
        async_task = task.AsyncTask(func=async_process)
        test_pool.put(async_task)
        print('get result in timestamp: %d' % time.time())
        result = async_task.get_result()
        print('Get result in timestamp: %d: %d' % (time.time(), result))


# 测试没有等待是否也可以正常获取结果
def test_async_task3():
    def async_process():
        num = 0
        for i in range(100):
            num += i
        return num

    # 1. 初始化一个线程池
    test_pool = pool.ThreadPool()
    test_pool.start()
    # 2. 生成一系列的任务
    for i in range(1):
        async_task = task.AsyncTask(func=async_process)
        test_pool.put(async_task)
        print('get result in timestamp: %d' % time.time())
        # time.sleep(5)
        # 转而去处理别的逻辑
        result = async_task.get_result()
        print('Get result in timestamp: %d: %d' % (time.time(), result))


if __name__ == '__main__':
    test()
    # test_async_task()
    # test_async_task2()
    # # test_async_task3()

线程池源码

源码打包在GitHub,地址是HouTu/ThreadPool

猜你喜欢

转载自blog.csdn.net/wankcn/article/details/108430742