普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月22日iOS

嵌入主线程消息循环的任务调度器

作者 云风
2025年11月22日 13:52

最近在网友协助下把 soluna port 到包括 wasm 在内的非 windows 平台。其间遇到很多难题,大多是多线程环境的问题。因为 soluna 的根基就是基于 ltask 的多线程调度器,如果用单线程实现它,整个项目的意义就几乎不存在,所以它是把项目维护下去必须解决的问题。

好在 lua 有优秀的 coroutine 支持,它可以把运行流程抽象成数据,而 Lua 本身并未限制数据的具体储存方式,所以完全可以存在于内存堆中,脱离于 C 栈存在,这为各种在 C 环境下的多线程难题开了后门。C 语言依赖栈运行代码逻辑,而栈绑定于线程,线程调度通常由操作系统完成,所以用常规方式无法让代码跨线程运行:即,无法通过常规手法让一段代码的流程前半段在一个线程运行,而用另一个线程运行后半段;但是,在 C 上建立一个 Lua 层,则很容易绕开这个限制,只用标准方法就可以自由控制程序运行流程。

上一次发现利用一些技巧就可以完成一些看似不可能却的确可行的调度方式是 多线程串行运行 Lua 虚拟机

简单复述一下当时的需求:

希望可以在单个 Lua 虚拟机内模拟多线程并发。当一个 Lua 的 coroutine 运行到 C 函数中时,若此刻 C 函数希望阻塞等待一个 IO 请求,常规的方法是 yield 回 Lua 虚拟机,让调度器持有一个 Lua coroutine 的状态,待完成 IO 请求后,再由调度器 resume 这个 coroutine 。这样做的难题是,运行到一半的 C 函数,上下文状态还在 C 所属线程的栈中,一旦 yield 回 Lua 虚拟机,必须放弃 C 栈上的状态,并在下次 resume 时可以重建。这通常难以实现,这也是为何 Lua 的 coroutine C api 难以理解又很难使用的原因。尤其使用第三方 C 库,几乎没可能适配。

另一个折中的方法是让 Lua 虚拟机在 C 函数中阻塞,硬等到 IO 操作完成。但在阻塞过程中,无法使用这个 Lua 虚拟机。若使用者期待 Lua 虚拟机中多个 coroutine 以多线程方式并行工作,恐怕会失望。即使其它 coroutine 的业务和 IO 完全无关,一个 IO 阻塞操作会让它完全无法并行工作。

变通的方式是(在编译时)打开 Lua 的线程锁。在调用 IO 阻塞前解开线程锁,只要 IO 操作本身不涉及对 Lua State 的操作,那么 Lua 解释器在调用 C 函数前的那一刻会解开线程锁,这样就可以允许阻塞操作过程中,Lua 虚拟机可以执行其它操作。

线程锁本身依赖系统线程库的调度器。不适合像 ltask 这样自己实现任务调度(即在有限个系统线程下调度远超系统线程数的任务)。但是,我们可以配合 ltask 实现类似的锁机制。这就是之前这个 patch 实现的东西:Lua 层调用可能阻塞的 C 函数前加锁通知 ltask 调度器,在 C 函数中,用户主动在阻塞操作前解锁。ltask 的调度器在 C 函数返回前就将虚拟机提前放回调度表。当阻塞操作完成后,重新加锁会等待调度器完成(如果有)正在运行的在同一个 Lua 虚拟机上的任务完成。这样,整个 Lua 虚拟机实质上还在串行运行其中的任务。而使用者看起来在一个 coroutine 尚未 yield 之前就开始运行另一个 coroutine ,直到其它 coroutine yield 后再继续未完的工作。同一个 Lua 虚拟机的多个 coroutine 是在多个操作系统线程上完成的,但却保持串行。

这个 patch 最终并未合并进 ltask ,因为我觉得它对使用者有更高的要求。但经此,我开了不少脑洞,明白在必要时牺牲一些复杂度就可以完成一些超乎寻常的任务。


这次我面临的是新的问题:sokol 并未设计成线程安全。api 不能并发。一开始我并不想使用复杂的解决方案,以为只要保证 sokol 不并发就够了。期间遇到的问题是 Windows API 死锁 ,也很容易绕过。

对于图形 API ,我只是简单的将图形 API 调用都塞在同一个 render 服务中。并在主线程的 sokol 回调函数中利用一个信号量和渲染过程同步。虽然 Direct3D ,Matal ,Vulkan 这些为多线程设计的底层图形 API 这么用都没有问题,但 OpenGL (在 Linux 上开启)却将状态放在当前线程上。一开始,我们通过额外调用 MakeCurrent 绕开限制,但在我们向 wasm 移植时却遇到障碍。

最终,我还是希望找到一个方法让所有图形 API 的调用都真正从主线程,也就是 sokol 提供的 callback 函数中发起。而不是用信号量同步,让它们在其它工作线程运行。

