ai对话---多线程并发处理问题

简介: ai对话---多线程并发处理问题

ai对话—多线程并发处理问题


先简单回顾一下旧版本的对话接口如何实现


其实这里更多是涉及到多线程工作上的学习问题


在初代版本中 我自己以为的搞了一个线程池就能完成多线程的任务了

Java
public ThreadPoolExecutor pool=new ThreadPoolExecutor(13,13,8,
TimeUnit.MINUTES,new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

@RequestMapping("/get")
public Result get(@RequestParam(“question”) String question,@RequestParam(“id”) int id) throws Exception {

Future f1 = pool.submit(new callable(question,id));
String answer =f1.get();
return Result.ok(answer);
}

工作的代码就不放了 这里是重点 重点是我的接口中 每一次任务的执行都是new一个新的线程出来 去执行任务 但并没有主动的写关闭线程的语句 这就导致了 线程很容易堆满 每次执行完应该释放一个线程 而且这里并没有加多对异常的处理 如果对端那边的ai卡住了 就没有办法得知发生了什么事情


于是这里就有了下面的重写了的语句

Java
public ThreadPoolExecutor pool = new ThreadPoolExecutor(13, 13, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
@RequestMapping("/get")
public DeferredResult get(@RequestParam(“question”) String question, @RequestParam(“id”) String id) {

//创建了一个DeferredResult对象,并将其返回给前端。在异步任务执行完毕后,
// 通过调用deferredResult.setResult(result)方法将结果设置到DeferredResult对象中,从而实现异步返回结果给前端。
DeferredResult deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> {
try {
callable callable = new callable(question, id);
String answer = callable.call();
Result result = Result.ok(answer);
System.out.println(“接口:”+answer);
return result;
} catch (Exception e) {
Result result = Result.fail(e.getMessage());
return result;
}
}, pool).whenComplete((result, throwable) -> {
if (throwable != null) {
result = Result.fail(throwable.getMessage());
deferredResult.setResult(result);
} else {
deferredResult.setResult(result);
}
});
return deferredResult;
}
public class callable implements Callable{
private String id;
private String question;
public callable(String question,String id) {
//这里处理一下userId的长度 因为讯飞那边限制了
if (id.length() >= 30) {
id= id.substring(0, 30);
}
this.question = question;
this.id=id;
}
@Override
public String call() throws Exception {
String answer =main(question,id);
//System.out.println(answer);
answer = JSONUtil.toJsonStr(answer);
botText.content="";//清空
return answer;
}
}


再来讲这个和ai之间的对话的接口原理


实际上在每个main函数当中会构建一个WebSocket的服务区跟他进行对话 而当 每一个对话结束 实际上是没有把话说完的 是要进行n次回复 ai说的话才能被拼接好 这个过程就跟 ai一次性说完有比较大的区别 在于他的WebSocket每次都要新建这样的一个对象出来 来和对端的ai进行对话 并且要“等”ai说完


所以这里就遇到了几个问题:


  1. 主线程没办法精确的知道副线程当中 进行到什么地步了 容易没把话说完就回复给客户端了
  2. 如果进行了线程复用的话 很可能会串不同用户之间的对话历史记录
  3. 超时等待的时候 没有跳出 会直接让一个线程死在里面 如果并发线程量够大 足够造成死锁


下面就是解决的办法

Java
public String main(String newQuestion,String userid) throws Exception {
if(totalFlag){
totalFlag=false;
NewQuestion=newQuestion;
// 构建鉴权url
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
OkHttpClient client = new OkHttpClient.Builder().build();
String url = authUrl.toString().replace(“http://”, “ws://”).replace(“https://”, “wss://”);
Request request = new Request.Builder().url(url).build();
totalAnswer="";
//这里创建了大模型的新对象 实际上那些发送请求获取答案的操作都是在这个线程中做的
BigModelNew bigModelNew = null;
if (getHistory(userid)!=null){
bigModelNew=new BigModelNew(userid, false,getHistory(userid),stringRedisTemplate);
}
else {
bigModelNew=new BigModelNew(userid, false,historyList,stringRedisTemplate);
}
// 等待 WebSocket 的 run() 方法执行完毕
int maxWaitTime = 10000; // 最大等待时间,单位:毫秒
int currentWaitTime = 0; // 当前已等待的时间,单位:毫秒
int waitInterval = 1000;// 每次等待的时间间隔,单位:毫秒
WebSocket webSocket = client.newWebSocket(request, bigModelNew);
System.out.println(maxWaitTime);
while (currentWaitTime < maxWaitTime) {
if (bigModelNew.getBotContent().equals("")) {
// run() 方法还未执行完毕,可以进行一些其他操作或等待一段时间
Thread.sleep(waitInterval);
System.out.println(“正在执行线程”+Thread.currentThread().getName()+"…等待时间还剩:"+(maxWaitTime-currentWaitTime));
currentWaitTime += waitInterval;
} else {
return bigModelNew.getBotContent();
}
}
}
totalFlag=true;
return “网络开了点小差 试试重新发送你的消息吧”;
}


代码解释:


这是Spring提供的一种支持异步处理结果的类。在接口处理过程中,它会先返回一个空的DeferredResult对象给前端,然后在异步任务执行完毕后,通过调用deferredResult.setResult(result)方法将最终的结果设置到DeferredResult对象中,实现异步返回结果给前端。


在异步任务的实现中,使用CompletableFuture.supplyAsync()方法创建一个异步任务,并在其中执行具体的业务逻辑。这里使用了一个callable对象来处理问题和ID,并返回一个回答。


上方的代码解决了1和3 我们打印出来他的执行时间以及线程的名字 以便我们能够追踪到他


而超过了一定的时长 线程就会自动跳出 并且返回报错信息让用户重新发送


而线程2当中我们发现需要缓存历史记录 并且要对用户进行区分 所以在构造大模型对象的时候 我写了一个特殊的构造参数 (这里一定要记得 把Redis给注入进去 否则会爆空指针的错 大概原理是因为新的对象里面并没有被注入Redis 他作为一个新的Bean没有与这个Bean产生绑定的关系 这里涉及到Spring容器构成Bean的原理)

Java
// 构造函数
public BigModelNew(@org.springframework.beans.factory.annotation.Value("u s e r I d " ) S t r i n g u s e r I d < b r > ‘ ‘ , @ V a l u e ( " {userId}") String userId<br>` `,@Value("userId")StringuserId<br>‘‘,@Value("{wsCloseFlag}") Boolean wsCloseFlag
,@Value("H i s t o r y L i s t " ) L i s t < R o l e C o n t e n t > H i s t o r y L i s t < b r > ‘ ‘ , @ V a l u e ( " {HistoryList}")List<RoleContent> HistoryList<br>` `,@Value("HistoryList")List<RoleContent>HistoryList<br>‘‘,@Value("{stringRedisTemplate}") StringRedisTemplate stringRedisTemplate) {
this.userId = userId;
this.wsCloseFlag = wsCloseFlag;
this.historyList=HistoryList;
this.stringRedisTemplate = stringRedisTemplate;
}

而后 我们使用userId来进行区分 在每一个大模型对象当中 的静态变量中的userId给写死了,并且在初始化的时候 还要根据userId进行查询历史记录 如果有 就填充到其中的历史记录消息数组当中

Java
// 从 Redis 中获取对话历史
public List getHistory(String userId) {
String historyStr = stringRedisTemplate.opsForValue().get(“id:” + userId + “:history”);
if (historyStr==null){
return null;
}
return JSONUtil.toList(JSONUtil.parseArray(historyStr), RoleContent.class);
}

public String main(String newQuestion,String userid) throws Exception {
//…
//这里创建了大模型的新对象 实际上那些发送请求获取答案的操作都是在这个线程中做的
BigModelNew bigModelNew = null;
if (getHistory(userid)!=null){//这里进行了判断 这个用户有没有历史记录
bigModelNew=new BigModelNew(userid, false,getHistory(userid),stringRedisTemplate);
}
else {
bigModelNew=new BigModelNew(userid, false,historyList,stringRedisTemplate);
}
//…
}

这样我们就能异步的处理这些对话消息 并且把他们放在对应的缓存空间当中


这个是获取历史记录的方法

Java
@RequestMapping("/history")
public Result history(@RequestParam(“id”) String id) {

String history = stringRedisTemplate.opsForValue().get(“id:” + id + “:history”);
if (history == null) {
return Result.fail(“没有找到历史记录”);
}

JSONArray jsonObject = JSON.parseArray(history);
// String jsonString = JSON.toJSONString(jsonObject);
return Result.ok(jsonObject);
}
相关文章
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
983 0
|
3月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
213 4
|
6月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
381 83
|
6月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
360 83
|
8月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
294 0
|
8月前
|
设计模式 运维 监控
并发设计模式实战系列(4):线程池
需要建立持续的性能剖析(Profiling)和调优机制。通过以上十二个维度的系统化扩展,构建了一个从。设置合理队列容量/拒绝策略。动态扩容/优化任务处理速度。检查线程栈定位热点代码。调整最大用户进程数限制。CPU占用率100%
520 0
|
8月前
|
存储 缓存 安全
JUC并发—11.线程池源码分析
本文主要介绍了线程池的优势和JUC提供的线程池、ThreadPoolExecutor和Excutors创建的线程池、如何设计一个线程池、ThreadPoolExecutor线程池的执行流程、ThreadPoolExecutor的源码分析、如何合理设置线程池参数 + 定制线程池。
JUC并发—11.线程池源码分析
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
676 59
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
214 6

热门文章

最新文章