2026年3月

1 ,现在 Ai 服务器商 人们只看到设备折旧,却没想过现在设备发展速度慢了很多很多(显卡的摩尔定律失效,其他的硬件也基本停滞比如发动机,再比如光纤,网线,固态内存,机械硬盘),所以折旧不能按照几年前标准算了。
(不过听说 HBM 显存损坏率很高,这里存疑。)

2 ,Ai 算法优化空间还非常大,马斯克,还有其他技术人员都有说过这一点,Ai 的算法优化/智力成长也有类似于“摩尔定律”的东西。
不过我对智力成长存疑,我更支持算法优化有大进步。

而且算法优化是不会增加设备需求的。基于以上两点,我认为 Ai 云服务器的逻辑和互联网泡沫的逻辑有点对不上,不知道有没有不同的看法。

附:根据目前互联网软件/服务器厂商的 PE ,泡沫率大概在 10-25%。如果真有问题现在完全停止建设服务器,大概 3 年就能回本。

还有一件事,目前肉眼可见的是国内几乎所有 Ai 厂都降智了。

美国已经在中东部署了挺大兵力,伊朗最高领导人都被炸了,感觉这次玩的比较大,可能会造成伊朗全面战争吗

金价感觉一直以为在顶,但原来一直是腰部,能想到的对普通人投资就是投原油和黄金,v 友还有其他想法吗

另外就是觉得防空好关键,老美感觉除了中俄外,其他想斩首谁,都没太大问题

想找台二手 pixel ,有些问题还是很疑惑,请朋友们解答,谢谢。

1 、现在买 pixel 几 性价比最高?

2 、V*N 是不是要 pixel 7 以后的机型支持?

3 、相册容量无限是不是 pixel 全系列都支持?

4 、小黄鱼上很多写着 OEM 开锁是不是已经 BL 解锁和 root 了? 解了后是不是 V*N 无法用了? 解了的机器是不是刷官方固件就能回锁? 若未解的机器 BL 解锁和 root 简单么?

感谢各位朋友解答,谢谢。

选择 Google Gemini 的Deep Research后,运行了将近 24 小时了,一直显示“研究已加入队列”,有人遇到吗?这也太久了,是卡死了吗?

这是Java19新增的预览版功能,到Java21正式可以使用

简介

虚拟线程是一种用户态下的线程,类似go语言中的goroutines 和Erlang中的processes,虚拟线程并非比线程快,而是提高了应用的吞吐量,相比于传统的线程是由操作系统调度来看,虚拟线程是我们自己程序调度的线程。如果你对之前java提供的线程API比较熟悉了,那么在学习虚拟线程的时候会比较轻松,传统线程能运行的代码,虚拟线程也可以运行。虚拟线程的出现,并没有修改java原有的并发模型,也不会替代原有的线程。虚拟线程主要作用是提升服务器端的吞吐量。

为什么要有虚拟线程

服务器应用程序的伸缩性受利特尔法则(Little’s Law)的制约,与下面3点有关

  • 延迟:请求处理的耗时
  • 并发量:同一时刻处理的请求数量
  • 吞吐量:单位时间内处理的数据数量

比如一个服务器应用程序的延迟是50ms,处理10个并发请求,则吞吐量是200请求/秒(10 / 0.05),如果吞吐量要达到2000请求/秒,则处理的并发请求数量是100。按照1个请求对应一个线程的比例来看,要想提高吞吐量,线程数量也要增加。

java中的线程是在操作系统线程(OS thread)进行了一层包装(目前大部分语言实现采用的线程模型,都是用户态的线程一对一映射到内核线程上,好处是实现简单,统一由操作系统负责调度),OS线程的优点是它足够通用,不管是什么语言/什么应用场景,但OS线程的问题也正是来自于此:

  • OS不知道用户态的程序会如何使用线程,它会给每条线程分配一个固定大小的堆栈,通常会比实际使用的要大很多;
  • 线程的上下文切换要通过内核调度进行,相对更慢;
  • 线程的调度算法需要做兼顾和妥协,很难做特定的优化,像web server中处理请求的线程和视频编解码的线程行为有很大的区别;

为了解决该问题,虚拟线程就出现了。也就是多对多的线程模型:经典的就是Erlang的进程和Go的goroutine,M:N 的映射关系,大量(M)虚拟的线程被调度在较少数量(N)的操作系统线程上运行。用户态的运行时负责调度用户态线程,OS则只需要负责OS线程,各司其职。灵活度更高,开发者基本不用担心线程数爆炸的问题。

与虚拟地址可以映射到物理内存类似,java是将大量的虚拟线程映射到少量的操作系统线程,带来了一些好处:

  • 线程的切换很快,无需系统调用和系统级别的上下文切换
  • 分配线程的开销很低:一方面是创建和销毁很快,另一方面内存使用也更少
  • 竞态条件和线程同步处理起来更简单
  • 且虚拟线程的生命周期短暂,不会有很深的栈的调用
  • 一个虚拟线程的生命周期中只运行一个任务,因此可以创建大量的虚拟线程,
  • 虚拟线程无需池化

另一方面,虚拟线程不能带来什么?

要意识到虚拟线程是更轻量的线程,但并不是"更快"的线程,它每秒执行的CPU指令并不会比普通线程要多。假设有这样一个场景,需要同时启动10000个任务做一些事情:

// 创建一个虚拟线程的Executor,该Executor每执行一个任务就会创建一个新的虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            doSomething();
            return i;
        });
    });
}  // executor.close() is called implicitly, and waits

考虑两种场景:

  1. 如果doSomething()里执行的是某类IO操作,那么使用虚拟线程是非常合适的,因为虚拟线程创建和切换的代价很低,底层对应的可能只需要几个OS线程。如果没有虚拟线程,不考虑ForkJoin之类的工具,使用普通线程的话:
    • Executors.newVirtualThreadPerTaskExecutor()换成Executors.newCachedThreadPool()。结果是程序会崩溃,因为大多数操作系统和硬件不支持这种规模的线程数。
    • 换成Executors.newFixedThreadPool(200)或者其他自定义的线程池,那这10000个任务将会共享200个线程,许多任务将按顺序运行而不是同时运行,并且程序需要很长时间才能完成。
  2. 如果doSomething()里执行的是某类计算任务,例如给一个大数组排序,那么虚拟线程反而还可能带来多余的开销。

总结一下,虚拟线程真正擅长的是等待,等待大量阻塞操作完成。它能提供的是 scale(更高的吞吐量),而不是 speed(更低的延迟)。虚拟线程最适合的是原来需要更多线程数来处理计算无关业务的场景,典型的就是像web容器、数据库、文件操作一类的IO密集型的应用。

虚拟线程的理解

平台线程和虚拟线程

平台线程(platform thread):指Java中的线程,比如通过Executors.newFixedThreadPool()创建出来的线程,我们称之为平台线程。

虚拟线程并不会直接分配给cpu去执行,而是通过调度器分配给平台线程,平台线程再被调度器管理。Java中虚拟线程的调度器采用了工作窃取的模式进行FIFO的操作,调度器的并行数默认是Jvm获取的处理器数量(通过该方法获取的数量Runtime.getRuntime().availableProcessors()),调度器并非分时(time sharing)的。在使用虚拟线程编写程序时,不能控制虚拟线程何时分配给平台线程,也不能控制平台线程何时分配给cpu。

以前任务和平台线程的关系:

使用虚拟线程之后,任务-虚拟线程-调度器-平台线程的关系,1个平台线程可以被调度器分配不同的虚拟线程:

携带器

调度器将虚拟线程挂载到平台线程之后,该平台线程叫做虚拟线程的携带器(言外之意就是,平台线程携带着虚拟线程),调度器并不维护虚拟线程和携带器之间的关联关系,因此在一个虚拟线程的生命周期中可以被分配到不同的携带器,即虚拟线程运行了一小段代码后,可能会脱离携带器,此时其他的虚拟线程会被分配到这个携带器上。

携带器和虚拟线程是相互独立的,比如:

  • 虚拟线程不能使用携带器的标识,Thread.current()方法获取的是虚拟线程本身。
  • 两者有各自的栈空间。
  • 两者不能访问对方的Thread Local变量。

在程序的执行过程中,虚拟线程遇到阻塞的操作时大部分情况下会被解除挂载,阻塞结束后,虚拟线程会被调度器重新挂载到携带器上,因此虚拟线程会频繁的挂载和解除挂载,这并不会导致操作系统线程的阻塞。下面的代码在执行两个get方法和send方法(会有io操作)时会使虚拟线程发生挂载和解除挂载:

response.send(future1.get() + future2.get());

有些阻塞操作并不会导致虚拟线程解除挂载,这样会同时阻塞携带器和操作系统线程,例如:操作系统基本的文件操作,java中的Object.wait()方法。下面两种情况不会导致虚拟线程的解除挂载:

  1. 执行synchronized同步代码(会导致携带器阻塞,所以建议使用ReentrantLock替换掉synchronized)
  2. 执行本地方法或外部函数

虚拟线程和平台线程的区别

从内存空间上来说,虚拟线程的栈空间可以看作是一个大块的栈对象,它被存储在了java堆中,相比于单独存储对象,堆中存储虚拟线程的栈会造成一些空间的浪费,这点在后续的java版本中应该会得到改善,当然这样也是有一些好处的,就是可以重复利用这部分栈空间,不用多次申请开辟新的内存地址。虚拟线程的栈空间最大可以达到平台线程的栈空间容量。