难题在于,主线程是通过事件消息循环驱动的,没有全部的控制权。不适合在其上实现任务调度器。一个任务调度器最好有所有时间片的控制权,它才好简单有效的分配时间片,没有任务时可以休眠而不是在事件循环没有新事件时强制休眠。我不想为这种特别的工作方式改造 ltask 的任务调度器,让主线程的事件回调函数伪装成一个功能不完整的特殊工作线程。我实际需要的是:把一个 Lua 虚拟机内的特定任务分配给主线程回调函数运行,在没有这种特定任务时,其它任务还是交给 ltask 做常规调度。

细想之下,解决方法和上一个需求有异曲同工之处:Lua 在启动这种特殊任务(必须在主线程回调函数内运行)前通知调度器。这时把虚拟机暂时移出调度表,而在主线程的回调函数中(通过信号量)发现有新任务到来,就接手处理特殊片段。处理完毕后,再把它归还给调度器。

通过这个方案,我们顺利把 soluna port 到 wasm 环境,同时简化了 Linux/OpenGL 实现。当我了解到 wasm 上有 pthread api 和原生 web worker api 两套多线程 api 后,我又信心满满的想用 worker api 来实现。但最终未能如愿。具体讨论在这个 issue 中 ,倒不是完全做不到,而是我觉得不应该牺牲太多复杂度。比如把 soluna 中所有的 IO 操作都转发到主线程中运行(这是 web worker 的限制所在,也是 wasm pthread 原本要解决的问题)。


昨天发现了上面解决方案实施中的一点纰漏:虽然给 ltask 打了个洞,可以在系统主线程夺过指定任务运行,但在交换控制权回调度器时,忽略了 ltask 的所有工作线程可能因为没有任务而全部休眠的可能性。仅仅把任务推回(线程安全的)任务队列是不够的。还需要重启调度器(如果处于休眠状态)。具体讨论见这个 issue

ps. 自从搬家后,我的 Linux 机器一直没有开机。昨天为了在 Linux 环境下测试,才重新装起来。bug 虽然重现,但视乎在我的机器上更为严重:一旦程序失去响应,整个系统都卡住了,甚至冷启动都没用。直接把机器弄死,而且五分钟内都开不了机(BIOS 进不去,屏幕无信号)。我怀疑是显卡驱动的 bug ,因为太久没升级系统,头一次升级还失败了,pacman 报告出现依赖问题拒绝更新。强删了几个 Electron (这个毒瘤)的几个历史版本后,系统升级才得以继续。最后更新了最新版的 Nvidia 包似乎就一切正常了。

昨天 — 2025年11月21日iOS
昨天以前iOS

Apple更新App审核条款,严打擅自与第三方 AI 共享个人数据的应用

作者 JarvanMo
2025年11月19日 09:12

App审核条款变更

