Typescript 第八章 异步编程,并行和并发(JavaScript事件循环,异步流,多线程类型安全)
Typescript第八章 异步编程,并发和并行
异步API,比如说回调,promise和流。
JavaScript引擎在一个线路中多路复用任务,而其他任务则处于空闲状态。这种事件循环是JavaScript引擎的标准线程模型。
多路复用是指在一个线程中同时处理多个任务
异步编程让程序难以理解。我们不能一行一行的分析程序。
Typescript提供了工具,通过类型可以追踪异步操作,借助内置的async/await可以把熟悉的同步思想运用到异步程序上。使用Typescript还可以为多线程程序指定严格的的消息传递协议。
我们先讨论一下JavaScript引擎中具体是如何运作的,弄清楚为什么看似单个的线程中可以暂停和恢复执行。
8.1 JavaScript的事件循环
下面代码怎么执行?
setTimeout(()=>console.log("A"),1)// 时间到了就加入事件队列等待执行setTimeout(()=>console.log("B"),2)console.log("C");
JavaScript和C语言中sleep才用的并发模型,以及java把作业调度到另外一个线程中不同。
概括的说,JavaScriptVM采用下述方式模拟并发
- JavaScript主线程调用XMLHTTPRequest(处理Ajax请求),setTimeout,readFile等异步API。这些API由JavaScript平台提供,我们自己不能创建
- 调用原生的异步API之后,控制权返回主线程,继续向下执行,就像从未调用异步API一样。
- 异步操作执行完毕后,平台在事件队列中添加一个任务。每个线程都有自己的队列,异步操作的结果就通过队列发回主线程。任务中有关于调用的元信息,还有主线程中回调函数的引用。
- 主线程的调用堆栈清空后,平台将检查事件队列中有没有待处理的任务。如果有等待处理的任务,平台着手处理,触发一个函数调用,把控制权返还给主线程中的那个函数。调用那个函数之后,如果调用堆栈又变空了,平台再次检查事件队列中有没有可以处理的任务了。这个循环一直运转下去,直到调用栈和事件队列都空,而且所有原生的异步API调用都已结束。
8.2 处理回调
JavaScript异步程序的核心基础就是回调。回调其实就是常规的函数,只是作为参数传给另一个函数。就像在同步程序中一样,另一个函数在做完操作(处理网络请求等)之后调用回调函数。异步代码调用的回调也是函数,而且类型签名中没有标明函数是异步调用的。
NodeJS的原生API。例如fs.readFile(采用异步方式从磁盘中读取文件的内容)和dns.resolveCname(采用异步方式解析CNAME记录),按照约定,回调的第一个参数是错误或null,第二个参数是结果或null。(错误优先)。
readFile的类型签名如下:
function readFile(path:string,options:{encoding:string,flag?:string},callback:(err:Error|null,data:string|null)=>void):void{}
注意:readFile和callback的类型没有什么特别之处,都是常规的JavaScript函数。签名中没有特别标明readFile是异步的,也没有指出在调用readFile之后控制权会立即传给下一行代码(不等待readFile的结果)。
pnpm add @types/node -D安装nodejs的类型声明
下面写一个案例读取文件
import * as fs from 'fs'fs.readFile("log.txt",{encoding:'utf-8'},(error,data)=>{if(error){console.log(error);return}console.log("success!:"+data);})// 采用并发写入fs.appendFile("log.txt","笑死我了",error=>{if(error){console.log("error"+error);}})
这里API是异步的,不能把API在代码中调用顺序理解为执行文件系统的操作顺序。readFile虽然在前面调用但是读取出来的访问日志可能没有后面新增的那行日志,具体有没有要看运行这段代码时文件系统有多繁忙。
Nodejs一般约定,如果函数的最后一个参数是一个接受两个参数的函数,而且顺序为Error|null和T|null类型,那么这个函数通常是异步的。
无论如何,从类型上是看不出来的。
回调函数容易产生一个问题,回调地狱
setTimeout(()=>{console.log(1);setTimeout(()=>{console.log(2);setTimeout(()=>{console.log(3);console.log("啊");},1)},1)},1)
按顺序执行的操作往往是一环扣一环,前一步成功才执行下一步,除非遇到错误,我们要自己手动维护执行的顺序。按一定顺序执行的回调很容易出错。
有序的操作只是我们想借助异步任务执行的一种操作,此外我们可能还想并行运行几个函数,获知全部函数何时运行完毕,或者让几个函数竞争,只获取第一个结束的函数返回结果等。
- 使用回调函数可执行简单的异步任务
- 虽然回调适合处理简单的任务,但是如果异步任务变多,很容易一团乱麻。
8.3 promise(我tm来了!!!)
我们不是第一批遇到这些限制的程序员。本节说明promise,这个概念对异步进行抽象,方便任务编排,排列任务等。即使你以前使用过promise或future,也能更好的理解他们的原理。
// 自行分析console.log("start");async function async1() {console.log("async1");let result = await async2()console.log(result);}async function async2() {console.log("async2");return Promise.resolve("wohaole")}async1()console.log("end");
下面举个例子,指出我们想如何使用Promise:先向文件中添加一些内容,然后再把文件中的内容读取出来:
function appendAndReadPromise(path:string,data:string):Promise<string>{return appendPromise(path,data).then(()=>readPromise(path)).catch(error=>console.log(error);)}
注意:这里没有回调地狱,我们把想执行的一些异步任务,变成了易于理解的线性链条:前一个任务完成后才能执行下一个任务,倘若失败,则跳到catch子句。假如是基于回调的API,那么写出的代码可能是下面这样:
function appendAndRead(path: string,data: string,cb: (error: Error | null, result: string | null | any) => void) {appendFile(path, data, err => {if (err) {return cb(err, null)}readFile(path, (err, result) => {if (err) {return cb(err, null)}cb(null, result)})})}
针对这个设想,我们手动实现PromiseAPI
new Promise接受一个函数,我们称为执行器(executor)。在Promise的实现中,执行器接受两个参数,一个是resolve函数,一个是reject函数。
type Executor = (resolve: Function,reject: Function) => voidclass Promise {constructor(f:Executor){}}
那么,resolve和reject是如何运行的呢?下面通过示例说明一下。假设我们把Nodejs中的一个回调API(例如:fs.readFile)改造成基于Promise的API,Nodejs内置的fs.readFileAPI时这样使用的:
import {readFile} from 'fs'readFile(path,(error,result)=>{//..})
import {readFile} from 'fs'function readFilePromise(path:string):Promise<string>{return new Promise((resolve,reject)=>{readFile(path,(err,result)=>{if(err){reject(err)}resolve(result)})})}
可见,resolve的参数是什么类型取决于具体使用的API(这里,其参数的类型就是result的类型),而reject的参数始终是Error类型。因此,我们要更行类型,把不安全的Function改为更具体的类型:
type Executor<T,E extends Error> = (resolve: (result:T)=>void,reject: (error:E)=>void) => voidclass Promise<T,E extends Error> {constructor(f:Executor<T,E>){}}
class Promise<T,E extends Error> {constructor(f:Executor<T,E>){}then<U,F extends Error>(g:(result:T)=>Promise<U,F>):Promise<U,F>{//}catch<U,F extends Error>(g:(error:E)=>Promise<U,F>):Promise<U,F>{//}}
then和catch以不同的方式排列Promise:then把成功从一个Promise获得的结果映射到一个新Promise上,catch则把错误映射到一个新的Promise上,从被拒绝的状态中走出去。(这种风格和上节中的Option设计模式一样都受函数式编程语言Haskell中的Monad设计模式影响)
then的使用方法
let a:()=>Promise<string,TypeError> = //..let b:(s:string)=>Promise<number,never> = //let c:()=>Promise<boolean,RangeError>=//a().then(b).catch(e=>c()).then(result=>console.info("Done",result)).catch(e=>console.log("error",e))
此外,我们还有处理Promise真正抛出异常的情况(例如,throw Error(‘foo’)),为此,在实现then和catch要把代码放在try/catch中,在cattch中分支处理被拒绝的情况,然后事情并没有那么简单,这里还涉及一些其他问题
- Promise对象都有可能被拒,而通过静态检查发现不了这个问题。
- Promise对象被拒不一定是因为有错。Typescript别无选择,只能继承JavaScript的行为,而在JavaScript中,通过throw可以抛出一切。
考虑这两点,我们要放宽对Promise类型的要求,不指定错误类型
type Executor<T> = (resolve: (result: T) => void,reject: (error: unknown) => void) => voidclass Promise<T> {constructor(f: Executor<T>) { }then<U>(g: (result: T) => Promise<U>): Promise<U> {// }catch<U>(g: (error: unknown) => Promise<U>): Promise<U>{// }}let a = new Promise((resolve,reject)=>{resolve(123)})a.then((result)=>{// })
至此,Promise封装好了,内部具体实现,自行研究。
8.4 async和await
promise对于异步代码所做的抽象十分强大,JavaScript自身(当然包括Typescript)也有相应的句法:async和awai,使用这种句法,可以像同步操作那样处理异步操作
await可以视为.then在语言上的语法糖。使用await处理Promise对象时,要把相关的代码放在async块中。这种情况下不再使用.catch,而是把await放在常规的try/catch中。
// 老写法function getUser(){getUserId(18).then(user=>getLocation(user)).then(location=>console.log(location)).catch(error=>console.log(error)).finally(()=>console.log("done"))}
async function getUser() {try {let user = await getUserId(18)let location = await getLocation(user)console.log(location);} catch (error) {console.log("error");} finally{console.log("done");}}
async和await是JavaScript特性,这里就不深入探究了。
8.5 异步流
promise对象是便于建模,排列和编排未来的值,但是如果有多个值在未来的不同时刻产出。这种情况并不少见,比如从文件系统中读取文。
这样的操作有不同的建模方式,最为常见的是事件发射器(Nodejs EventEmitter)或响应式编程库(RxJS)。这两种方式之间的区别就像回调和promise对象一样:事件简单,轻量,而响应式编程库更强大,可以编排和排列事件流。
事件发射器
事件发射器提供的API用于在通道中发射事件,并监控通道中的事件:
interface Emitter{emit(channel:string,value:unknown):voidon(channel:string,f:(value:string)=>void):void}
发射器是JavaScript中一种常见的设计模式,使用DOM事件,或Nodejs EventEmitter模块中可能涉及。
type RedisClient = {on<E extends keyof Events>(event:E,f:(arg:Events[E])=>void):voidemit<E extends keyof Events>(event:E,arg:Events[E]):void}type RedisClient = {on(event:'ready',f:()=>void):voidon(event:'error',f:(e:Error)=>void):voidon(event:'reconnecting',f:(params:{attempt:number,delay:number})=>void):void}// 优化type Events = {ready:voiderror:Errorreconnecting:{attempt:number,delay:number}}type RedisClient = {on<E extends keyof Events>(event:E,f:(arg:Events[E])=>void):void}
把事件名称和参数提取到结构中,然后映射该结构,生成监听器和发射器,这是Typescript中常见的模式。
8.6 多线程类型安全
目前我们讨论的异步程序基本上运行在一个CPU线程中,不过,一些CPU密集型任务可能需要并行,把一项任务分到多个线程中。这么做可能是为了提升速度,可能是想让主线程空闲出来,继续相应后序操作。本节介绍编写安全的并行程序。涵盖浏览器和服务器。
8.6.1 在浏览器中:使用Web职程
浏览器大都支持web职程(worker)处理多线程。未免某些操作(例如CPU密集型任务)阻塞主线程,导致UI无响应,我们可以在JavaScript主线程中创建一些职称(严格受限的后后台代码,而Promise和setTimeout等异步PAI只是并发运行代码。职程在另一个cpu中运行代码)。web职程可以处理网络请求,文件系统写入等操作,不过有一些小限制。
Web职程是浏览器提供的API,设计人员对安全性提出了更高的要求,这里的“安全性”指的是,内存安全。如果多个线程读取同一块内存,很容易遇到各种并发问题。例如不确定性,死锁。
在tsconfig.json中加入“lib”:[“dom”,”es20120”,”webworker”]支持webworker
// 主线程代码let worker = new Worker("./workerScript.js")worker.postMessage("some data")// 接受其他线程worker.onmessage = e=>{console.log(e.data);}
// 并行线程,只能在浏览器环境才能用onmessage = e=>{console.log("child",e.data);// 发给主线程 postMessage("receiver"+e.data)}
这样传递消息很简单,但是没有类型,无法确保正确处理可能发送的所有消息类型。
类型安全的协议
我们知道如何在两个线程之间传递消息,那么,若想指明一个命令始终收到特定事件的响应怎么做呢?
我们可以选择在职程 中定义函数,把参数发给该函数,再把结果发送回来。
我们构建一个计算引擎,让他支持三种运算:求矩阵的行列式,计算两个矩阵的点积和求逆矩阵。
type Matrix = number[][]type MatrixProtocol = {determinant:{in:[Matrix],out:number},'dot-product':{in:[Matrix,Matrix]out:Matrix},invert:{in:[Matrix],out:Matrix}}
我们将在主线程中定义矩阵,运算则交给职程。我们对不安全的操作(职程发送和接受不带类型的消息)进行包装,把带类型的API开放给使用方。我们定义一个简单请求响应协议Protocol,列出职程可执行的操作,并为预期的输入和输出声明类型。
type Protocol = { // 1[command:string]:{in:unknown[],out:unknown}}function createProtocol<P extends Protocol>(script:string){ //2return <K extends keyof P>(command:K)=>(...args:P[K]['in'])=>{return new Promise<P[K]['out']>((resolve,reject)=>{let worker = new Worker(script)worker.onerror = rejectworker.onmessage = event => resolve(event.data.data)worker.postMessage({command,args})})}}
// let runWithMatrixProtocol = createProtocol<MatrixProtocol>('MatricWorkerScript.js')let parallelDeterminant = runWithMatrixProtocol('determinant')parallelDeterminant([[1,2],[3,4]]).then(determinant=>console.log(determinant))
type Matrix = number[][]type MatrixProtocol = {determinant:{in:[Matrix],out:number},'dot-product':{in:[Matrix,Matrix]out:Matrix},invert:{in:[Matrix],out:Matrix}}type Protocol = { // 1[command:string]:{in:unknown[],out:unknown}}function createProtocol<P extends Protocol>(script:string){ //2return <K extends keyof P>(command:K)=>{return (...args:P[K]['in'])=>{return new Promise<P[K]['out']>((resolve,reject)=>{let worker = new Worker(script)worker.onerror = rejectworker.onmessage = event => resolve(event.data.data)worker.postMessage({command,args})})}}}// let runWithMatrixProtocol = createProtocol<MatrixProtocol>('MatricWorkerScript.js')let parallelDeterminant = runWithMatrixProtocol('determinant')parallelDeterminant([[1,2],[3,4]]).then(determinant=>console.log(determinant))
8.6.2 在nodejs使用子进程
// mainimport {fork} from 'child_process'let child = fork("./child.js")child.on("message",data=>{})child.send({type:'syn',data:[3]})
相关文章:
Typescript 第八章 异步编程,并行和并发(JavaScript事件循环,异步流,多线程类型安全)
Typescript第八章 异步编程,并发和并行 异步API,比如说回调,promise和流。 JavaScript引擎在一个线路中多路复用任务,而其他任务则处于空闲状态。这种事件循环是JavaScript引擎的标准线程模型。 多路复用是指在一个线程中同时处…...
c++ 打印当前时间(精确到毫秒)
打印时间精确到毫秒好实现,但是那种对用户可读性不好,更适合开头记一次结尾记一次,打印中间减出来的程序运行时间。 但是因为一些情况,我开多线程开的不方便打印结束时间,同事跟我说那你把开始时间打印一下࿰…...

mapstruct 错误 java.lang.NoSuchMethodError: Ljava/lang/Double 错误
问题描述 在使用 mapstruct 的过程中遇到错误 java.lang.NoSuchMethodError: Ljava/lang/Double 错误 问题解决 maven clean, 然后 maven install Build -> Rebuild Project 执行 maven install 时, 如果报错 找不到 xxx 类, 但 ctrl鼠标左键 发现可以点进去这个类, 那…...

SpringBoot+AOP+Redission实战分布式锁
文章目录 前言一、Redission是什么?二、使用场景三、代码实战1.项目结构2.类图3.maven依赖4.yml5.config6.annotation7.aop8.model9.service 四、单元测试总结 前言 在集群环境下非单体应用存在的问题:JVM锁只能控制本地资源的访问,无法控制…...

Linux系统---进程概念
文章目录 冯诺依曼体系结构操作系统(OS)进程的理解 进程状态 进程优先级 环境变量 进程地址空间 Linux2.6内核进程调度队列 一、冯诺依曼体系结构 我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。 如图…...
ELK常用语法和线上问题排查
ELK常用语法及线上问题排查 速查问题 1.全文搜索 在搜索栏输入目标值,查询所有字段中包含该值的文档 案例:用户反馈了一串id或者其他同事给了一个jobid,还没有头绪怎么查的时候,直接全局搜索 jobid 2.字段 限定字段全文搜索…...
session-cookies 三个缓存 localStorage、sessionStorage、Cookies。
session-cookies session-cookies This plugin is used to summarize the browser’s three caches localStorage, sessionStorage, Cookies.The plugin is designed to be quick and easy to use. Below is a summary of some apis. session-cookies 这个插件是用来汇总浏览…...
LA@行列式性质
文章目录 行列式性质🎈转置不变性质交换性质多重交换移动(抽出插入)👺 因子提取性质拆和性质倍加性质 手算行列式的主要方法原理:任何行列式都可以化为三角行列式 行列式性质🎈 设行列式 ∣ A ∣ d e t ( a i j ) |A|\mathrm{det}(a_{ij}) …...

【java安全】原生反序列化利用链JDK7u21
文章目录 【java安全】原生反序列化利用链JDK7u21前言原理equalsImpl()如何调用equalsImpl()?HashSet通过反序列化间接执行equals()方法如何使hash相等? 思路整理POCGadget为什么在HashSet#add()前要将HashMap的value设为其他值? 【java安全】…...
[SQL智慧航行者] - SQL刷题规划
前言: 不知不觉, 慢慢的把一些基础知识整理到了现在. SQL挖掘机系列可能就要到此结束了. 目前是规划把这部分SQL练习部分放到SQL刷题的专栏, 就不放到SQL专栏了. 因为后续如果接触更多的知识点和题目也好分类. 请大家如果关注的话, 想看SQL知识点 --> SQL专栏; 想看SQL刷…...

概率论与数理统计:第一章:随机事件及其概率
文章目录 概率论Ch1. 随机事件及其概率1.基本概念(1)随机试验、随机事件、样本空间(2)事件的关系和运算①定义:互斥(互不相容)、对立②运算法则:德摩根率 (3)概率的定义(4)概率的性质(5)概率计算排列组合 2.等可能概型1.古典概型 (离散)2.几何概型 (连续…...
elementUi重置Select选择器样式、option、deep、vue3、plus
文章目录 前言select选框样式(element-plus)option选项面板样式(element-plus) 前言 样式标签属性为<style scoped lang"scss"></style> select选框样式(element-plus) // 设置选择框的背景样式 ::v-deep .el-input__wrapper {background: transparent;b…...

jar命令的安装与使用
场景: 项目中经常遇到使用WinR软件替换jar包中的文件,有时候存在WinRAR解压替换时提示没有权限,此时winRAR不能用还有有什么方法替换jar包中的文件。 方法: 使用jar命令进行修改替换 问题: 执行jar命令报错jar 不…...
windows系统多线程多进程下应用函数CloseHandle的一些细节
Windows CloseHandle()函数调用之后,对应的内核对象的句柄计数器减去1。如果此内核对象的句柄的计数器计数变为0,系统会回收此内核对象。所以,如果创建了内核对象(例如可调用了CreateThread创建一个线程内核对象。此对象创建成功后此线程内核…...

微服务——elasticsearch
初识ES——什么是elasticsearch elasticsearch的发展 初识ES——正向索引和倒排索引 初识ES——es与mysql的概念对比 类比到mysql中是表结构约束 概念对比 初始ES——安装es和kibana 1.部署单点es 1.1创建网络 要安装es容器和kibana容器并让他们之间相连,这里…...

paddlenlp:社交网络中多模态虚假媒体内容核查
初赛之环境配置篇 一、背景二、任务三、数据集1、初赛阶段2、评分标准 四、环境操作五、写在最后 一、背景 随着新媒体时代信息媒介的多元化发展,各种内容大量活跃在媒体内中,与此同时各类虚假信息也充斥着社交媒体,影响着公众的判断和决策。…...
Centos系统有哪些特点呢
CentOS 是一个基于 Red Hat Enterprise Linux (RHEL) 的免费的开源操作系统,由 CentOS 项目维护。CentOS 项目是一个社区驱动的项目,旨在为用户提供一个稳定的、可扩展的 Linux 发行版。 CentOS 系统具有以下特点: 稳定性:CentOS 系统非常稳定,即使是在高负载的环境下也是如此…...

一文学会git常用命令和使用指南
文章目录 0. 前言1.分支分类和管理1. 分支分类规范:2. 最佳实践3. 分支命名规范示例:4. 分支管理方法: 2. commit 注释规范1. 提交注释结构:2. 提交注释的准则: 3. git 常用命令1. git pull 核心用法2. git push 命令1…...
[PyTorch][chapter 46][LSTM -1]
前言: 长短期记忆网络(LSTM,Long Short-Term Memory)是一种时间循环神经网络,是为了解决一般的RNN(循环神经网络)存在的长期依赖问题而专门设计出来的。 目录: 背景简介 LSTM C…...

寄存器详解(二)
目录 内存中字的存储 示例: 数据段寄存器DS与[address] 字的传送 数据段简介 CPU提供的栈机制 栈段寄存器SS和栈顶指针寄存器SP PUSH AX指令的完整描述 示例图 POP AX指令的完整描述 示例图 栈顶超界问题 示例一: 示例二: 内存中字…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...

企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...