虚拟线程并不是GC root,其中的引用不会出现stop-world,当虚拟线程被阻塞之后比如BlockingQueue.take(),平台线程既不能获取到虚拟线程,也不能获取到queue队列,这样该平台线程可能会被回收掉,虚拟线程在运行或阻塞时不会被GC

  • 通过Thread构造方法创建的线程都是平台线程
  • 虚拟线程是守护线程,不能通过setDaemon方法改成非守护线程
  • 虚拟线程的优先级是默认的5,不能被修改,将来的版本可能允许修改
  • 虚拟线程不支持stop(),suspend(),resume()方法

使用虚拟线程

java中创建的虚拟线程本质都是通过Thread.Builder.OfVirtual对象进行创建的,虚拟线程的API非常非常简单,在设计上与现有的Thread类完全兼容。虚拟线程创建出来后也是Thread实例,因此很多原先的代码可以无缝迁移。创建虚拟线程有三种方式:

  1. 通过Thread.startVirtualThread直接创建一个虚拟线程
//创建任务
Runnable task = () -> {
    System.out.println("执行任务");
};

//创建虚拟线程将任务task传入并启动
Thread.startVirtualThread(task);

//主线程睡眠,否则可能看不到控制台的打印
TimeUnit.SECONDS.sleep(1);
  1. 使用Thread.ofVirtual()方法创建
//创建任务
Runnable task = () -> {
    System.out.println(Thread.currentThread().getName());
};

//创建虚拟线程命名为诺手,将任务task传入
Thread vt1 = Thread.ofVirtual().name("诺手").unstarted(task);
vt1.start();//启动虚拟线程

//主线程睡眠,否则可能看不到控制台的打印
TimeUnit.SECONDS.sleep(1);
  1. 通过ExecutorService创建,为每个任务分配一个虚拟线程,下面代码中提交了100个任务,对应会有100个虚拟线程进行处理。
/*
    通过ExecutorService创建虚拟线程
    ExecutorService实现了AutoCloseable接口,可以自动关闭了
*/
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    //向executor中提交100个任务
    IntStream.range(0, 100).forEach(i -> {
        executor.submit(() -> {
            //睡眠1秒
            try {
                Thread.sleep(Duration.ofSeconds(1));
                System.out.println(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }                    
        });
    });
}

现在平台线程和虚拟线程都是Thread的对象,那该如何区分该对象是平台线程还是虚拟线程?可以利用Thread中的isVirtual()方法进行判断,返回true表示虚拟线程:

//创建任务
Runnable task = () -> {
    System.out.println("执行任务");
};

//创建虚拟线程将任务task传入并启动
Thread vt = Thread.startVirtualThread(task);
System.out.println(vt.isVirtual());

性能对比

public void tryCreateInfiniteThreads() {
    var adder = new LongAdder();
    Runnable job = () -> {
        adder.increment();
        System.out.println("Thread count = " + adder.longValue());
        LockSupport.park();
    };

    // 启动普通线程
    startThreads(() -> new Thread(job));
    // 或是启动虚拟线程
    startThreads(() -> Thread.ofVirtual().unstarted(job));
}

public void startThreads(Supplier<Thread> threadSupplier) {
    while (true) {
        Thread thread = threadSupplier.get();
        thread.start();
    }
}

普通线程:创建到4064个线程后程序报OOM错误崩溃。