最近iOS开发者该都收到了Apple发来了更新审核条款的邮件,原文内容如下:

  • 1.2.1(a): This new guideline specifies that creator apps must provide a way for users to identify content that exceeds the app’s age rating, and use an age restriction mechanism based on verified or declared age to limit access by underage users.
  • 2.5.10: This language has been deleted ("Apps should not be submitted with empty ad banners or test advertisements.”).
  • 3.2.2(ix): Clarified that loan apps may not charge a maximum APR higher than 36%, including costs and fees, and may not require repayment in full in 60 days or less.
  • 4.1(c): This new guideline specifies that you cannot use another developer's icon, brand, or product name in your app's icon or name, without approval from the developer.
  • 4.7: Clarifies that HTML5 and JavaScript mini apps and mini games are in scope of the guideline.
  • 4.7.2: Clarifies that apps offering software not embedded in the binary may not extend or expose native platform APIs or technologies to the software without prior permission from Apple.
  • 4.7.5: Clarifies that apps offering software not embedded in the binary must provide a way for users to identify content that exceeds the app’s age rating, and use an age restriction mechanism based on verified or declared age to limit access by underage users.
  • 5.1.1(ix): Adds crypto exchanges to the list of apps that provide services in highly regulated fields.
  • 5.1.2(i): Clarifies that you must clearly disclose where personal data will be shared with third parties, including with third-party AI, and obtain explicit permission before doing so.

翻译过来就是:

  • 1.2.1(a): 这项新的指南明确规定,创作者应用(creator apps)必须提供一种方式,让用户能够识别超出应用年龄分级的内容,并使用基于验证或声明年龄的年龄限制机制来限制未成年用户访问这些内容。

  • 2.5.10: 此措辞已被删除(原措辞为:“应用不应提交带有空白广告横幅或测试广告。”)。

  • 3.2.2(ix): 澄清了贷款应用收取的最高年利率 (Maximum APR) 不得高于 36% (包括所有成本和费用),并且不得要求在 60 天或更短时间内全额偿还贷款。

  • 4.1(c): 这项新的指南明确规定,未经该开发者批准,您不得在您的应用图标或名称中使用其他开发者的图标、品牌或产品名称

  • 4.7: 澄清了 HTML5 和 JavaScript 小程序(mini apps)和迷你游戏(mini games) 属于该指南的管辖范围。

  • 4.7.2: 澄清了提供未嵌入二进制文件的软件的应用,未经 Apple 事先许可,不得向该软件扩展或暴露原生平台 API 或技术。

  • 4.7.5: 澄清了提供未嵌入二进制文件的软件的应用,必须提供一种方式,让用户能够识别超出应用年龄分级的内容,并使用基于验证或声明年龄的年龄限制机制来限制未成年用户访问这些内容。

  • 5.1.1(ix):加密货币交易所 (crypto exchanges) 加入到提供高度监管服务的应用列表。

  • 5.1.2(i): 澄清了您必须清楚地披露个人数据将与第三方(包括第三方 AI)共享的位置,并在共享之前获得明确的许可

值得一提的是,Apple新规首次明确要求,各类应用若要把用户个人数据提供给第三方 AI,必须事先公开说明并获得用户授权

苹果公司目前正着手调整其 App Store 政策,此举被视为对即将到来的 AI 时代,尤其是对 2026 年即将发布的全新、更智能的 Siri 的战略性准备。

据传闻,下一代 Siri 将具备更强大的跨应用语音操作能力,其部分核心技术据悉将由 Google 的 Gemini 模型提供支持。

政策调整背后的考量

苹果在此时更新开发者指南,主要目标之一是加强对用户隐私的保护,特别是要防止应用程序在用户不知情或未经同意的情况下,将个人数据传输给 AI 服务提供商或其他相关公司。

这次政策修改的关键意义不在于引入了全新的数据保护概念,而在于苹果首次将 AI 相关的企业和技术明确纳入了既有的监管框架

具体变化和影响

原有的审核规则 5.1.2 (i) 已经要求开发者在分享用户数据前必须透明披露并获得用户许可,并禁止未经允许地“使用、传输或分享”个人信息。这一规定是苹果为遵守如欧盟 GDPR、加州 CCPA 等全球隐私法规的重要举措,旨在确保用户对个人数据拥有控制权。违规应用将面临被下架的风险。

新版本在这一要求的基础上加入了更具针对性的明确措辞:开发者必须清楚说明个人数据会被提供给哪些第三方——包括第三方 AI,并且在数据共享操作发生前,必须获取用户的明确授权

这一变化预计将对那些依赖 AI 技术来收集、处理用户数据以提供个性化服务或特定功能的应用程序产生影响。然而,由于“AI”是一个广阔的范畴,既包含大型语言模型(LLM),也涵盖各种机器学习技术,目前尚不清楚苹果将以何种程度和力度去执行这一新要求。

深入 iMessage 底层:一个 Agent 是如何诞生的

作者 Fatbobman
2025年11月19日 22:12

iMessage 深度集成在 Apple 生态中,却从未提供官方 API。本文邀请 imessage-kit 作者 LingJueYa 分享如何突破这一限制,让 AI Agent 进入 iMessage。文章详细介绍了从解析 SQLite 数据库、处理 Core Data 时间戳、绕过 macOS 沙盒限制,到用 AppleScript 实现消息发送的完整技术方案,以及在构建过程中踩过的坑与解决之道。

Swift 一个小型游戏对象模型渐进式设计(四)——类型擦除与 Existential:当泛型遇见动态派发

作者 unravel2025
2025年11月17日 17:57
为什么“泛型”还不够 上一篇我们写出了这样的代码: 它编译得快、跑得也快,但当你想把它存进数组、或者作为属性逃逸到运行时,就会遇到三个灵魂问题: 编译器不知道具体类型有多大,如何分配内存? 协议里有

Swift 一个小型游戏对象模型渐进式设计(三)——把能力再抽象一层,写一套“伤害计算器”框架

作者 unravel2025
2025年11月17日 17:51
为什么要“再抽象一层” 上两篇我们已经用协议把“攻击”拆成了能力插件,但遗留了一个硬核问题: 游戏前期用 Int 足够,后期为了避免除法误差想换成 Double,甚至金融级精度要用 Decimal;

Swift 一个小型游戏对象模型渐进式设计(一)——继承机制解读:从基础类到防止重写

作者 unravel2025
2025年11月17日 17:41
为什么必须有“继承” 在真实世界里,我们习惯把事物归类:车 → 自行车 → 双人自行车。 Swift 的 class 类型允许我们用同样的层级方式建模,把公共的代码放在“上层”,把差异化的代码放在“下
❌
❌