0%

PDFium 线程安全架构改造:详细方案与技术路线图

1. 目标“线程安全”模型的定义与范围

将 PDFium 这样的重量级 C++ 库从非线程安全改造为线程安全,首要任务是精确定义“线程安全”的目标状态。一个不明确的目标将导致灾难性的架构妥协。

1.1. 线程安全模型的权衡

PDFium 的当前状态是在进程级别上非线程安全的。其全局初始化函数 FPDF_InitLibrary() [1] 及其管理的全局资源,使得任何并发调用都可能导致数据竞争 (Race Condition) [2]。

在改造时,我们面临两种主要模型:

  1. 完全可重入 (Full Re-entrancy): 允许多个线程同时对同一个 FPDF_DOCUMENT 实例执行操作。这要求在库的极深层实现极其复杂的细粒度锁定 [3, 4],极易出错,且可能导致严重的锁竞争。
  2. 每实例线程安全 (Per-Instance Thread Safety): 这是我们推荐的目标模型。它保证,只要两个线程操作的是不同FPDF_DOCUMENT 实例,它们就是完全线程安全的。这是在社区讨论中被认为是可行且有价值的模型 [5]。

1.2. 目标架构:每实例线程安全

本方案定义的目标架构是**“每实例线程安全”**。

具体而言,这意味着:

  • 安全场景: 线程 A 处理 document1.pdf,线程 B 同时处理 document2.pdf。即使它们共享某些全局资源(如字体缓存),这种并发操作也必须是安全的。
  • 不安全场景: 线程 A 渲染 document1.pdf 的第 1 页,线程 B 同时渲染 document1.pdf 的第 2 页。

此模型为服务器端应用(例如,并行处理大量不同PDF的Web服务)提供了最大的可伸缩性和价值。

1.3. 关键边界:文档内同步的客户端责任

作为目标架构的必然推论,我们必须设定一个清晰的 API 契约:PDFium 库本身不会保护单个 FPDF_DOCUMENT 实例免受并发访问。

这意味着:

  • 我们不会FPDF_DOCUMENT [6] 级别添加一个粗粒度的互斥锁(Mutex)。
  • API 契约: API 的调用者(客户端)有绝对责任实现自己的锁定机制(例如 std::mutex),以确保任何单个 FPDF_DOCUMENT 实例在任何时候都只被一个线程访问 [7, 8]。

这种设计避免了在库内部实现有缺陷的锁(这无法防止客户端的 “check-then-act” 竞争),并将锁的粒度控制权完全交给最了解业务逻辑的客户端。

2. 深度分析:解构 PDFium 的全局状态架构

PDFium 线程不安全的根源在于其深度依赖的、贯穿整个代码库的全局状态和单例模式。

2.1. FPDF_InitLibrary 的全局初始化问题

公共 API FPDF_InitLibrary() [1] 和 FPDF_InitLibraryWithConfig() [6] 建立了一个进程范围的全局状态。在多组件环境中,这立即导致了初始化冲突和数据竞争 [2]。社区中提出的使用 std::call_once [2] 只是对症状的缓解,而非对根本架构缺陷的修复。

2.2. 核心病灶:非线程安全的单例模式 (CFX_GEModule)

对代码库的深入分析 [9] 揭示了全局状态的真正来源:CFX_GEModule 类。

  1. 全局裸指针:core/fxge/cfx_gemodule.cpp 中定义了一个全局裸指针 CFX_GEModule* g_pGEModule = nullptr; [9]。
  2. 非原子创建: CFX_GEModule::Create(...)(由 FPDF_InitLibrary 调用)直接对这个全局指针进行赋值:g_pGEModule = new CFX_GEModule(...) [9]。
  3. 全局访问: 代码库中任何地方都可以通过静态方法 CFX_GEModule::Get() [9] 来获取这个全局单例。

这是典型的数据竞争:

  • 两个线程同时调用 Create() 会导致内存泄漏或双重初始化。
  • 一个线程调用 Get() 而另一个线程正在调用 Destroy(),会导致“悬空指针”(use-after-free) 或空指针解引用。

更糟糕的是,这个全局 CFX_GEModule 实例 [10] 拥有其他关键的共享资源,特别是 m_pFontCache(字体缓存)[10],从而将全局污染扩散到整个字体和图形引擎 (FXGE) 层。

