启用多线程调用接口

本文讲述了在Java中如何使用线程池、Callable和FutureTask来优化接口调用,解决单线程顺序调用导致的响应时间过长问题。通过创建线程池,使用Callable封装带有返回值的业务方法,再利用FutureTask控制返回顺序,确保接口调用的顺序性,从而提高程序效率。

最近在做一个定时任务接口,接口的流程很简单,就是调用几个接口,然后将这这些接口的返回json字符串解析,存入后台数据库,编码很顺利,很快完成了。

随后负责人很快就发现问题,由于是单线程顺序调用,这些接口调用时间是线性叠加的,(假如前一个接口耗时10秒,在它结束前第二个接口一直处于阻塞中,这前一个接口调用所花费的时间就完全浪费了)我的这个模块响应时间已经超过了一分钟,于是提出了启用线程池的优化方案。

因为时间精力有限,暂时不研究线程池 阻塞队列这些,这里只提供一个简单易懂的解决方案

我很快选定了缓冲池作为线程池,具体创建过程代码如下

ExecutorService executorService = Executors.newCachedThreadPool();

第一次编码时,我用强大的百度查到了线程创建方法,也是我之前背诵的滚瓜烂熟的两种方式

extends Thread 或者 implements Runnable

当时我心想,这下我背的东西可是用得着了,于是我毫不犹豫,当场建了几个Thread类,用run方法包裹了自己的方法,一共5个接口,我建了5个Thread,分别包裹了这5个方法,迅速完成了5个线程,再依次调用线程池的submit方法依次传入这5个线程对象。

Thread thread1 = new Thread(new Runnable() {
  @Override
  public void run() {
    //封装的调用接口的方法,
    getCompareInfo(params);
  }
});

Thread thread1 ...................................

executorService.submit(thread1);
executorService.submit(thread2);
executorService.submit(thread3);
executorService.submit(thread4);
executorService.submit(thread5);

代码很少,很快写完,让我顿时产生多线程不过如此的错觉,也就是创建一个线程然后用线程池去调度的事情,写完submit方法我觉得问题已经解决了,但是,当我想取回线程中调用接口的返回值然后解析的时候我意识到了一个尴尬的问题,Thread中的public void run()方法时没有返回值,这意味着我无法获取接口的返回。于是我思考了下,如果是启用线程去做写DB,或者存东西之类的操作我想用Thread是可行的,但是需要返回结果的时候就不行了

又是通过强大的专业广告搜索引擎,我百度倒了Callable这个类,并找到了应用的示例。

ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService<Map<String,String>> cs = new ExecutorCompletionService<>( executorService);
Callable compareCallable = new Callable() {
  @Override
  public Map<String, String> call() {
    return getCompareInfo(params);
  }
};
cs.submit(compareCallable);
cs.submit(*);
cs.submit(*);
cs.submit(*);
cs.submit(*);
Map<String,String> res1 = cs.take().get();
Map<String,String> res2 = cs.take().get();
Map<String,String> res3 = cs.take().get();
Map<String,String> res4 = cs.take().get();
Map<String,String> res5 = cs.take().get();

我用Callable取代了Thread去执行接口调用方法,这里调度的对象换成了ExecutorCompletionService声明返回泛型是个map,最后按照调用顺序来获取接口返回

但随后的测试中我发现一个问题,我调用接口的顺序是

接口1

接口2

接口3

接口4

接口5

但是返回的顺序是1 2 5 3 4

因为接口响应有快有慢,比较快的接口先调用结束,就先返回了,顺序直接受到接口响应时间的影响,我还是无法拿到返回值,因为我不知道接口返回的顺寻,它是变动的

最后问了下业务熟悉的大佬,找到了FutureTask这个类,保留先前写好的Callable,要用到

//构建futureTask任务,控制返回顺序
FutureTask<Map<String,String>> futureTask1 = new FutureTask<Map<String,String>>(compareCallable);
FutureTask<Map<String,String>> futureTask2 = new FutureTask<Map<String,String>>(getDeptInfoCallable);
FutureTask<Map<String,String>> futureTask3 = new FutureTask<Map<String,String>>(getPaymentDetailInfoCallable);
FutureTask<Map<String,String>> futureTask4 = new FutureTask<Map<String,String>>(getAllowanceDetailCallable);

直接把之前的callable对象作为参数传递到FutureTask的构造方法中,然后换成

ExecutorService executorService = Executors.newCachedThreadPool();

executorService的submit方法,调用

Map<String,String> res = futureTask1.get();

Map<String,String> res = futureTask2.get();

Map<String,String> res = futureTask3.get();

Map<String,String> res = futureTask4.get();