.......
Thread count = 4063
Thread count = 4064
[0.927s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached.
[0.927s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-4064"
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
    at java.base/java.lang.Thread.start0(Native Method)
    at java.base/java.lang.Thread.start(Thread.java:1535)
    at com.rhino.vt.VtExample.startThread(VtExample.java:24)
    at com.rhino.vt.VtExample.main(VtExample.java:13)

虚拟线程:创建了超过360万个虚拟线程后被挂起,但没有崩溃,虚拟线程的计数一直在缓慢增长,这是因为被 park 的虚拟线程会被垃圾回收,然后 JVM 能够创建更多的虚拟线程并将其分配给底层的平台线程。

Github上有位老哥做了个更接近真实场景的测试,模拟远程服务请求数据,比较了使用普通线程阻塞式请求、CompletableFeature异步请求、虚拟线程的三种方式的差异,结果显示在连接数少的时候三者差别不大,连接数上去后虚拟线程在吞吐量、内存占用、延迟、CPU占用率方面都有比较大的优势,如下图:

可能这么对比还是不够公平,毕竟一般我们不会直接用这么简单的异步编程,还是会通过各种框架轮子搞。Oracle 的Helidon Níma 号称是第一个采用了虚拟线程的微服务框架,主要的卖点也是性能,可以参考其QPS性能测试数据:

可以看到使用了虚拟线程的web服务器性能很好,与用Netty的差距很小,这也符合预期。相比起来虚拟线程使用起来更简单。

深入虚拟线程

thread = continuation + scheduler

回过头来讨论下:到底什么是"线程"?简单的定义是,"线程"是顺序执行的一系列计算机指令。由于我们处理的操作可能不仅涉及计算,还涉及 IO、定时暂停和同步等,线程会有包括运行、阻塞、等待在内的各种状态,并在状态之间调度流转。当一个线程阻塞或等待时,它应该腾出计算资源(CPU内核),并允许另一个线程运行,然后在等待的事件发生时恢复执行。这其中涉及到两个概念:

  1. continuation(这个词实在不知道怎么翻译才恰当):一系列顺序执行的指令序列,可能会暂停或阻塞,然后恢复执行;
  2. scheduler:顾名思义,负责协调调度线程的机制;

两者是独立的,因此我们可以选择不同的实现。之前的普通线程,在VM层面仅仅是对OS线程的一层简单封装,continuation和scheduler都是交给OS管理,而虚拟线程实现则是在VM里完成这两件事情,当然底层还是需要有相应的OS线程作为载体线程(CarrierThread),并且这个对应并不是固定不变的,在虚拟线程恢复后,完全可能被调度到另一个载体线程。

组合scheduler-OSscheduler-Runtime
continuation-OSJava现在的Thread谷歌对Linux内核修改的User-Level Threads
continuation-Runtime糟糕的选择?虚拟线程

虚拟线程的调用堆栈存在Java堆上,而不是OS分配的栈区内。其内存占用开始时只有几百字节,并可以随着调用堆栈自动伸缩。虚拟线程的运行其实就是两个操作:

  • 挂载(mount):挂载虚拟线程意味着将所需的栈帧从堆中临时复制到载体线程的堆栈中,并在挂载时借用载体堆栈执行。

  • 卸载(unmount):当在虚拟线程中运行的代码因为 IO、锁等原因阻塞后,它可以从载体线程中卸载,然后将修改的栈帧复制回堆中,从而释放载体线程以进行其他操作(例如运行另一个虚拟线程)。对应的,JDK 中几乎所有的阻塞点都已经过调整,因此当在虚拟线程上遇到阻塞操作时,虚拟线程会从其载体上卸载而不是阻塞。

关于scheduler就比较简单了,因为JDK中有现成的ForkJoinPool可以用。work-stealing + FIFO,性能很好。scheduler的并行性是可用于调度虚拟线程的OS线程数。默认情况下,它等于可用CPU核数,也可以使用系统属性jdk.virtualThreadScheduler.parallelism进行调整。

需要注意的是,JDK中的绝大多数阻塞操作将卸载虚拟线程,释放其载体线程来承担新的工作。但是,JDK中的一些阻塞操作不会卸载虚拟线程,因此会阻塞其载体线程。这是因为操作系统级别(例如,许多文件系统操作)或JDK级别(例如,Object.wait())的限制。这些阻塞操作的解决方式是,通过临时扩展scheduler的并行性来补偿操作系统线程的捕获。因此,scheduler的ForkJoinPool中的平台线程数量可能暂时超过CPU核数。scheduler可用的最大平台线程数可以使用系统属性:jdk.virtualThreadScheduler.maxPoolSize进行调整。

虚拟线程源码

试着写一个使用虚拟线程进行网络IO的例子,来窥视下虚拟线程底层的魔法。

下面代码使用了基于虚拟线程的ExecutorService来获取一组URL的响应。每个URL任务会启动一个虚拟线程进行处理。

// record是JDK 14中引入的,这里作为简单的数据类,保存url和响应
record URLData (URL url, byte[] response) { }

public List<URLData> retrieveURLs(URL... urls) throws Exception {
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        var tasks = Arrays.stream(urls)
            .map(url -> (Callable<URLData>)() -> getURL(url))
            .toList();
        return executor.invokeAll(tasks)
            .stream()
            .filter(Future::isDone)
            .map(this::getFutureResult)
            .toList();
    }
}

获取响应的逻辑在getURL中实现,使用同步的URLConnectionAPI来读取数据。

URLData getURL(URL url) throws IOException {
  try (InputStream in = url.openStream()) {
    return new URLData(url, in.readAllBytes());
  }
}

这里我模拟了两个HTTP接口,其中一个响应很慢,因此在运行后不会马上完成。

// test1接口sleep 1s返回,test2接口则sleep 100s
example.retrieveURLs(new URL("http://localhost:7001/test1"), new URL("http://localhost:7001/test2"));

这样就可以用jcmd命令进行线程转储。

$ jcmd `jps | grep VtExample | awk '{print $1}'` Thread.dump_to_file -format=json thread_dump.json

把结果中的普通线程堆栈去掉后,就得到了虚拟线程的堆栈:

{
  "container": "java.util.concurrent.ThreadPerTaskExecutor@5d5a133a",
  "parent": "<root>",
  "owner": null,
  "threads": [
   {
     "tid": "24",
     "name": "",
     "stack": [
        "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:357)",
        "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)",
        "java.base\/java.lang.VirtualThread.park(VirtualThread.java:499)",
        "java.base\/java.lang.System$2.parkVirtualThread(System.java:2596)",
        "java.base\/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)",
        "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:369)",
        "java.base\/sun.nio.ch.Poller.poll2(Poller.java:139)",
        "java.base\/sun.nio.ch.Poller.poll(Poller.java:102)",
        "java.base\/sun.nio.ch.Poller.poll(Poller.java:87)",
        "java.base\/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:175)",
        "java.base\/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:196)",
        "java.base\/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:304)",
        "java.base\/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:340)",
        "java.base\/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:789)",
        "java.base\/java.net.Socket$SocketInputStream.read(Socket.java:1025)",
        "java.base\/java.io.BufferedInputStream.fill(BufferedInputStream.java:255)",
        "java.base\/java.io.BufferedInputStream.read1(BufferedInputStream.java:310)",
        "java.base\/java.io.BufferedInputStream.implRead(BufferedInputStream.java:382)",
        "java.base\/java.io.BufferedInputStream.read(BufferedInputStream.java:361)",
        "java.base\/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:827)",
        "java.base\/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:759)",
        "java.base\/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1684)",
        "java.base\/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1585)",
        "java.base\/java.net.URL.openStream(URL.java:1162)",
        "com.rhino.vt.VtExample.getURL(VtExample.java:59)",
        "com.rhino.vt.VtExample.lambda$retrieveURLs$0(VtExample.java:40)",
        "java.base\/java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:352)",
        "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)",
        "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)",
        "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:327)",
        "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:320)"
     ]
   }
  ],
  "threadCount": "1"
}

作为对比,把代码中的executor改成Executors.newCachedThreadPool(),再dump出直接使用普通线程的堆栈:

{
   "tid": "23",
   "name": "pool-1-thread-2",
   "stack": [
      "java.base\/sun.nio.ch.SocketDispatcher.read0(Native Method)",
      "java.base\/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:47)",
      "java.base\/sun.nio.ch.NioSocketImpl.tryRead(NioSocketImpl.java:251)",
      "java.base\/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:302)",
      "java.base\/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:340)",
      "java.base\/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:789)",
      "java.base\/java.net.Socket$SocketInputStream.read(Socket.java:1025)",
      "java.base\/java.io.BufferedInputStream.fill(BufferedInputStream.java:255)",
      "java.base\/java.io.BufferedInputStream.read1(BufferedInputStream.java:310)",
      "java.base\/java.io.BufferedInputStream.implRead(BufferedInputStream.java:382)",
      "java.base\/java.io.BufferedInputStream.read(BufferedInputStream.java:361)",
      "java.base\/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:827)",
      "java.base\/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:759)",
      "java.base\/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1684)",
      "java.base\/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1585)",
      "java.base\/java.net.URL.openStream(URL.java:1162)",
      "com.rhino.vt.VtExample.getURL(VtExample.java:59)",
      "com.rhino.vt.VtExample.lambda$retrieveURLs$0(VtExample.java:40)",
      "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)",
      "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)",
      "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)",
      "java.base\/java.lang.Thread.run(Thread.java:1589)"
   ]
 }

两个堆栈对比一下会发现,除了中间执行的业务逻辑部分是一致的,有两点不同:

  1. 普通线程的入口是Thread.run,而虚拟线程的入口是Continuation,这个类是虚拟线程的核心类,是VM内部对上面所说的continuation的抽象。Continuation有两个关键方法:yield()和run()。

可以试着跑一下这段代码看看输出结果:

public void testContinuation() {
    var scope = new ContinuationScope("test");
    var continuation = new Continuation(scope, () -> {
        System.out.println("C1");
        Continuation.yield(scope);
        System.out.println("C2");
        Continuation.yield(scope);
        System.out.println("C3");
        Continuation.yield(scope);
    });
    System.out.println("start");
    continuation.run();
    System.out.println("came back");
    continuation.run();
    System.out.println("back again");
    continuation.run();
    System.out.println("back again again");
    continuation.run();
}

// Output:
start
C1
came back
C2
back again
C3
back again again

PS:在Java19中还是预览版,需要加上下面的参数:(Java21后已经是正式版了)

--add-opens java.base/jdk.internal.vm=ALL-UNNAMED
  1. 普通线程会阻塞在read本地方法调用上(底层应该就是read系统调用),而虚拟线程则会通过VirtualThread#park挂起,这也对应了上面说的,JDK中几乎所有的阻塞点都已经过调整了。VirtualThread维护了一组state状态,调用park后就会设置成PARKING,可以在注释里看到状态之间的流转逻辑。

在线程dump文件里还能找到一个叫Read-Poller的线程(对应的还有一个写操作的 Write-Poller线程):

{
   "tid": "27",
   "name": "Read-Poller",
   "stack": [
      "java.base\/sun.nio.ch.KQueue.poll(Native Method)",
      "java.base\/sun.nio.ch.KQueuePoller.poll(KQueuePoller.java:66)",
      "java.base\/sun.nio.ch.Poller.poll(Poller.java:363)",
      "java.base\/sun.nio.ch.Poller.pollLoop(Poller.java:270)",
      "java.base\/java.lang.Thread.run(Thread.java:1589)",
      "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)"
   ]
 }

JDK底层做了什么调整呢?从Read-Poller可以看出,其实就是把原来的阻塞调用改为了非阻塞的IO调用。流程如下:

  1. 在阻塞调用中,检查是否虚拟线程,如果是的话,就注册一个NIO handler,即将文件描述符注册到Read-Poller的selector上。然后调用Continuation.yield()暂停自身。因为我本机是mac,所以线程堆栈里显示的NIO handler用的是KQueue,如果换成Linux,那就是我们熟悉的epoll了。
  2. Read-Poller底层维护了一组文件描述符 - 虚拟线程的映射,当一个文件描述符被注册到Read-Poller上时,同样也会将对应的虚拟线程加到这个映射里。
  3. 当Socket可读时,这个Read-Poller就会得到通知,随即调用wakeup()方法,从映射里找到文件描述符对应的虚拟线程,再将之前park()的虚拟线程unpark(),这样就完成了虚拟线程的唤醒。
/**
 * Unparks any thread that is polling the given file descriptor.
 */
private void wakeup(int fdVal) {
    Thread t = map.remove(fdVal);
    if (t != null) {
        LockSupport.unpark(t);
    }
}

虚拟线程的unpark()方法如下:

/**
 * Re-enables this virtual thread for scheduling. If the virtual thread was
 * {@link #park() parked} then it will be unblocked, otherwise its next call
 * to {@code park} or {@linkplain #parkNanos(long) parkNanos} is guaranteed
 * not to block.
 * @throws RejectedExecutionException if the scheduler cannot accept a task
 */
@Override
@ChangesCurrentThread
void unpark() {
    Thread currentThread = Thread.currentThread();
    if (!getAndSetParkPermit(true) && currentThread != this) {
        int s = state();
        // CAS设置线程状态
        if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) {
            if (currentThread instanceof VirtualThread vthread) {
                Thread carrier = vthread.carrierThread;
                carrier.setCurrentThread(carrier);
                try {
                    // 提交给scheduler执行
                    submitRunContinuation();
                } finally {
                    carrier.setCurrentThread(vthread);
                }
            } else {
                submitRunContinuation();
            }
        } else if (s == PINNED) {
            // unpark carrier thread when pinned.
            synchronized (carrierThreadAccessLock()) {
                Thread carrier = carrierThread;
                if (carrier != null && state() == PINNED) {
                    U.unpark(carrier);
                }
            }
        }
    }
}

在unpark()中,会将虚拟线程的状态重新设置为RUNNABLE,并且调用submitRunContinuation()方法将任务交给调度器执行,真正执行时,就会调用到Continuation.run()方法。另外,上面调用executor.invokeAll()方法提交任务时,底层同样也是调用了VirtualThread.submitRunContinuation()方法,这里的scheduler默认就是ForkJoinPool实例。

/**
 * Submits the runContinuation task to the scheduler.
 * @param {@code lazySubmit} to lazy submit
 * @throws RejectedExecutionException
 * @see ForkJoinPool#lazySubmit(ForkJoinTask)
 */
private void submitRunContinuation(boolean lazySubmit) {
    try {
        if (lazySubmit && scheduler instanceof ForkJoinPool pool) {
            pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
        } else {
            // 默认shceduler就是ForkJoinPool
            scheduler.execute(runContinuation);
        }
    } catch (RejectedExecutionException ree) {
        // 省略异常处理代码
    }
}

而在park()里,虚拟线程让出资源的关键方法是VirtualThread.yieldContinuation(),可以发现mount()和unmount()操作。


/**
 * Unmounts this virtual thread, invokes Continuation.yield, and re-mounts the
 * thread when continued. When enabled, JVMTI must be notified from this method.
 * @return true if the yield was successful
 */