2.3. 识别其他全局与静态风险点

除了 CFX_GEModule,还存在其他全局和静态变量,它们都是潜在的数据竞争来源:

  • CPDF_ModuleMgr: 如社区所指出 [11],这是一个必须被重构的全局管理器。其身影出现在多个模块中 [12]。
  • 缓存对象: 诸如 CPDF_PageImageCache [13, 14, 15] 和 CPDF_DocPageData [14, 15, 16] 这样的缓存。幸运的是,分析显示它们目前被 CPDF_Document 所拥有 [17]。这在我们的新 API 契约(1.3节)下是安全的——只要客户端锁定了文档,这些缓存就是安全的。
  • 遗留代码: PDFium 拥有悠久历史的遗留代码 [17],很可能存在 C++11 之前的“魔术静态变量”(magic statics) [18]。这种变量的初始化在多线程环境中不是原子的,可能导致全局锁竞争或竞争条件。

2.4. 全局状态与单例分析

为了量化重构工作,下表识别了关键的全局状态问题点及其拟议的解决方案。

表 1:全局状态与单例分析

类 / 变量 位置 (示例) 当前目的 线程安全问题 拟议重构 (见第 3 节)
g_pGEModule core/fxge/cfx_gemodule.cpp [9] CFX_GEModule 的全局单例指针。 经典的全局指针数据竞争。 彻底废除。
CFX_GEModule core/fxge/cfx_gemodule.h [10] 拥有平台和字体系统的单例。 通过 Get() [9] 全局访问。 重构为普通类。实例将由 FPDF_CONTEXT 拥有。
m_pFontCache core/fxge/cfx_gemodule.h [10] CFX_GEModule 拥有,用于缓存字体。 通过 g_pGEModule 全局共享。其内部的 map 不是线程安全的。 将由上下文拥有的 CFX_GEModule 实例拥有。必须为其添加内部 base::Lock 以保护其 map
CPDF_ModuleMgr core/fpdfapi/cpdf_modulemgr.h [11, 12] 管理 PDF API 的全局模块。 假定为全局单例模式。 必须重构为由 FPDF_CONTEXT 拥有。
CPDF_PageImageCache core/fpdfapi/page/cpdf_pageimagecache.h [13, 15] CPDF_Document 拥有,用于缓存页面图像。 当前的每文档设计是可接受的。 保持由 CPDF_Document 拥有。我们的 API 契约 (1.3 节) 保证其安全。

3. 核心架构解决方案:”上下文对象” (Context Object) 模式

要消除全局状态,我们必须引入一种新的机制来承载这些状态。

3.1. 方案评估:为何 thread_local 不足取

一个看似简单的解决方案是使用 thread_local [11, 19] 来存储原先的全局变量。这是一个技术陷阱,原因如下:

  1. 生命周期噩梦: 在现代线程池架构中,线程会被复用。thread_local 对象的生命周期与线程绑定,而不是与任务绑定。这会导致状态在一个不相关的任务中“泄漏”,引发灾难性故障。
  2. 资源浪费: 每个线程都将创建并拥有自己的全套 CFX_GEModuleCFX_FontCache [10]。在一个拥有 128 个线程的服务器上,这将产生 128 个完全独立、不共享的字体缓存,造成巨大的内存浪费和性能下降(缓存命中率暴跌)。
  3. API 复杂性: API 契约会变得混乱。客户端是否需要在每个线程上都调用 FPDF_InitLibrary()?这是反直觉且容易出错的。

3.2. 提案:FPDF_CONTEXT 作为核心状态载体

我们选择的解决方案是“上下文对象” (Context Object) 模式,这是在 C/C++ 库设计中用于实现线程安全的黄金标准 [20, 21, 22]。

  1. 定义新句柄: 我们将定义一个新的、公开的、不透明的 C API 句柄:FPDF_CONTEXT
  2. 客户端所有权: API 的调用者(客户端)将负责创建、拥有和销毁这个 FPDF_CONTEXT 实例。
  3. 状态隔离: FPDF_CONTEXT 实例将拥有所有先前是全局的或 thread_local 的状态。

