python 线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

线程池的使用
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。

如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。

Exectuor 提供了如下常用方法:

1、submit(fn, *args, **kwargs):将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。
2、map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
3、shutdown(wait=True):关闭线程池。
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。

Future 提供了如下方法:
1、cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
2、cancelled():返回 Future 代表的线程任务是否被成功取消。
3、running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。
4、done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
5、result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。
6、exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
7、add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。

使用线程池来执行线程任务的步骤如下:
1、调用 ThreadPoolExecutor 类的构造器创建一个线程池。
2、定义一个普通函数作为线程任务。
3、调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
4、当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。

def test(value1, value2=None):
    print("%s threading is printed %s, %s"%(threading.current_thread().name, value1, value2))
    time.sleep(2)
    return 'finished'

def test_result(future):
    print(future.result())

if __name__ == "__main__":
    import numpy as np
    from concurrent.futures import ThreadPoolExecutor
    threadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")
    for i in range(0,10):
        future = threadPool.submit(test, i,i+1)

    threadPool.shutdown(wait=True)

获取执行结果
前面程序调用了 Future 的 result() 方法来获取线程任务的运回值,但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。

如果程序不希望直接调用 result() 方法阻塞线程,则可通过 Future 的 add_done_callback() 方法来添加回调函数,该回调函数形如 fn(future)。当线程任务完成后,程序会自动触发该回调函数,并将对应的 Future 对象作为参数传给该回调函数。

直接调用result函数结果

def test(value1, value2=None):
    print("%s threading is printed %s, %s"%(threading.current_thread().name, value1, value2))
    time.sleep(2)
    return 'finished'

def test_result(future):
    print(future.result())

if __name__ == "__main__":
    import numpy as np
    from concurrent.futures import ThreadPoolExecutor
    threadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")
    for i in range(0,10):
        future = threadPool.submit(test, i,i+1)
#         future.add_done_callback(test_result)  # 调用future.add_done_callback函数
        print(future.result())

    threadPool.shutdown(wait=True)
    print('main finished')

另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池,如上面的程序所示。

此外,Exectuor 还提供了一个 map(func, *iterables, timeout=None, chunksize=1) 方法,该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。

例如,如下程序使用 Executor 的 map() 方法来启动线程,并收集线程任务的返回值:

示例换成多参数的:

def test(value1, value2=None):
    print("%s threading is printed %s, %s"%(threading.current_thread().name, value1, value2))
#     time.sleep(2)


if __name__ == "__main__":
    import numpy as np
    from concurrent.futures import ThreadPoolExecutor
    threadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")
    for i in range(0,10):
#         test(str(i), str(i+1))
        threadPool.map(test, [i],[i+1]) # 这是运行一次test的参数,众所周知map可以让test执行多次,即一个[]代表一个参数,一个参数赋予不同的值即增加[]的长度如从[1]到[1,2,3]
    threadPool.shutdown(wait=True)

上面程序使用 map() 方法来启动 4个线程(该程序的线程池包含 4 个线程,如果继续使用只包含两个线程的线程池,此时将有一个任务处于等待状态,必须等其中一个任务完成,线程空闲出来才会获得执行的机会),map() 方法的返回值将会收集每个线程任务的返回结果。
通过上面程序可以看出,使用 map() 方法来启动线程,并收集线程的执行结果,不仅具有代码简单的优点,而且虽然程序会以并发方式来执行 test() 函数,但最后收集的 test() 函数的执行结果,依然与传入参数的结果保持一致。

### Python 线程池使用方法 #### 创建线程池 为了创建一个线程池,在Python中可以利用`concurrent.futures`模块中的`ThreadPoolExecutor`类。构造此实例时,可以通过传递`max_workers`参数指定线程池中最大允许同时运行的线程数量[^4]。 ```python from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=5) ``` #### 提交任务到线程池 一旦有了线程池对象,就可以向其中提交任务了。这可通过调用`submit()`方法完成,它接受目标函数以及任意数量的位置或关键字参数作为输入,并返回一个表示这个操作结果的`Future`对象[^5]。 ```python def task(n): return n * n future = executor.submit(task, 10) result = future.result() # 获取任务的结果 print(result) # 输出: 100 ``` #### 使用 `map` 方法批量处理数据 对于需要对一系列项应用相同的操作的情况,可采用`map()`方法。其行为与内置的`map()`相似,但是会在后台自动分配工作给不同的线程去执行。 ```python numbers = [1, 2, 3, 4, 5] results = list(executor.map(task, numbers)) print(results) # 输出: [1, 4, 9, 16, 25] ``` #### 关闭线程池 当不再需要继续提交新任务时,应该调用`shutdown()`来释放资源,默认情况下会等待所有已提交的任务完成后才真正关闭线程池。 ```python executor.shutdown() ``` ### 完整示例代码 下面给出一段完整的例子,演示如何定义一个多线程应用程序来计算列表里各个数目的平方: ```python from concurrent.futures import ThreadPoolExecutor, as_completed def square(x): """ 计算x的平方 """ return x*x if __name__ == '__main__': nums = range(8) with ThreadPoolExecutor(max_workers=4) as exe: futures = {exe.submit(square, num): num for num in nums} for fut in as_completed(futures): try: res = fut.result() print(f'Square of {futures[fut]} is {res}') except Exception as exc: print(f'Task generated an exception: {exc}') ``` 上述代码展示了怎样在一个上下文中管理线程池生命周期,确保即使发生错误也能正常清理资源[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值