@ChangesCurrentThread
private boolean yieldContinuation() {
    boolean notifyJvmti = notifyJvmtiEvents;

    // unmount
    if (notifyJvmti) notifyJvmtiUnmountBegin(false);
    unmount();
    try {
        return Continuation.yield(VTHREAD_SCOPE);
    } finally {
        // re-mount
        mount();
        if (notifyJvmti) notifyJvmtiMountEnd(false);
    }
}

mount()和unmount()会在Java堆和本地线程栈之间做栈帧的拷贝,这是Project Loom中为数不多的在JVM层面实现的本地方法,感兴趣的可以去Loom的github库里搜一下continuationFreezeThaw.cpp。其余的大部分代码在JDK中实现, 参见java.base模块下的jdk.internal.vm包。

点赞 + 关注 + 收藏 = 学会了

整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》

Volume Shader BM(俗称 “毒蘑菇测试”)是一款基于 WebGL 的开源浏览器 GPU 基准测试工具。

这个工具测试的是当前打开这个页面的设备,而不是部署它的设备。

也就是说,你部署在 NAS,但你用A电脑打开这个网页,测的是A电脑 GPU 性能,而不是 NAS 的 GPU 性能。

我用的是飞牛 NAS 部署毒蘑菇,其他品牌的 NAS 部署方式也是差不多的。

在“文件管理”里找到“docker”文件夹,在里面创建一个“volumeshader_bm”文件夹。

打开“Docker”应用,在“Compose”面板点击“新增项目”,填入以下内容。

路径选择刚刚在“文件管理”里创建的那个路径。

代码如下:

services:
  volumeshader_bm:
    image: heizicao/volumeshader_bm:latest
    container_name: volumeshader_bm
    ports:
      - 3331:3000
    restart: always

3331 是我给这个项目配置的端口,如果这个端口和你其他项目冲突的话,改过一个没用过的数字就行。

项目构建成功后,打开浏览器,输入 NAS的IP:3331 就可以折磨你的显卡了。

页面左下角是帧数。测测看吧~


以上就是本文的全部内容啦,有疑问可以在评论区讨论~

想了解更多NAS玩法可以关注《NAS邪修》👏

往期推荐:

点赞 + 关注 + 收藏 = 学会了

前提:公司之前开放过全员的 copilot ,后面转到全员的 cursor ,到后面 cursor 改了计费算法,就又改为了 codex 。
之前都是当个辅助开发使用,大家用量都不高。最近在推进全员的高频使用,确实很多坑。

  1. codex 算是走的企业中转,目前使用 5.3 速度非常慢(自己买的其实快的飞起),也能用,就是当前确实慢的头疼。
  2. Claude code ,前一段时间给部分员工开放了(中转),联系过之前的 cursor 采购商,这个非要离岸公司才能签署(众所周知的原因)。cc 确实好用,除了贵没毛病。
  3. 也联系过 qoder ,坊间传闻极致模式就是 cc 。极致模式确实可以,用量也非常快,基本上 30 美刀高频使用也是 1 一天的量。
  4. 其他的一些中转商,这个太不稳定了,特别是最近几天的大规模封禁,费用算法比之前贵 50 倍(之前是 0.02 对 1 刀,最近都是 1 元对 1 刀了)
  5. 其他的只用过 trae ,一坨。

大佬们这边公司都是使用什么方案呢?感谢大佬们给一些建议!

点赞 + 关注 + 收藏 = 学会了

💡整理了一个 NAS 专属玩法专栏,感兴趣的工友可以戳这里关注 👉 《NAS邪修》

用绿联 NAS 的工友们,平时有没有留意到应用商店里的一款AI相关的应用👉 AI Plugins

相比大家熟悉的 Open WebUI,AI Plugins 支持 GPU 加速,推理能力更强,主打一个本地模型的高效运行。

想要深入了解的工友,可以去绿联官网搜索 AI Plugins 官方文档👇

https://support.ugnas.com/knowledgecenter/#/detail/eyJpZCI6MT...

如果你习惯了 AI Plugins 的界面,又想接入外部的强大模型,最重要的是不想花钱,那么可以试试美团的 LongCat。

本文不是广,纯纯的羊毛分享

在写本文时,LongCat 极其慷慨的给了 2 套模型额度

  • LongCat-Flash-Lite:每天刷新 5000万 token!
  • 其他三款模型(Flash-Chat、Flash-Thinking、Flash-Thinking-2601):共用额外的 500万 token!

加起来每天足足 5500万 token 的免费额度!注意,是每天!

之前我在 《『n8n』推荐几个免费的大模型给学习n8n的工友们使用》 里也推荐过它,当时每天还只有 500 万额度。现在放在 n8n 里用简直爽飞,速度快、能力在线,最主要是免费(能薅多久不清楚,但现在白嫖就是赚)!

既然绿联官方提供了 AI Plugins 这个好用的壳子,LongCat 又如此慷慨,不用白不用。