这种模式赋予客户端完全的控制权:

  • 每线程一个上下文: 客户端可以为每个线程创建一个 FPDF_CONTEXT
  • 应用程序共享一个上下文: 客户端可以创建一个 FPDF_CONTEXT 并在所有线程中共享(由客户端自己的锁保护)。
  • 每会话一个上下文: 客户端可以为每个用户会话创建一个 FPDF_CONTEXT

这是最灵活、最健壮的架构。

3.3. 所有权与数据流重构

FPDF_CONTEXT 的 C++ 内部实现(例如 PDFiumContext 类)将是重构的核心:

  1. CFX_GEModule 的新家: PDFiumContext 类将包含一个 std::unique_ptr<CFX_GEModule> 成员。CFX_GEModule 将被重构为一个普通类,其 Get() / Create() / g_pGEModule [9] 将被完全删除。

  2. CPDF_Document 的关联: CPDF_Document [23] 将被修改,增加一个 UnownedPtr<PDFiumContext> 成员。当客户端调用新的 FPDF_LoadDocumentWithContext 函数时,该文档实例将存储指向创建它的上下文的指针。

  3. 缓存的所有权:

    • CFX_FontCache [10]:它仍被 CFX_GEModule 拥有,而 CFX_GEModule 现在被 PDFiumContext 拥有。这是关键点: 为了允许客户端安全地在线程间共享一个 FPDF_CONTEXT(我们测试计划中的场景 2),CFX_FontCache 本身必须被改造为内部线程安全。它必须使用一个(例如 Chromium 的 base::Lock [24])来保护其内部的 std::map,使其 GetFont()SetFont() 操作是原子的。这是在唯一需要的地方(共享资源)应用细粒度锁定 [3] 的典范。
    • CPDF_PageImageCache [15]:它保持其在 CPDF_Document 内的所有权 [14, 17]。根据我们的 API 契约(1.3节),这是安全的。
  4. 新的数据访问链: 这种设计建立了一个清晰的、自下而上的数据访问链。例如,当渲染 FPDF_PAGE [25] 需要访问字体时:

    1. FPDF_PAGE 句柄 -> CPDF_Page 实例
    2. CPDF_Page::GetDocument() [25] -> CPDF_Document*
    3. CPDF_Document::GetContext() (新方法) -> PDFiumContext*
    4. PDFiumContext::GetGEModule() (新方法) -> CFX_GEModule*
    5. CFX_GEModule::GetFontCache() [10] -> CFX_FontCache*
    6. CFX_FontCache::GetFont(...) (此函数内部使用 base::AutoLock [24] 来安全地访问其 map)

这个设计 [20, 22] 完美地实现了我们的目标:线程 A(处理文档 1)和线程 B(处理文档 2)共享同一个 FPDF_CONTEXT,它们可以并行地执行解析和渲染,只有在它们同时访问 CFX_FontCachemap 时,才会被 base::Lock 短暂地串行化。

4. 第三方依赖库的线程安全审计与修复

我们的“上下文对象”架构是健壮的,但它依赖于一个关键假设:我们的第三方依赖库是可控的。我们不能在不安全的依赖库之上构建安全的系统 [5, 11]。

4.1. 审计框架

我们必须审计 PDFium 使用的所有第三方库 [26]。我们的 FPDF_CONTEXT 是管理那些需要每线程句柄或上下文指针的 C 库的完美工具。