Map<String,String> res = futureTask5.get();

这样就能按照顺序返回接口的响应了,在方法内我们可以用try catch包裹业务代码块处理异常,也可以用线程执行的返回状态判断接口调用是否正常,来设定重试机制,FutureTask的原理是阻塞队列,在任务结束之前会阻塞,所以取到的返回是有顺序的。

若是你的接口中存在多个调用三方接口的操作,或者多个大批量数据处理的工作,且它们之前是没有关联先后顺序的,你也可以像这样创建线程并启用线程池并行执行这些操作,我的接口在高早前响应时间是70秒,改造后10秒左右就可以结束,虽说不是很快,但相比之前已经强多了。

总结就是,使用Callable对象封装具体业务代码,然后依次塞进FutureTask,使用线程池调度,然后依次调用get方法取出返回值即可,这样就可以避免单线程代码中,后续业务因为前面的业务耗时长而造成的block(阻塞)带来的长响应时间。

转载请注明出处

 

<think> 嗯,用户这次的问题是关于Python主函数中启用多线程调用子函数的实现方法。从用户提供的引用内容来看,ta应该是个有一定编程基础的开发者,正在处理多线程任务,尤其是C++调用Python时遇到GIL问题。用户情绪可能有些困惑,因为引用[3]提到C++调用Python时无法开启多进程,而引用[4]又涉及复杂的GIL处理。 用户的核心需求其实很明确:如何在Python主线程中安全地创建并管理子线程来执行函数。不过ta可能没意识到,纯Python环境和C++嵌入Python环境的多线程实现差异很大。引用[1]给的threading.Thread示例其实已经解决了基础需求,但引用[3][4]暗示ta可能在混合编程场景下工作——这需要特别提醒GIL限制。 我决定从简到难分两层回答:先给纯Python的标准方案(引用[1]的优化版),再补充C++调用Python时的特殊处理(引用[3][4]的要点)。特别要强调daemon线程的陷阱,因为很多开发者会忽略主线程退出时子线程被强制终止的问题。 代码示例需要改进原引用[1]的缺陷:原示例没等待线程结束,可能导致资源泄漏。我改成with ThreadPoolExecutor上下文管理,既简洁又能自动join。对于C++部分,PyGILState_Ensure/Release的配对调用必须演示清楚,毕竟引用[4]提到“需要由用户负责释放锁”这点很关键。 用户可能后续会问性能优化或线程通信,所以预设了相关问题。不过ta没提具体应用场景,暂时不展开协程或进程池方案——但如果ta追问IO密集型任务,可以补充asyncio方案。 </think> 在Python主函数中启用多线程调用子函数的实现方法如下(分步说明): ### 一、基础实现方法 ```python import threading def 子函数(参数): print(f"子线程执行: 参数={参数}") if __name__ == '__main__': # 创建线程实例 t1 = threading.Thread(target=子函数, args=(1,)) t2 = threading.Thread(target=子函数, args=(2,)) # 启动线程 t1.start() t2.start() # 等待线程结束 t1.join() t2.join() print("主线程结束") ``` ### 二、关键参数说明 1. **`target`**:指定要调用的子函数 2. **`args`**:传递元组格式的参数(单个参数需加逗号) 3. **`daemon`**: - `daemon=True`:主线程结束时立即终止子线程 - 默认`False`:主线程会等待子线程完成 ### 三、推荐实践(使用线程池) ```python from concurrent.futures import ThreadPoolExecutor def 任务函数(参数): return f"结果-{参数}" if __name__ == '__main__': with ThreadPoolExecutor(max_workers=3) as executor: # 提交多个任务 结果1 = executor.submit(任务函数, 10) 结果2 = executor.submit(任务函数, 20) # 获取结果 print(结果1.result()) print(结果2.result()) ``` ### 四、注意事项 1. **全局解释器锁(GIL)**: - Python线程适用于I/O密集型任务 - CPU密集型任务建议用多进程(`multiprocessing`模块) 2. **线程安全**: ```python # 使用Lock保证资源安全 锁 = threading.Lock() def 安全函数(): with 锁: # 临界区代码 ``` 3. **C++调用Python的特殊处理**: - 需手动管理GIL(如引用[4]所述) - 调用前需执行: ```cpp PyGILState_STATE gstate = PyGILState_Ensure(); // 获取GIL /* 执行Python代码 */ PyGILState_Release(gstate); // 释放GIL [^4] ``` > **GIL工作原理**:Python解释器通过`PyEval_InitThreads()`初始化线程支持后,每个线程需通过`PyGILState_Ensure()`获取锁才能执行Python字节码[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值