打开浏览器,访问 LongCat API 开放平台,登录账号后,切换到 API Keys 面板(https://longcat.chat/platform/api_keys

点击创建,生成并复制你的专属 API Key(注意保密!!!)。

回到绿联 NAS,打开 AI Plugins 应用。登录后,点击页面左下角的头像,进入设置

在设置面板中,找到并点击“外部连接”,然后点击右上角的 “+” 号添加一个新连接。

  • URL输入:https://api.longcat.chat/openai
  • 密钥:填入你在第一步复制的 LongCat API Key
  • 模型ID:暂时可选以下 4 个(直接复制模型名称填入)

    • LongCat-Flash-Lite
    • LongCat-Flash-Chat
    • LongCat-Flash-Thinking
    • LongCat-Flash-Thinking-2601

保存配置后,回到 AI Plugins 的首页,新增一个聊天窗

在聊天窗的左上角,你就可以下拉选择刚刚配置好的 LongCat 模型了。


以上就是今天的“薅羊毛”全部内容啦!每天 5500 万额度,足够大家折腾各种有趣的玩法了。

我在 NAS 搭建了一套 n8n,它可以调用大模型帮我完成一些工作,有兴趣的工友可以了解一下n8n 实战教程

你平时在 NAS 上还有什么好玩、实用的 Docker 镜像推荐吗?欢迎在评论区留言,一起讨论折腾!

想了解更多NAS玩法记得关注《NAS邪修》👏

点赞 + 关注 + 收藏 = 学会了

当你的 PHP 应用的 API 没有限流时会发生什么?

API 为何需要限流来防止宕机、提升性能并增强安全性

想象一下:API 开始接收意料之外的流量激增。可能是爬虫在刷接口、用户活动突然暴增,甚至是恶意攻击。起初一切正常 —— 直到服务器突然宕机、响应时间飙升、用户反馈应用无响应。

问题出在哪?

根源可能是 PHP API 缺少限流机制。没有限流保护的 API 容易遭受过量请求的冲击,导致服务器资源紧张、性能下降,最坏的情况是服务完全中断。本文深入探讨 API 缺少限流时的后果、如何排查问题,以及如何有效实现限流。

实际发生了什么

在讨论缺少限流引发的问题之前,先了解 PHP 在典型 API 环境中如何处理请求和服务器资源。

API 被调用时,PHP 处理传入请求并加载必要资源,如脚本和文件。若未在特定时间段内限制请求或操作次数,服务器就容易被过度使用。限流的作用正是在此 —— 通过控制用户或服务在特定时间段内调用 API 的次数,充当一道安全防线。

PHP 文件包含机制

PHP 中通过 includerequireinclude_oncerequire_once 等函数实现文件包含,这对加载可复用资源或模板至关重要。但过度使用或实现不当会给服务器增加不必要的负担,导致性能下降:

  • includerequire 用于包含并执行 PHP 文件,但无法阻止文件被多次包含
  • include_oncerequire_once 防止文件在单次执行中被重复包含

理解这些函数的差异及其对性能的影响,对处理大型应用至关重要。

限流机制如何发挥作用

没有限流机制,API 可能因请求过多而被滥用,消耗服务器资源并拖慢响应速度。实施限流后,可限制用户在特定时间段内向 API 发起的请求数量,从而防止性能退化、保护敏感资源,甚至有助于防御 DDoS 攻击。

常见错误

以下是开发者在 PHP API 中实施或忽略限流时常犯的错误。这些陷阱不仅破坏功能,还引入安全与性能风险。

完全忽略限流

表现:API 可被无限制访问,用户请求数量不受任何约束。

原因:容易跳过限流实现,尤其当预期流量不大或 API 使用率不高时。

后果

  • 性能问题:无法控制请求数量会导致服务器过载
  • 安全风险:攻击者可无限制地滥用 API,引发 DDoS 攻击
  • 用户体验:正常用户可能遭遇响应变慢或错误

对不同用户类型不加区分地实施限流

表现:对普通用户和 VIP 用户实施相同的限流策略。

原因:可能误以为限流应该对所有用户一视同仁。

后果

  • 缺乏用户区分:VIP 用户或受信任的应用可能被不公平地节流,导致高优先级客户服务质量下降
  • 扩展性差:限流策略应根据用户类型灵活调整,但这种机会被错过

未使用高效的限流算法

表现:实现基础限流,如简单计数器在固定时间段后重置。

原因:以最简单的方式实现限流,常使用会话中的计数器或时间戳。

后果

  • 扩展性问题:简单方法难以扩展,尤其在多服务器或云基础设施场景下
  • 安全漏洞:若用户可操纵会话数据,这些方法更容易被绕过

忘记优雅地处理错误

表现:API 在超出限流阈值时返回晦涩的错误码或干脆无响应。

原因:实施了限流,但未考虑用户友好的错误提示或完善的日志记录。

后果

  • 用户困惑:用户可能不知道为什么请求被拒绝
  • 排查困难:没有完善的日志,难以调试请求为何被节流或限流

未考虑 Serverless 和云环境

表现:限流逻辑在本地服务器运行正常,但部署到云基础设施或 Serverless 环境时失效。

原因:AWS Lambda 或 Docker 容器等云环境在会话存储和状态持久化方面存在特定挑战。

后果

  • 行为不一致:没有集中式状态管理,限流计数器可能无法跨请求持久化
  • 性能退化:无状态环境可能引发竞态条件或内存过度使用,导致服务不稳定

正确的实现方式

以下是在现代 PHP 8+ API 中实施限流并避免上述陷阱的方法。采用基于中间件和共享缓存(如 Redis)的稳健方案来维护限流数据。

基础限流中间件示例

// RateLimiterMiddleware.php
class RateLimiterMiddleware {
    private $cache;
    private $rateLimit = 100;  // 每分钟最大请求数
    private $timeWindow = 60;  // 时间窗口(秒)

    public function __construct($cache) {
        $this->cache = $cache;
    }

    public function handle($request, $next) {
        $userId = $request->user()->id;
        $key = "rate_limit:{$userId}";

        $current = $this->cache->get($key);

        if ($current && $current >= $this->rateLimit) {
            return response('Rate limit exceeded', 429);
        }

        $this->cache->increment($key);
        $this->cache->expire($key, $this->timeWindow);

        return $next($request);
    }
}

优雅处理限流超限

// API 控制器中
public function getUserData(Request $request) {
    if ($this->rateLimitExceeded($request)) {
        return response()->json([
            'message' => 'Rate limit exceeded, please try again later'
        ], 429);
    }
    // 正常业务逻辑...
}

上述示例使用共享缓存(如 Redis)追踪用户在定义时间窗口内的请求次数。若计数超过阈值,请求将被拒绝并返回 429 状态码。

生产环境注意事项

部署 API 到生产环境时需考虑以下方面:

安全影响

限流有助于缓解暴力破解攻击或恶意爬虫对 API 的冲击。但需注意:

  • 路径穿越攻击:若允许用户输入文件路径,务必进行适当净化,避免暴露敏感文件
  • 远程文件包含:不要信任用户输入来包含远程资源。处理文件路径时始终验证并净化输入

扩展与性能

实施限流实际上有助于提升性能:

  • Opcode 缓存:使用 Redis 或 Memcached 等缓存层存储限流数据,避免每次请求重复计算
  • *_once 开销include_once 等函数会影响性能。确保在 API 请求期间不会重复加载同一文件

可观测性

为追踪生产环境中的限流情况,确保有完善的日志和错误报告机制。使用结构化日志捕获限流事件,并在监控工具中可视化。

部署差异

部署到云环境或 Serverless 时,确保跨容器或函数一致地管理状态。例如,使用 Redis 可确保各实例访问相同的限流数据。

排查检查清单

遇到限流问题时,可按以下清单排查:

  • 检查缓存配置:确保限流数据存储在共享缓存(如 Redis)中
  • 审查 API 日志:在日志中查找与限流相关的条目,识别请求峰值
  • 验证用户识别:确保通过 IP 地址或用户 ID 一致地追踪用户
  • 测试边界情况:模拟高流量并检查 API 对请求洪水的响应

调试代码示例

// 改进的日志记录
if ($this->rateLimitExceeded($request)) {
    Log::warning('Rate limit exceeded', [
        'user_id' => $request->user()->id,
        'ip' => $request->ip(),
        'timestamp' => now(),
    ]);
    return response()->json(['message' => 'Rate limit exceeded'], 429);
}

结论

关键要点:

  • 限流对保护 API 免受滥用、确保公平使用及防止性能退化至关重要
  • 恰当的限流策略需选择合适工具(如 Redis)并优雅地处理错误
  • 实施限流时应考虑用户区分、可扩展性和可观测性
  • 限流的调试与监控应纳入日常开发流程

下一步:

审查现有 API,确认是否已实施限流。若尚未实施,采用本文讨论的技术进行部署,以保护应用免受突发流量冲击。

常见问题

什么是限流?
限流控制用户在特定时间范围内可向 API 发起的请求数量,防止过载和滥用。

如何判断是否需要限流?
若 API 服务大量用户或处理敏感数据,限流至关重要。若曾遭遇流量激增或安全威胁,限流同样有用。

限流是否适用于所有用户?
可以,但建议针对不同用户类型设置分级限制,如 VIP 或高信任度应用。

[当你的 PHP 应用的 API 没有限流时会发生什么?
](https://catchadmin.com/post/2026-02/php-api-rate-limiting)

OpenAI 接替 Anthropic 与五角大楼达成合作

据《华尔街日报》等报道,2 月 27 日,特朗普政府宣布全面禁止联邦机构使用人工智能初创公司 Anthropic 的技术,并将其列为「供应链风险」。与此同时,美国国防部与 Anthropic 的竞争对手 OpenAI 达成协议,允许其 AI 模型在机密军事环境中部署。

此前,五角大楼要求拥有 Claude 模型在所有合法范围内的绝对使用权,而 Anthropic 坚持禁止用于国内大规模监控和自动化武器的红线。谈判破裂后,特朗普在社交平台上指责 Anthropic 是「激进左翼」企业,下令所有联邦政府机构在六个月内全面停用其模型。随后,美国国防部将其列入通常针对外国对手企业的「供应链风险」名单。此举不仅切断了该公司约 2 亿美元的政府合同,还可能迫使其他美国国防承包商剥离该技术。Anthropic 表示将对此项决定提起诉讼。

在 Anthropic 出局后,OpenAI 迅速填补了军方合同空缺。Sam Altman 声称,与军方达成的新协议中同样包含了禁止用于大规模监控和自主武器的安全条款。OpenAI 在网站公布了这些条款的具体措辞,并声称不清楚为什么 Anthropic 未能达成同等安全的协议。

OpenAI 特别强调,合作模型仍然托管在云端,因此自主可控,而这正是 Anthropic 在谈判中分析过并明确否定的方案,因为现代军事本来就是高度联网的,云端和武器端没有实质区别。此外,OpenAI 的条款只禁止在「法律法规、部门政策要求由人控制」的场合,由 AI 操控武器,但国防部的政策里目前只有「适当水平的人类判断」之类的模糊措辞,并且可以通过修改政策来绕过该限制。关于大规模监控的保障条款则主要在重述现有的适用法律,并未禁止政府通过间接方式或第三方获得监视数据,而 Anthropic 试图在合同中明确禁止政府用 AI 分析「非机密大批量商业数据」。

据媒体报道,OpenAI 高管曾在去年向支持特朗普的政治行动委员会捐献了数千万美元,而 Anthropic CEO 与国防部 CTO 之间有个人不和。在截止谈判当天,特朗普提前写好了攻击 Anthropic 的帖文。在 Anthropic 代表仍在推动谈判时,国防部与 OpenAI 的替代协议已经准备好。

Anthropic 成立于 2021 年,由多名因担忧 OpenAI 忽视 AI 安全而离职的核心研究人员创立,是目前全球估值最高的 AI 企业之一。该公司与现任美国政府的关系长期紧张,曾因聘用拜登政府前官员、呼吁加强 AI 监管以及批评限制对华 AI 芯片出口等举动引发政府不满。今年 1 月,美军在委内瑞拉的军事行动中使用了 Claude 模型,Anthropic 随后向合作伙伴询问模型使用细节,被五角大楼视为挑战军方行动的最终决定权,这成为了双方关系破裂的导火索。就在谈判破裂前几个小时,美军在中东的空袭行动中仍使用了 Claude。

此次事件在美国用户中间引发了卸载 OpenAI 的 ChatGPT 安装 Claude 的热潮。Claude 周六跃居苹果 App Store 美国区免费应用榜榜首,超过了 ChatGPT。


派拉蒙将收购华纳兄弟,Netflix 出局

据《华尔街日报》报道,2 月 26 日晚,经过长达半年的激烈竞购,由 David Ellison 领导的派拉蒙(Paramount)以高达 810 亿美元的报价成功收购华纳兄弟探索公司(Warner Bros. Discovery)。流媒体巨头 Netflix 正式宣布退出竞标,放弃了此前与华纳达成的 720 亿美元收购协议。

David Ellison 是甲骨文(Oracle)创始人 Larry Ellison 之子。此前,他刚通过旗下的天空之舞传媒(Skydance)拿下了派拉蒙的控制权。此次吞并华纳后,Ellison 将掌控一个规模空前的传媒帝国,将 HBO、CNN、CBS 新闻、华纳兄弟影业,以及 DC 漫画和《哈利·波特》等超级 IP 收入囊中。

这场夺标之战异常曲折,Ellison 在半年内先后提出了九次报价。起初,华纳多次拒绝其提案,甚至在去年 12 月一度宣布接受 Netflix 的收购方案。然而 Ellison 并未妥协,不仅将报价一路提高至每股 31 美元(较首次报价高出 63%),承诺替华纳向 Netflix 支付 28 亿美元违约金,威胁在股东大会上夺权,并在华盛顿展开广泛的政治游说,最终迫使华纳董事会倒戈。

消息传出后资本市场反应强烈,周五派拉蒙股价飙升超过 20%,及时退出价格战的 Netflix 股价也大涨近 14%。该笔交易预计需要 6 到 18 个月的时间才能正式完成交割。预计合并后的派拉蒙将承担约 1000 亿美元的债务,管理层将面临削减 60 亿美元成本的考验。


韩国解除对谷歌地图的数据限制

据《纽约时报》报道,2 月 27 日,韩国交通部批准了谷歌导出韩国高精度地图数据的请求。这一决定推翻了韩国长期以来的限制政策,为谷歌地图在该国提供包括实时导航在内的完整功能扫清了障碍。

长期以来,出于国家安全考量,韩国政府严格限制地图数据出境。这导致谷歌地图在韩国一直无法提供实时导航、步行路线以及详细的商铺信息,韩国民众和外国游客只能依赖本土导航应用。根据此次获批的条件,谷歌可以将经过韩国政府审查的地图数据转移至其全球数据中心,但军事基地等敏感安全信息必须被严格剔除。谷歌高管对该决定表示欢迎,不过暂未透露完整导航功能的具体上线时间。

谷歌自 2008 年将地图服务引入韩国以来便一直受制于上述问题,并于 2016 年首次公开提出导出数据的请求。据韩国交通部称,此次政策转向是出于「促进旅游业和提振经济」的考量,但实际上也受到美韩贸易谈判的影响。在此之前,特朗普政府曾向首尔方面施压,要求韩国停止在位置数据等监管层面歧视美国科技公司。

完整版谷歌地图的落地,预计将直接挑战 Naver 和 Kakao 等韩国巨头在数字地图领域的主导地位。韩国空间信息产业界对此表达了担忧,认为全球化平台的进入可能使本土企业最终沦为谷歌的分包商。但部分韩国旅游及商界人士持乐观态度,认为这不仅能帮助本土商家更好地拓展国际市场,也将以良性竞争促使本土地图服务进一步优化升级。


Ultrahuman 推出 Ring Pro 指环

The Verge 报道,2 月 27 日,健康科技品牌 Ultrahuman 发布了第三代旗舰智能戒指 Ring Pro 及 AI 健康平台 Jade。Ring Pro 共有四种颜色可选,定价 479 美元,目前已在全球开启预购,并将于 3 月开始发货。不过,由于与智能戒指头部品牌 Oura 存在专利纠纷,该新品暂未在美国市场发售。

在硬件规格上,Ring Pro 单次续航时间从原先的 4 至 6 天升至最长 15 天,并支持离线存储 250 天的健康数据。此外,新设备搭载了更快的双核处理器,配备新的心率传感器,并在物理设计上做了安全优化,确保在紧急情况下能更容易从手指上拆除。

随同推出的还有一款全新的 Pro 充电盒,能提供额外 45 天的续航,还能存储长达一年的设备数据。充电盒支持无线充电并配有 LED 电量指示灯,同时内置了扬声器和近距离追踪器。

Jade 生物智能 AI 平台目前能够提供实时健康洞察、指导呼吸训练并执行心房颤动(AFib)检测。Ultrahuman 计划通过未来更新使其具备主动预警健康风险、调节智能家居温度乃至在线点餐等功能,致力于将其打造为全天候在线的「自主健康代理」。


英伟达将推出 AI 推理专用芯片

据《华尔街日报》报道,英伟达计划在下月举行的圣何塞 GTC 开发者大会上,推出一款专为AI「推理」(Inference,即模型响应用户提示的过程)量身定制的新型芯片。该产品整合了初创公司 Groq 的芯片设计,能更高效地处理复杂的 AI 请求。ChatGPT 开发商 OpenAI 已同意成为该款新芯片的最大客户之一。

随着企业大量部署能相对自主执行任务的 AI 智能体(agents)和自动编程工具,市场对推理算力的需求激增。尽管英伟达凭借 Hopper、Blackwell 等系列占据了全球 GPU 市场 90% 以上的份额,但在实际运行推理任务时,传统 GPU 成本高昂、能耗过大。

为应对谷歌、亚马逊的自研芯片以及 Cerebras 等初创企业的竞争,英伟达于去年底斥巨资将 Groq 的技术与核心团队收入麾下。Groq 采用被称为「语言处理单元」(LPU)的独立架构,在 AI 模型逐词生成回答的解码阶段具备极高效率。目前,OpenAI 计划利用英伟达的新系统改进其自动编程工具 Codex,以对抗主要依托亚马逊和谷歌云芯片运行的竞品 Claude Code。

为了留住核心客户,英伟达近期向 OpenAI 注资 300 亿美元,还锁定了其「专用推理算力」的大额订单,对冲了 OpenAI 近期采购亚马逊和 Cerebras 芯片带来的影响。此外,英伟达本月还扩大了与 Meta 的合作,首次大规模部署纯依托其自研 Vera CPU 的服务器集群,用于支持 Meta 的广告 AI 智能体计算任务。


内存成本提升可能让入门 PC 消失

The Register 援引分析机构 Gartner 预测,受 AI 热潮带来的内存需求激增影响,全球 PC 和智能手机市场正面临严峻的成本危机。由于 DRAM 和 NAND 闪存价格飙升,今年全球 PC 出货量可能将下降超过 10%,智能手机出货量将下降约 8%,售价低于 500 美元(约 3430 元)的入门级平价设备或将彻底从市场上消失。

这一轮内存涨价与以往的产能缩减周期不同,主要源于大型云服务商(Hyperscalers)对 AI 算力基础设施的庞大需求,直接推高了消费电子领域的物料成本。部分内存价格自去年起已翻了数倍,Gartner 预计到 2026 年底,个人设备所用的内存和闪存价格还将进一步上涨 130%。在此压力下,设备厂商已无法维持平价 PC 的利润空间,若强行提价,价格敏感型消费者也不会买单。

此外,AI PC 的普及也受到了高昂内存价格的阻碍。以微软 Copilot+ 平台为例,其最低 16GB 的内存要求大幅拉高了制造门槛。惠普近期透露,DRAM 目前已占 PC 整机物料成本的 35%。这导致 AI PC 短期内只能维持在高端价位,预计到 2028 年其市场份额才能突破 50%。智能手机领域也面临同样困境,由于存储组件在廉价手机中的成本占比更高,低端手机受到的冲击尤为严重。

由于新机购入成本全面上升,企业和普通消费者将被迫延长现有设备的使用寿命,预计商用和家用 PC 的换机周期将分别拉长 15% 和 20%。分析师指出,此次由需求端引发的内存短缺恐将长期存在,预计将一直持续至 2027 年底。


不妨一看的简讯

  • 2 月 28 日,谷歌宣布,将自动解封近期因违反服务条款而被封号的 Gemini CLI 和 Antigravity 开发者账号,并推出自助式账号恢复流程。目前受影响的账号预计将在一两天内恢复正常访问。未来,谷歌将不会再无预警直接封禁。被系统标记违规的用户将收到邮件通知及明确的错误提示,并获取专属的申诉表单链接。用户需通过表单重新承诺遵守服务条款,特别确认不再使用第三方工具绕过系统的使用限制。提交申请后,账号将通过定期同步流程自动恢复;但若出现第二次违规,则将面临永久封禁。官方表示,任何利用第三方软件、工具或服务(如 OpenClaw),通过「窃取」(harvest)或「蹭用」(piggyback)Gemini CLI 的 OAuth 身份验证,从而调用谷歌后端服务及额度的行为,均属于严重违规。
  • 2 月 26 日,汉堡王(Burger King)母公司 RBI 宣布,将在汉堡王美国门店全面推广一款名为 Patty 的人工智能助手,用于辅助员工工作并监听其与顾客的互动,以评估团队的「友善度」。针对外界对职场过度监控的担忧,汉堡王方面强调该 AI 并非用于监视或考核个人系统不会强制员工使用特定话术,而是通过汇总「欢迎」「请」和「谢谢」等关键词的使用频率,帮助管理者了解餐厅整体的服务模式。此前,麦当劳和 Taco Bell 等品牌曾尝试引入面向顾客的 AI 得来速(Drive-through)自动点餐系统,但因频繁出错而遭叫停。
  • The Register 报道,微软的 HoloLens 在美军中找到了新的用武之地。此前在 2018 年,HoloLens 作为战场作战装备的测试导致士兵出现头痛、视力疲劳和恶心等严重生理不适,以失败告终,如今美国陆军和空军正将其重新转用于远程航空货物检查的辅助工作,并已取得初步的成功。在实际操作中,负责装载物资的陆军士兵佩戴 HoloLens,空军专家则通过笔记本电脑和 Wi-Fi 热点远程接入。空军人员能够以第一视角看到现场画面,并通过 AR 视觉提示指导士兵调整索具和重新摆放货物,以确保空投物资的配重符合飞行安全要求。据军方表示,这套系统经过一年的调试,目前已实现「即插即用」。微软已于 2024 年底停止 HoloLens 的后续开发,现有型号的官方支持也将在 2027 年底结束。尚不清楚用于此次实验的 HoloLens 头显是军方剩余库存,还是专门新采购的。
  • 3 月 1 日,星纪魅族集团中国区 CMO 万志强在魅族京东自营店直播期间表示,魅族现有型号仍在正常售卖,后续也不会涨价,但部分型号已经或接近售罄。魅族 23 已完成开发,但是不会面向市场发售。后续 Flyme 系统不会再推出大版本更新,但会持续提供基本维护。此前,魅族科技在 2 月 27 日发布战略转型公告,宣布将暂停国内手机新产品自研硬件项目。
  • Mark Gurman 声称——
    • 苹果计划在 2026 年底发布触控屏 MacBook Pro,但将坚持维持 Mac 和 iPad 的独立地位。新款 MacBook Pro 将保留传统设计和 macOS 界面,触控仅作为可选功能而非主要交互方式。此外,一款大型折叠屏 iPad 也在研发中,预计 2020 年代末问世,但仍将运行 iPadOS。MacBook Air 的改版预计要到 2028 年左右。
    • 在今年的 WWDC 上,苹果计划将 Core ML 框架更名为 Core AI。


少数派的近期动态

  • 少数派年度征文来了,古法手搓大战人工智能,你会是哪条赛道的大赢家?参与一下
  • 重磅新片《寻源南疆》上线,我们在雪山上拍了一部「公路电影」。看看精彩画面
  • 将设计装进耳朵:少数派×飞傲联名 CD 机盖板设计大赛已经开始啦。了解详情
  • 没什么用,但就是好玩:盘点或恶搞或无聊的「神经病」应用。看看都有啥
  • Sonos × 少数派 × 暖风家联合打造:声音与视觉的沉浸体验空间正式上线啦。了解详情
  • 我们正在优化并改进新的首页版式,如果你在使用过程中发现了任何问题或者有改进建议,请通过反馈表单告知我们。首页反馈收集


你可能错过的好文章


    📰 今日新闻精选:

    • 中指研究:2 月百城新房均价同比上涨 2.37%,环比涨幅杭州居首,二手房均价同比下跌 8.78%,环比上涨城市为 5 个
    • 2026 年度中国电影总票房已突破 100 亿元,持续领跑全球单一市场票房榜
    • 杭州一影院推出 “观影不满意可退票” 服务:放映 20 钟内体验不佳,可退还 40% 票价
    • 2026 上半年迎来近十年超短学期,多地中小学生实际在校上课时间预计不足 90 天
    • 个税汇算清缴首日网友晒退税额,有人退税上万元有人需补缴
    • 珠江、淮河、钱塘江、闽江及海南内陆水域 3 月 1 日起进入春季禁渔期
    • 一女子称坐火车硬卧遭虫咬致左手肿如馒头,12306 致回应:已经将登记工单流转至相关责任单位
    • 重庆奉节一地下暗河疑被养猪场排放的猪粪所污染,当地通报:已成立专班
    • 票务平台显示叙利亚大马士革飞往上海机票高达 382 万元,平台称系操作失误已下架
    • 巴菲特 “收官” 成绩单出炉:伯克希尔 60 年累计暴涨 6 万倍,现金储备飙至 3733 亿美元
    • 美媒:美国得州奥斯汀市中心发生枪击事件,致 3 死 14 伤,FBI 将该案件作为潜在恐袭进行调查
    • 外媒:伊朗遇袭后已致最高领袖哈梅内伊、前总统内贾德等约 40 名高官身亡,千余名平民死伤
    • 外媒:伊朗称向美军林肯号航母发射 4 枚导弹,美军否认称未被击中;特朗普称已击沉 9 艘伊朗舰艇,基本摧毁伊朗海军总部
    • 外媒:中东多地遭伊朗报复性打击,迪拜帆船酒店遭袭起火,伊朗称无意侵犯邻国主权,而是打击美军基地
    • 外媒:伊朗总统等 3 人将临时代行最高领袖职权;特朗普称伊朗新领导层希望恢复谈判,他已同意进行对话

    📅 今日信息:

    • 公历:2026-03-02 星期一 双鱼座
    • 农历:二〇二六年正月十四
    • 下一节气:2026-03-05,惊蛰
    • 今年进度:16.71%(已过 61 天,剩余 303 天)

    🌟 历史上的今天

    • 1962 年:美国篮球运动员沙奎尔·奥尼尔出生,他以幽默和强大的球场表现闻名,曾带领湖人队赢得多个 NBA 总冠军。
    • 1995 年:互联网先驱雅虎公司正式成立,由杨致远和大卫·费罗创立,早期推动了全球网络搜索和门户网站的发展。

    Apped 一下:
    1.已说祝她好运,我在意的点是她 28-29 开始谈,这个阶段应该是奔着结婚去,30 岁左右应该有个判断这段感情该不该继续,而不是拖 6 年。这段感情应该很坚固,我不希望是某人的替代品。当然,我身高不高,外在条件也不好,也没有信心能成为别人的替代品。
    2.我不介意二婚或者有没有交往经历,介意在感情和人生规划是不是果断。
    3.ChatGPT 和 Gemini 给的心理分析和建议都比较充实,只是想发出来大家闲暇讨论下,一言不合就开喷其实没必要。
    4.年纪的女生基本都有长时间的感情经历,很普遍,只是我不能接受这个点。

    除非类型特别长,否则少用 auto ,对 AI 不友好

    原项目是继续 windows 的,大量 windows api mfc atl 和 windows 资源的使用。还有 rpc 和跨进程的 handle 共享显示。几年前完成了 mac 的移植,做了大量的兼容和 stub 代码。平台宏定义满地都是。

    现在完成编译链接工作。运行起来各种 crash,几十年的祖传代码感觉用 ai 调内存问题也困难重重。

    是不是大公司项目方向对,在继续就行?最后能不能出东西,根本就没人管?

    C++什么会得到像我这样老年人的喜爱?

    首先 C++这个语言表达力及其丰富,以至于初学者不知所措,经常会看到不认识的语法,这是在其它语言不太会经历到的。 但是它所有的复杂性都服务于一个目标,抽象(abstraction)。抽象是一个高级的思考过程,它试图从杂乱无章中找到模式。

    不知各位有没有用过 boost json ,json 仅有几种有限的数据类型,大部分语言有类(class),用它来抽象这些数据类型也挺不错,c++也是 OOP 。 但是 C++还有 std::variant,就是说如果一个东西只可能有固定的几个类型,那么用std::variant来抽象更恰当(也可能更快,更不容易错,或者无法错)。

    其它比如shared_from_this等都是为解决问题而生,如果你没有碰到问题,那么你就不会深入理解shared_from_this。它是为了在异步环境中让对象自己保持活着,不然异步回调时如果对象已经销毁,就会 UAF 。

    namespace certctrl {
    
    class UpdateHandler : public IHandler, 
                          public std::enable_shared_from_this<UpdateHandler> {
    private:
      certctrl::ICertctrlConfigProvider &config_provider_;
      customio::ConsoleOutput &output_;
      client_async::HttpClientManager &http_client_;
      certctrl::CliCtx &cli_ctx_;
      std::shared_ptr<AgentUpdateChecker> update_checker_;
    
      // Platform detection
      std::string detect_platform();
      std::string detect_architecture();
      
      // Update workflow steps
      monad::IO<bool> check_for_updates(const std::string &current_version);
      monad::IO<bool> confirm_update();
      monad::IO<void> perform_update();
      monad::IO<std::string> download_update(const std::string &download_url);
      monad::IO<void> install_update(const std::string &downloaded_file);
      monad::IO<void> backup_current_binary();
      monad::IO<void> replace_binary(const std::string &new_binary_path);
      
      // Helper methods
      std::string get_current_binary_path();
      std::string generate_backup_path();
      bool verify_downloaded_file(const std::string &file_path, const std::string &checksum_url);
    
    public:
      UpdateHandler(certctrl::ICertctrlConfigProvider &config_provider,
                    customio::ConsoleOutput &output,
                    client_async::HttpClientManager &http_client,
                    certctrl::CliCtx &cli_ctx,
                    std::shared_ptr<AgentUpdateChecker> update_checker);
    
      std::string command() const override;
      monad::IO<void> start() override;
    };
    
    }
    

    当然这里仅仅举几个例子,每一个特性都是为解决问题而设计的。

    说到为什么年长者更喜欢 c++,我估计可能和大脑的抽象能力相关,我不是脑科学专家,我还问了 chatgpt ,它的答复:

    情况	结果
    纯逻辑、非经验性的抽象任务(数学推理、形状类比、无语言图形测试)	年轻人通常更强
    基于经验的抽象总结、模式识别	年长者可能更强
    需要同时抽象 + 处理大量新信息的任务	年轻人更快
    需要抽象 + 基于经验的判断	年长者表现可能更佳
    

    所以更准确的结论应该是,经验丰富的编程者可能会选择 C++。 如果你是初学者,不要为 C++的复杂度困扰,这需要一个过程,一个进步的过程。

    https://github.com/Hackerl/asyncio

    asyncio 是一个基于 libuv 的协程网络框架,使用 C++23 开发,支持 Linux/Windows/Android/macOS 四个主流平台。

    它绝不是一个玩具,而是可以真正用于生产的代码。在我公司内部,基于它研发的软件,已运行在了数万台员工的办公 PC 上(Windows/macOS),线上 Linux 服务器也正在逐步覆盖。

    项目有何优点?

    • 简单、精巧的代码
    • 灵活、优雅的子任务管理
    • 借鉴自多种语言,易于使用的 API
    • 借鉴自多种语言,设计优良的接口
    • 简单直接的任务取消机制
    • 基于线程、线程池可以轻松融合同步代码

    我用 C++ 实现了一个模式匹配库 Patternia:
    https://github.com/sentomk/patternia

    目前整体设计与实现已经趋于稳定,具备实际可用性,但仍然非常依赖真实使用场景来暴露设计与语义层面的不足,因此非常欢迎 issue 、反馈或设计层面的讨论。

    Patternia 旨在以零运行时开销的方式,为 C++ 提供一种更结构化、更具表达力的条件分支与数据解构机制,使控制流能够围绕数据的形态与语义展开,而不是分散在大量 if / switch 与手动解构之中。

    当前已支持的核心能力包括(但不限于):

    • 值模式与字面量匹配
    • 结构化匹配与成员解构
    • 绑定模式与占位符
    • 守卫模式( guard )与自定义谓词约束

    可以参考这里的示例集合来直观看看 Patternia 的实际用法:
    https://github.com/sentomk/patternia/tree/main/samples

    对 variant 等代数式数据结构以及运行时多态的模式匹配已在规划中,后续的稳定版本也将逐步完善穷尽性检查与编译期诊断能力。

    各位好,打扰了,本人想寻找本科( 15 年到 19 年)和硕士( 19 年到 22 年)均就读于川大计算机学院的某位无锡籍学长,想请教一些事情。目前只知道他名字是三个字,已找到他 18 年保研的名单(链接 https://cs.scu.edu.cn/info/1085/6245.htm ),但不清楚是哪个人。如有能帮我指出的,奖励 500 元 RMB ,如能提供他有效联系方式的,额外奖励 300 元 RMB 。以上信息属实,如有知晓相关信息的,请 WeChat MTU4NTg1OTk0Ng==,感谢各位

    DeepStudent

    在 AI 深度融入教育场景的今天,学习者的需求早已不简单满足于在 AI 聊天这一层面。真正高效的学习闭环需要 AI 贯穿全流程:从理解知识、梳理体系,到制作记忆卡片、自动调研出题、批改作文、翻译文献——每一个环节都需要不同类型的 AI 能力协同配合。然而,当前市面上的学习工具大多功能单一,笔记归笔记、对话归对话,数据割裂、流程断裂,难以支撑完整的学习链路。

    DeepStudent 是一款开源的、AI 原生的本地优先学习系统,旨在构建从输入到内化的完整学习闭环。它以 Chat V2 对话引擎为支撑,技能系统为核心驱动,以虚拟文件系统为统一数据底座,将智能对话、知识管理、Anki 制卡、深度调研、学术论文、知识导图、题库练习、翻译工作台、作文批改等多项专业技能整合在一个平台中。全部数据本地存储( SQLite + LanceDB + Blob ),安全可控。

    通过与硅基流动( SiliconFlow )的深度集成,DeepStudent 的全场景 AI 能力获得了坚实的底层支撑。只需一个硅基流动 API Key ,即可一键配齐对话、推理、嵌入、OCR 、翻译、重排序等完整能力矩阵,让每一位学习者都能快速搭建起自己的 AI 学习基础设施。同时,学习者也可以自由接入多种供应商的 API 来实现不同 AI 的自定义搭配。

    1. 安装 DeepStudent

    DeepStudent 基于 Tauri 2 构建,支持 Windows 、macOS 、Android 等主流平台,下载安装包后即可使用。

    软件主页图

    2. 在 DeepStudent 中使用 SiliconFlow 模型

    2.1 获取 SiliconFlow API 密钥

    1. 登录 SiliconFlow ( https://cloud.siliconflow.cn )(首次登录将自动完成注册)。
    2. 在控制台中进入"API 密钥"页面。
    3. 点击"新建密钥",为其命名(如 DeepStudent )。
    4. 复制生成的密钥并妥善保存。

    2.2 在 DeepStudent 中配置

    1. 打开 DeepStudent ,点击左下角进入设置中心。

    2. 找到 SiliconFlow 专属配置区域——DeepStudent 预置了 9 家模型供应商( SiliconFlow / DeepSeek / 通义千问 / 智谱 AI / 字节豆包 / MiniMax / 月之暗面 / OpenAI / Google Gemini ),其中 SiliconFlow 作为首选供应商拥有独立的快速配置界面。

      注册-3

    3. 输入 SiliconFlow API 密钥。

    4. 点击"一键分配"按钮,DeepStudent 将自动创建并分配好全部所需模型:主对话模型( DeepSeek-V3.2 )、Anki 制卡模型( Qwen3-30B-A3B )、嵌入模型( bge-m3 )、重排序模型( bge-reranker-v2-m3 )、翻译模型( Hunyuan-MT-7B )、轻量任务模型( Ling-mini-2.0 )以及 5 个 OCR 引擎( PaddleOCR-VL-1.5 / PaddleOCR-VL / DeepSeek-OCR / GLM-4.6V / Qwen3-VL-8B ),无需逐个手动添加。(推荐手动分配硅基流动的各个 Pro 系列模型以获得更加卓越的性能)

    也可以点击"获取模型列表"从 SiliconFlow 平台拉取全量可用模型,按需选择添加。DeepStudent 会自动推断每个模型的能力属性(多模态、推理、嵌入、工具调用等),并配置最优的适配参数。

    3. 开始使用

    3.1 AI 智能对话与深度推理

    DeepStudent 的 Chat V2 对话引擎专为学习场景打造。搭配 SiliconFlow 提供的 DeepSeek-V3.2 等推理模型,支持多模态文件拖拽上传与自动 ocr (图片、PDF 、Word 等)、深度推理(思维链展示 AI 完整思考过程)、多 Tab 会话、会话分支,以及通过引用面板将知识库中的各种学习资源作为附件注入上下文。

    分组

    pdf 阅读-1

    3.2 技能系统

    DeepStudent 的核心驱动力是其技能系统( Skills )。不同于传统 AI 工具将所有指令塞进一个臃肿的 System Prompt ,DeepStudent 将 AI 能力拆分为独立的技能模块,每个技能封装了特定场景的指令与工具集,激活时才渐进披露,既节省 Token 开销,又保证专业性。目前内置 22 项技能(含 5 个整合型技能与 17 个工具组技能),覆盖制卡、调研、论文、导图、题库、记忆、翻译、导师、文献综述、Office 套件、子代理等核心学习场景,同时支持用户通过 SKILL.md 格式自定义扩展。默认启用的"深度学者"策略会主动回忆用户画像、优先检索本地知识库,让每一次对话都具备个性化上下文。

    技能系统让 AI 如虎添翼,让 AI 对学习数据具有了灵活的读写能力。

    技能系统

    3.3 学习资源中心

    DeepStudent 内置了一个类似 macOS 访达的学习资源管理器( Learning Hub ),统一管理笔记、PDF 教材、题目集、翻译、作文、知识导图、图片等全类型学习资产。支持导入 PDF 、DOCX 、XLSX 、PPTX 、EPUB 、RTF 、Markdown 、CSV 、HTML 、JSON 及 JPG/PNG/HEIC 等常见图片格式。资源导入后自动进入向量化流水线——经由 SiliconFlow 提供的 OCR 引擎( PaddleOCR-VL / DeepSeek-OCR 等)识别文字,再通过嵌入模型( bge-m3 )生成向量索引存入 LanceDB 。向量索引支持多维度管理,用户可创建不同维度的嵌入空间( text / multimodal ),绑定不同的嵌入模型,满足检索的差异化需求
    学习资源管理器

    3.3 深度调研

    激活"深度调研"技能后,AI 会以多步骤 Agent 模式自动执行完整调研流程:明确目标 → 联网搜索(支持 7 种搜索引擎,需配置)→ 本地知识库检索 → 整理分析 → 生成结构化报告,并自动保存为笔记。适用于课题研究、开题报告、学术综述等场景。

    调研-2

    3.4 ChatAnki 智能制卡

    在对话中用自然语言触发制卡技能(如"把这篇文档做成卡片"),AI 批量生成高质量记忆卡片。内置模板设计师支持可视化编辑,生成结果支持 3D 翻转预览,确认无误后一键同步至 Anki ,打通从"理解"到"记忆"的最后一步。

    anki-制卡 2

    3.5 更多学习场景

    • 知识导图:在聊天界面一句话即可生成完整学科知识体系,支持 AI 多轮编辑、大纲/导图视图切换与背诵模式。
      知识导图-4

    • 题库练习:上传教材/试卷,AI 自动提取题目集,支持每日练习、模拟考试与 AI 深度解析。

      题目集-2

    • 翻译工作台:全文翻译与逐段双语对照,内置学术、技术、文学等多领域预设。

      翻译-2

    • 作文批改:覆盖高考、雅思、托福、四六级等考试标准,多维度评分与逐句润色建议。

      作文-1

    • 智能记忆:受 mem0 与 memU 启发设计的自进化用户画像系统,自动从对话中提取事实,越用越懂你。

      记忆

    • MCP 扩展:兼容 Model Context Protocol ,可连接如 Context7 等外部工具服务。

    • 学术论文:通过 arXiv 与 OpenAlex 检索论文,支持批量下载 PDF 、多源自动回退、BibTeX / GB/T 7714 / APA 引用格式化。

      论文搜索-1

      论文搜索-3

    两人团队历时接近一年的作品,我们正行在通往正式版的道路上,仍有许多不足与亟待打磨之处,希望各位不吝指教。