4.2. 审计结果与修复计划

  • 安全库: FreeType (fx_freetype), libpng (fx_lpng), zlib (fx_zlib) 均被报告为线程安全 [11]。ICU 在其设计上对我们的使用模型(不同对象)是安全的 [11]。这些库不需要修改。

  • lcms2 (fx_lcms2):

    • 问题: 全局非线程安全。PDFium 自己的源码树中包含的 lcms2.h [27] 显示了 cmsCreateContext() API。文档 [28, 29] 证实,线程安全要求使用 ThreadContext
    • 修复方案: PDFiumContext 类(FPDF_CONTEXT 的实现)将在其构造函数中调用 cmsCreateContext(),并存储返回的 cmsContext 句柄。所有对 lcms2 的内部调用都必须被重构,以使用需要此 cmsContext 句柄的函数变体。
  • libjpeg-turbo (jpeg):

    • 问题: 库本身在每个 tjhandle 上是线程安全的 [30]。但关键问题是:其全局错误处理函数 tjGetErrorStr() 使用一个静态缓冲区,因此它不是线程安全的 [30, 31, 32]。
    • 修复方案: 我们不能修复 libjpeg-turbo。相反,我们将在 PDFium 内部实现一个包装器,该包装器拥有一个 static base::Lock。任何调用 libjpeg-turbo 函数并需要通过 tjGetErrorStr() 获取错误信息的 PDFium 代码,都必须通过这个包装器,该包装器在 base::AutoLock 的保护下调用 tjGetErrorStr()。这是一个仅在错误路径上发生的、可接受的性能瓶颈。
  • openjpeg (fx_libopenjpeg):

    • 问题: 这是最高风险的依赖项。该库“并非真正的线程安全” [33]。它是“在没有考虑线程安全的情况下设计的” [33],并使用了全局变量。
    • 修复方案: 我们无法控制此库 [5]。维护一个 openjpeg 的“fork”在工程上是不可持续的。唯一安全的解决方案是假定该库 100% 是单线程的。我们将创建一个进程级的静态 base::Lock(例如 g_openjpeg_mutex),并用它来串行化对 openjpeg 库的所有调用(包括编码和解码)。
    • 性能影响: 这将导致在并发处理 JPEG2000 图像时出现严重的性能瓶颈。这是为了在不修改 openjpeg 的情况下获得正确性而必须付出的代价。此决策必须在文档中明确声明。

4.3. 第三方依赖库线程安全审计

表 2:第三方依赖库线程安全审计与修复计划

报告的线程安全性 识别的问题 修复计划
FreeType 是 [11] 无需操作。
ICU 是(有条件)[11] 对我们的模型安全。 无需操作。
lcms2 否(全局)[27] 线程安全需要 cmsContext [28]。 FPDF_CONTEXT 将拥有一个 cmsContext 实例,并将其传递给所有 lcms2 调用。
libjpeg-turbo 部分 [30] tjGetErrorStr() 使用全局静态缓冲区 [31]。 仅对错误处理调用使用全局静态 base::Lock
openjpeg 否 [33] 根本上非线程安全;使用全局变量。 所有 openjpeg 调用使用全局静态 base::Lock。串行化所有 JPEG2000 处理。
libpng, zlib 是 [11] 无需操作。

5. 内部并发:锁策略的演进

基于上述分析,我们最终的锁策略是一个清晰的两层模型。

5.1. 锁模型澄清:上下文 vs. 文档

  1. 上下文级别(共享资源):
    FPDF_CONTEXT 拥有且设计为在多个文档之间共享的资源(唯一的例子是 CFX_FontCache [10]),必须是内部线程安全的。它们将使用自己的细粒度 base::Lock [24] 来保护其内部状态(例如 std::map)。这允许了高并发性 [3, 34]。

  2. 文档级别(独占资源):
    FPDF_DOCUMENT [6]、FPDF_PAGE [35] 及其拥有的所有数据(例如 CPDF_PageImageCache [15])被明确定义为非线程安全

5.2. 客户端锁的必要性

在 1.3 节中,我们规定客户端必须锁定文档。这里是技术上的理由:

  • 避免 API 陷阱: 假设我们试图“帮助”客户端,并在 FPDF_GetPageCountFPDF_LoadPage 内部都添加了锁。客户端代码如下:

    1
    2
    int n = FPDF_GetPageCount(doc);
    FPDF_PAGE page = FPDF_LoadPage(doc, n - 1);

    FPDF_GetPageCount 调用返回之后FPDF_LoadPage 调用开始之前,另一个线程仍然可以修改文档(例如删除最后一页),导致 n-1 成为一个无效索引。

  • 正确的“事务”: 客户端是唯一知道操作“事务”边界的。正确的、健壮的客户端代码必须如下所示:

    1
    2
    3
    my_mutex.lock();
    int n = FPDF_GetPageCount(doc);
    FPDF_PAGE page = FPDF_LoadPage(doc, n - 1);

… // 渲染页面
FPDF_ClosePage(page);
my_mutex.unlock();
```
我们的 API 契约强制客户端采用这种正确的设计,而不是提供一种具有误导性的、虚假的“安全感”。

6. 分阶段实施与 API 演进路线图

如此大规模的架构更改必须分阶段进行,以保持向后兼容性,并为客户端提供清晰的迁移路径。

6.1. 阶段一:内部重构(”大爆炸”式改造)

目标:修改任何 public/ 目录下的头文件 [36] 的前提下,重构所有内部代码以使用新的 PDFiumContext 模式。

实施方案:

  1. 执行第 3 节和第 4 节中描述的所有内部重构。
  2. 创建“遗留 API 垫片 (Legacy Shim)”:fpdf_view.cpp [37] 中,创建一个单一的、静态的、全局的 PDFiumContext* g_legacy_context;
  3. g_legacy_context 必须使用 std::call_once [2] 或等效的线程安全机制进行“惰性初始化”。
  4. 创建一个新的全局锁:static base::Lock g_legacy_api_mutex;
  5. 重写旧 API:
    • FPDF_InitLibrary() [1] 的新实现是:调用 std::call_once 来初始化 g_legacy_context
    • FPDF_DestroyLibrary() [11] 的新实现是:调用 std::call_once 来销毁 g_legacy_context(或者在进程退出时依赖操作系统清理)。
    • FPDF_LoadDocument() [38] 的新实现是:
      1
      2
      base::AutoLock lock(g_legacy_api_mutex);
      return FPDF_LoadDocumentWithContext(g_legacy_context,...);
    • 所有其他旧的 API(如 FPDF_RenderPage [1])都必须以 base::AutoLock lock(g_legacy_api_mutex); 开始。

结果: 内部代码库已完全重构。旧的 API 仍然可以编译和运行(保持 ABI/API 兼容性),但现在它的所有操作都被 g_legacy_api_mutex 完全串行化了。这为不迁移的客户端提供了正确性(无数据竞争),但代价是巨大的性能惩罚,从而强烈激励他们迁移到新 API。

6.2. 阶段二:引入新的公共 API

目标: 引入新的、高性能的、线程安全的 API。这是C API演进的标准实践 [39, 40, 41]。

实施方案:public/fpdfview.h [6, 36] 等头文件中添加新函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 新的上下文配置结构体 (用于传入分配器、V8 Isolate [6] 等)
typedef struct {... } FPDF_CONTEXT_CONFIG;

// 创建和销毁上下文
FPDF_EXPORT FPDF_CONTEXT FPDF_CALLCONV FPDF_CreateContext(FPDF_CONTEXT_CONFIG* config);
FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyContext(FPDF_CONTEXT context);

// 使用上下文加载文档
FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_LoadDocumentWithContext(
FPDF_CONTEXT context,
FPDF_FILEACCESS* pFileAccess,
FPDF_BYTESTRING password);

//... (以及 FPDF_LoadMemDocumentWithContext 等变体)

关键 API 设计: 只有 LoadDocument 及其变体需要显式接收 FPDF_CONTEXTCPDF_Document 实例将存储该上下文指针。所有后续操作(如 FPDF_LoadPageFPDF_RenderPageBitmap [42])的签名保持不变。它们的内部实现将通过传入的 FPDF_PAGEFPDF_DOCUMENT 句柄,间接获取所需的上下文(如 3.3 节所述)。这极大地简化了 API 的演进。

6.3. 阶段三:废弃与迁移

  1. 在头文件中将所有旧的、不带上下文的 API 函数(如 FPDF_InitLibrary)标记为 @deprecated
  2. 发布详细的迁移指南 [43],解释新的线程安全模型(1.3节)、新的 API(6.2节)以及不迁移的性能后果(g_legacy_api_mutex 串行化)。

6.4. API 演进对比

表 3:API 演进:遗留 vs. 线程安全

遗留函数 (fpdfview.h) 新的线程安全等价物 遗留函数的(重构后)实现
FPDF_InitLibrary() [1] FPDF_CONTEXT ctx = FPDF_CreateContext(config); std::call_once(InitGlobalLegacyContext);
FPDF_DestroyLibrary() [11] FPDF_DestroyContext(ctx); (依赖 g_legacy_context 的析构)
FPDF_LoadDocument(...) [6, 38] FPDF_LoadDocumentWithContext(ctx,...) base::AutoLock(g_legacy_api_mutex);
return FPDF_LoadDocumentWithContext(g_legacy_context,...);
FPDF_RenderPageBitmap(...) [42] FPDF_RenderPageBitmap(...) (签名不变) base::AutoLock(g_legacy_api_mutex);
// (内部实现从 FPDF_PAGE 获取上下文)
所有其他 API (签名不变) base::AutoLock(g_legacy_api_mutex);
// (内部实现从句柄获取上下文)

7. 验证策略:TSan 驱动的并发测试

并发错误是出了名的难以检测和复现 [44]。没有严格的验证策略,此方案将毫无价值。

7.1. 核心工具:ThreadSanitizer (TSan)

我们的主要验证工具必须是 ThreadSanitizer (TSan) [45, 46]。

  • TSan 是一种动态分析工具,它能在运行时检测实际发生的数据竞争 [44, 47]。
  • 所有 PDFium 测试套件必须在 CI(持续集成)上使用 -fsanitize=thread [46] 进行编译和运行。这是现代多线程系统开发的标准做法 [48]。

7.2. 构建新的多线程压力测试套件

PDFium 的现有测试可能主要是单线程的。我们必须创建一套新的、专门用于触发并发问题的多线程压力测试 [48, 49, 50]。此套件将使用第 6.2 节中的新 API。

  • 测试 1:“上下文流失” (Context Churn):

    • 目的: 测试 FPDF_CreateContext / FPDF_DestroyContext 的数据竞争。
    • 方法: 启动 50 个线程。每个线程在循环中创建和销毁 FPDF_CONTEXT 100 次。
    • 预期: TSan 报告为零。
  • 测试 2:“共享上下文,不同文档” (黄金路径):

    • 目的: 验证我们的核心目标(1.2节)。严格测试共享资源(如 CFX_FontCache [10])的内部锁。
    • 方法: 创建一个 FPDF_CONTEXT。启动 50 个线程。每个线程在循环中从语料库中随机选择一个 PDF,使用共享的上下文调用 FPDF_LoadDocumentWithContext,加载并渲染一个随机页面,然后关闭文档。
    • 预期: TSan 报告为零。CFX_FontCache 中任何遗漏的 base::Lock 都会在这里立即暴露。
  • 测试 3:“客户端锁(负面测试)”:

    • 目的: 验证我们的 API 契约(1.3节)——即客户端必须加锁。
    • 方法: 创建一个上下文并加载一个 FPDF_DOCUMENT。启动 10 个线程。所有线程同时对这同一个文档句柄调用 FPDF_RenderPageBitmap使用任何外部锁。
    • 预期: 此测试必须失败——无论是崩溃、ASan 报告 [13]、TSan 报告,还是产生损坏的图像。这证明了我们的 API 契约是必要的。
  • 测试 4:“客户端锁(正面测试)”:

    • 目的: 证明我们的客户端锁模型(1.3节)是可行且正确的。
    • 方法: 与测试 3 相同,但测试工具在所有 10 个线程的 FPDF_RenderPageBitmap 调用外围包裹一个 std::mutex
    • 预期: 测试必须 cleanly 通过,TSan 报告为零。

7.3. CI 集成与质量门禁

  • 所有 TSan 启用的构建和测试必须在 CI 中运行。
  • 来自 TSan [44] 的任何数据竞争报告都必须自动导致构建失败。零容忍。

8. 结论与核心技术建议

此方案提出了一个将 PDFium 从进程级非线程安全转变为健壮的、每实例线程安全的库的完整路线图。该计划的核心是放弃 thread_local 等捷径,转而采用更健壮但更复杂的“上下文对象”模式。

成功的关键在于严格执行以下技术建议:

  1. 全局状态: 必须彻底消除所有可变全局状态。CFX_GEModule [9] 单例是首要目标。
  2. API 演进: 必须采用“上下文对象”模式 [20, 40]。引入 FPDF_CreateContextFPDF_LoadDocumentWithContext [40, 41]。旧 API 必须降级为使用全局锁的“遗留垫片”,以激励迁移。
  3. 第三方依赖: 必须通过 FPDF_CONTEXT 传递 cmsContext [27] 来修复 lcms2。必须接受 openjpeg [33] 带来的性能瓶颈,并对其所有调用进行全局串行化。
  4. 锁模型: 必须采用双重锁策略:对上下文拥有的共享资源(CFX_FontCache [10])使用内部细粒度锁 [24],同时明确要求客户端对独占资源(FPDF_DOCUMENT)进行外部锁定。
  5. 验证: 必须将 TSan [45, 46] 作为质量门禁,并构建一个全新的、专门设计用于触发并发问题的多线程压力测试套件 [48]。

参考资料

  1. public/fpdfview.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/main/public/fpdfview.h
  2. PDFium initialization conflict across isolates · Issue #474 · espresso3389/pdfrx - GitHub, accessed November 4, 2025, https://github.com/espresso3389/pdfrx/issues/474
  3. CS 111 – Spring 2005, accessed November 4, 2025, https://read.seas.harvard.edu/~kohler/class/cs111-s05/notes/notes8.html
  4. Performance Impact of Memory Ordering in Concurrent C++ - ULB : Dok - Universität Innsbruck, accessed November 4, 2025, https://ulb-dok.uibk.ac.at/download/pdf/10046094.pdf
  5. PDFium thread safety - Google Groups, accessed November 4, 2025, https://groups.google.com/g/pdfium/c/HeZSsM_KEUk
  6. Getting Started with PDFium, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/HEAD/docs/getting-started.md
  7. pdfium - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/pdfium
  8. lifetime problems with self-referential struct · Issue #44 · ajrcarey/pdfium-render - GitHub, accessed November 4, 2025, https://github.com/ajrcarey/pdfium-render/issues/44
  9. core/fxge/cfx_gemodule.cpp - pdfium.git - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium.git/+/refs/heads/chromium/4182/core/fxge/cfx_gemodule.cpp
  10. core/fxge/cfx_gemodule.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/6173/core/fxge/cfx_gemodule.h
  11. PDFium thread safety - Google Groups, accessed November 4, 2025, https://groups.google.com/d/msgid/pdfium/40a6eb27-5318-4bee-afd6-b25531931c1d%40googlegroups.com
  12. core/fpdfapi/page/cpdf_streamparser.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/2964/core/fpdfapi/page/cpdf_streamparser.cpp
  13. Pdfium Out-Of-Bounds Read in CPDF_DIB::LoadJpxBitmap [345518608] - Chromium Issue, accessed November 4, 2025, https://issues.chromium.org/issues/345518608
  14. fpdfsdk/fpdf_editpage.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/fpdfsdk/fpdf_editpage.cpp
  15. platform/external/pdfium - Git at Google - Android GoogleSource, accessed November 4, 2025, https://android.googlesource.com/platform/external/pdfium/+/03925281cf25fec70318bf2225356d022b12b566
  16. core/fpdfdoc/cpdf_formfield.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/core/fpdfdoc/cpdf_formfield.cpp
  17. Diff - bc260fd7e338f266d4e41e979c57c100ae9661b6^1..bc260fd7e338f266d4e41e979c57c100ae9661b6 - platform/external/pdfium - Git at Google - Android GoogleSource, accessed November 4, 2025, https://android.googlesource.com/platform/external/pdfium/+/bc260fd7e338f266d4e41e979c57c100ae9661b6%5E1..bc260fd7e338f266d4e41e979c57c100ae9661b6/
  18. Are function static variables thread-safe in GCC? - Stack Overflow, accessed November 4, 2025, https://stackoverflow.com/questions/1270927/are-function-static-variables-thread-safe-in-gcc
  19. Making global static variables multithread safe - Stack Overflow, accessed November 4, 2025, https://stackoverflow.com/questions/2662575/making-global-static-variables-multithread-safe
  20. Google C++ Style Guide for Drake - MIT, accessed November 4, 2025, https://drake.mit.edu/styleguide/cppguide.html
  21. C++ Development – Basics, Guidelines and Best Practices - For the love of challenges, accessed November 4, 2025, https://lionadi.wordpress.com/kiss-software-development-guide/programming-languages/c-development-basics-guidelines-and-best-practices/
  22. Non-Send Futures When? - matklad, accessed November 4, 2025, https://matklad.github.io/2023/12/10/nsfw.html
  23. core/fpdfapi/parser/cpdf_document.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/core/fpdfapi/parser/cpdf_document.cpp
  24. cef_base.h, accessed November 4, 2025, https://magpcss.org/ceforum/apidocs3/projects/(default)/cef_base.h.html
  25. core/fpdfapi/page/cpdf_page.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/core/fpdfapi/page/cpdf_page.h
  26. pypdfium2-team/pypdfium2: Python bindings to PDFium, reasonably cross-platform. - GitHub, accessed November 4, 2025, https://github.com/pypdfium2-team/pypdfium2
  27. lcms2.h - PDFium, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/5110c4743751145c4ae1934cd1d83bc6c55bb43f/core/src/fxcodec/lcms2/lcms2-2.6/include/lcms2.h
  28. Transform in lcms2 - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/lcms2/latest/lcms2/struct.Transform.html
  29. lcms2 - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/lcms2
  30. Re: [Libjpeg-turbo-users] Is the TurboJPEG API thread-safe? - SourceForge, accessed November 4, 2025, https://sourceforge.net/p/libjpeg-turbo/mailman/message/30336128/
  31. It is not possible to safely use libjpeg-turbo in multithreaded application through the turbo API #396 - GitHub, accessed November 4, 2025, https://github.com/libjpeg-turbo/libjpeg-turbo/issues/396
  32. Documentation / Official Binaries - libjpeg-turbo, accessed November 4, 2025, https://libjpeg-turbo.org/Documentation/OfficialBinaries
  33. Thread Safety, accessed November 4, 2025, https://galfar.vevb.net/imaging/smf/index.php?topic=840.0
  34. Implementing a flexible network stack - DTU Informatics, accessed November 4, 2025, https://www2.imm.dtu.dk/pubdb/edoc/imm6627.pdf
  35. public/fpdf_edit.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/public/fpdf_edit.h
  36. public/fpdfview.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/4016/public/fpdfview.h
  37. fpdfsdk/fpdf_view.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/4542/fpdfsdk/fpdf_view.cpp
  38. ajrcarey/pdfium-render: A high-level idiomatic Rust wrapper around Pdfium, the C++ PDF library used by the Google Chromium project. - GitHub, accessed November 4, 2025, https://github.com/ajrcarey/pdfium-render
  39. Standard Co-Emulation Modeling Interface (SCE-MI) Reference Manual Version 2.2 January 20 - Accellera Systems Initiative, accessed November 4, 2025, https://www.accellera.org/images/downloads/standards/sce-mi/SCE-MI_v22-140120-final.pdf
  40. 4.4. Using CVODE for IVP Solution - SUNDIALS documentation - Read the Docs, accessed November 4, 2025, https://sundials.readthedocs.io/en/v6.1.1/cvode/Usage/
  41. Pro*C/C++ - Developer’s Guide - Oracle Help Center, accessed November 4, 2025, https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpcc/c-c-developers-guide.pdf
  42. fpdfview.h - Android Code Search, accessed November 4, 2025, https://cs.android.com/android/platform/superproject/+/master:external/pdfium/public/fpdfview.h;l=710-711;drc=master;bpv=1;bpt=1
  43. API Backwards Compatibility Best Practices | Zuplo Learning Center, accessed November 4, 2025, https://zuplo.com/learning-center/api-versioning-backward-compatibility-best-practices
  44. ThreadSanitizer: data race detection in practice - Google Research, accessed November 4, 2025, https://research.google.com/pubs/archive/35604.pdf
  45. Debugging Race Conditions in C++ & C Programming with ThreadSanitizer (TSan), accessed November 4, 2025, https://www.youtube.com/watch?v=SiI3-pJ6MMU
  46. ThreadSanitizer — Clang 22.0.0git documentation - LLVM, accessed November 4, 2025, https://clang.llvm.org/docs/ThreadSanitizer.html
  47. ThreadSanitizerCppManual · google/sanitizers Wiki - GitHub, accessed November 4, 2025, https://github.com/google/sanitizers/wiki/threadsanitizercppmanual
  48. Designing a Browser to Benefit from Multi-core Silicon | Ekioh, accessed November 4, 2025, https://www.ekioh.com/wp-content/uploads/Designing-a-Browser-to-Benefit-from-Multi-core-Silicon.pdf
  49. (PDF) DataRaceBench: a benchmark suite for systematic evaluation of data race detection tools - ResearchGate, accessed November 4, 2025, https://www.researchgate.net/publication/320954178_DataRaceBench_a_benchmark_suite_for_systematic_evaluation_of_data_race_detection_tools
  50. Selectively Uniform Concurrency Testing - Dylan Wolff, accessed November 4, 2025, https://dylanjwolff.com/assets/surw.pdf
Buy me a coffee if you found this useful ☕