1. 目标“线程安全”模型的定义与范围
将 PDFium 这样的重量级 C++ 库从非线程安全改造为线程安全,首要任务是精确定义“线程安全”的目标状态。一个不明确的目标将导致灾难性的架构妥协。
1.1. 线程安全模型的权衡
PDFium 的当前状态是在进程级别上非线程安全的。其全局初始化函数 FPDF_InitLibrary() [1] 及其管理的全局资源,使得任何并发调用都可能导致数据竞争 (Race Condition) [2]。
在改造时,我们面临两种主要模型:
- 完全可重入 (Full Re-entrancy): 允许多个线程同时对同一个
FPDF_DOCUMENT实例执行操作。这要求在库的极深层实现极其复杂的细粒度锁定 [3, 4],极易出错,且可能导致严重的锁竞争。 - 每实例线程安全 (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 类。
- 全局裸指针: 在
core/fxge/cfx_gemodule.cpp中定义了一个全局裸指针CFX_GEModule* g_pGEModule = nullptr;[9]。 - 非原子创建:
CFX_GEModule::Create(...)(由FPDF_InitLibrary调用)直接对这个全局指针进行赋值:g_pGEModule = new CFX_GEModule(...)[9]。 - 全局访问: 代码库中任何地方都可以通过静态方法
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] 来存储原先的全局变量。这是一个技术陷阱,原因如下:
- 生命周期噩梦: 在现代线程池架构中,线程会被复用。
thread_local对象的生命周期与线程绑定,而不是与任务绑定。这会导致状态在一个不相关的任务中“泄漏”,引发灾难性故障。 - 资源浪费: 每个线程都将创建并拥有自己的全套
CFX_GEModule和CFX_FontCache[10]。在一个拥有 128 个线程的服务器上,这将产生 128 个完全独立、不共享的字体缓存,造成巨大的内存浪费和性能下降(缓存命中率暴跌)。 - API 复杂性: API 契约会变得混乱。客户端是否需要在每个线程上都调用
FPDF_InitLibrary()?这是反直觉且容易出错的。
3.2. 提案:FPDF_CONTEXT 作为核心状态载体
我们选择的解决方案是“上下文对象” (Context Object) 模式,这是在 C/C++ 库设计中用于实现线程安全的黄金标准 [20, 21, 22]。
- 定义新句柄: 我们将定义一个新的、公开的、不透明的 C API 句柄:
FPDF_CONTEXT。 - 客户端所有权: API 的调用者(客户端)将负责创建、拥有和销毁这个
FPDF_CONTEXT实例。 - 状态隔离:
FPDF_CONTEXT实例将拥有所有先前是全局的或thread_local的状态。
这种模式赋予客户端完全的控制权:
- 每线程一个上下文: 客户端可以为每个线程创建一个
FPDF_CONTEXT。 - 应用程序共享一个上下文: 客户端可以创建一个
FPDF_CONTEXT并在所有线程中共享(由客户端自己的锁保护)。 - 每会话一个上下文: 客户端可以为每个用户会话创建一个
FPDF_CONTEXT。
这是最灵活、最健壮的架构。
3.3. 所有权与数据流重构
FPDF_CONTEXT 的 C++ 内部实现(例如 PDFiumContext 类)将是重构的核心:
CFX_GEModule的新家:PDFiumContext类将包含一个std::unique_ptr<CFX_GEModule>成员。CFX_GEModule将被重构为一个普通类,其Get()/Create()/g_pGEModule[9] 将被完全删除。CPDF_Document的关联:CPDF_Document[23] 将被修改,增加一个UnownedPtr<PDFiumContext>成员。当客户端调用新的FPDF_LoadDocumentWithContext函数时,该文档实例将存储指向创建它的上下文的指针。缓存的所有权:
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节),这是安全的。
新的数据访问链: 这种设计建立了一个清晰的、自下而上的数据访问链。例如,当渲染
FPDF_PAGE[25] 需要访问字体时:FPDF_PAGE句柄 ->CPDF_Page实例CPDF_Page::GetDocument()[25] ->CPDF_Document*CPDF_Document::GetContext()(新方法) ->PDFiumContext*PDFiumContext::GetGEModule()(新方法) ->CFX_GEModule*CFX_GEModule::GetFontCache()[10] ->CFX_FontCache*CFX_FontCache::GetFont(...)(此函数内部使用base::AutoLock[24] 来安全地访问其map)
这个设计 [20, 22] 完美地实现了我们的目标:线程 A(处理文档 1)和线程 B(处理文档 2)共享同一个 FPDF_CONTEXT,它们可以并行地执行解析和渲染,只有在它们同时访问 CFX_FontCache 的 map 时,才会被 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句柄的函数变体。
- 问题: 全局非线程安全。PDFium 自己的源码树中包含的
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. 文档
上下文级别(共享资源):
由FPDF_CONTEXT拥有且设计为在多个文档之间共享的资源(唯一的例子是CFX_FontCache[10]),必须是内部线程安全的。它们将使用自己的细粒度base::Lock[24] 来保护其内部状态(例如std::map)。这允许了高并发性 [3, 34]。文档级别(独占资源):
FPDF_DOCUMENT[6]、FPDF_PAGE[35] 及其拥有的所有数据(例如CPDF_PageImageCache[15])被明确定义为非线程安全。
5.2. 客户端锁的必要性
在 1.3 节中,我们规定客户端必须锁定文档。这里是技术上的理由:
避免 API 陷阱: 假设我们试图“帮助”客户端,并在
FPDF_GetPageCount和FPDF_LoadPage内部都添加了锁。客户端代码如下:1
2int n = FPDF_GetPageCount(doc);
FPDF_PAGE page = FPDF_LoadPage(doc, n - 1);在
FPDF_GetPageCount调用返回之后和FPDF_LoadPage调用开始之前,另一个线程仍然可以修改文档(例如删除最后一页),导致n-1成为一个无效索引。正确的“事务”: 客户端是唯一知道操作“事务”边界的。正确的、健壮的客户端代码必须如下所示:
1
2
3my_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 模式。
实施方案:
- 执行第 3 节和第 4 节中描述的所有内部重构。
- 创建“遗留 API 垫片 (Legacy Shim)”: 在
fpdf_view.cpp[37] 中,创建一个单一的、静态的、全局的PDFiumContext* g_legacy_context;。 - 此
g_legacy_context必须使用std::call_once[2] 或等效的线程安全机制进行“惰性初始化”。 - 创建一个新的全局锁:
static base::Lock g_legacy_api_mutex;。 - 重写旧 API:
FPDF_InitLibrary()[1] 的新实现是:调用std::call_once来初始化g_legacy_context。FPDF_DestroyLibrary()[11] 的新实现是:调用std::call_once来销毁g_legacy_context(或者在进程退出时依赖操作系统清理)。FPDF_LoadDocument()[38] 的新实现是:1
2base::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 | // 新的上下文配置结构体 (用于传入分配器、V8 Isolate [6] 等) |
关键 API 设计: 只有 LoadDocument 及其变体需要显式接收 FPDF_CONTEXT。CPDF_Document 实例将存储该上下文指针。所有后续操作(如 FPDF_LoadPage、FPDF_RenderPageBitmap [42])的签名保持不变。它们的内部实现将通过传入的 FPDF_PAGE 或 FPDF_DOCUMENT 句柄,间接获取所需的上下文(如 3.3 节所述)。这极大地简化了 API 的演进。
6.3. 阶段三:废弃与迁移
- 在头文件中将所有旧的、不带上下文的 API 函数(如
FPDF_InitLibrary)标记为@deprecated。 - 发布详细的迁移指南 [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_CONTEXT100 次。 - 预期: TSan 报告为零。
- 目的: 测试
测试 2:“共享上下文,不同文档” (黄金路径):
- 目的: 验证我们的核心目标(1.2节)。严格测试共享资源(如
CFX_FontCache[10])的内部锁。 - 方法: 创建一个
FPDF_CONTEXT。启动 50 个线程。每个线程在循环中从语料库中随机选择一个 PDF,使用共享的上下文调用FPDF_LoadDocumentWithContext,加载并渲染一个随机页面,然后关闭文档。 - 预期: TSan 报告为零。
CFX_FontCache中任何遗漏的base::Lock都会在这里立即暴露。
- 目的: 验证我们的核心目标(1.2节)。严格测试共享资源(如
测试 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 等捷径,转而采用更健壮但更复杂的“上下文对象”模式。
成功的关键在于严格执行以下技术建议:
- 全局状态: 必须彻底消除所有可变全局状态。
CFX_GEModule[9] 单例是首要目标。 - API 演进: 必须采用“上下文对象”模式 [20, 40]。引入
FPDF_CreateContext和FPDF_LoadDocumentWithContext[40, 41]。旧 API 必须降级为使用全局锁的“遗留垫片”,以激励迁移。 - 第三方依赖: 必须通过
FPDF_CONTEXT传递cmsContext[27] 来修复lcms2。必须接受openjpeg[33] 带来的性能瓶颈,并对其所有调用进行全局串行化。 - 锁模型: 必须采用双重锁策略:对上下文拥有的共享资源(
CFX_FontCache[10])使用内部细粒度锁 [24],同时明确要求客户端对独占资源(FPDF_DOCUMENT)进行外部锁定。 - 验证: 必须将 TSan [45, 46] 作为质量门禁,并构建一个全新的、专门设计用于触发并发问题的多线程压力测试套件 [48]。
参考资料
- public/fpdfview.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/main/public/fpdfview.h
- PDFium initialization conflict across isolates · Issue #474 · espresso3389/pdfrx - GitHub, accessed November 4, 2025, https://github.com/espresso3389/pdfrx/issues/474
- CS 111 – Spring 2005, accessed November 4, 2025, https://read.seas.harvard.edu/~kohler/class/cs111-s05/notes/notes8.html
- 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
- PDFium thread safety - Google Groups, accessed November 4, 2025, https://groups.google.com/g/pdfium/c/HeZSsM_KEUk
- Getting Started with PDFium, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/HEAD/docs/getting-started.md
- pdfium - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/pdfium
- lifetime problems with self-referential struct · Issue #44 · ajrcarey/pdfium-render - GitHub, accessed November 4, 2025, https://github.com/ajrcarey/pdfium-render/issues/44
- 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
- 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
- PDFium thread safety - Google Groups, accessed November 4, 2025, https://groups.google.com/d/msgid/pdfium/40a6eb27-5318-4bee-afd6-b25531931c1d%40googlegroups.com
- 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
- Pdfium Out-Of-Bounds Read in CPDF_DIB::LoadJpxBitmap [345518608] - Chromium Issue, accessed November 4, 2025, https://issues.chromium.org/issues/345518608
- fpdfsdk/fpdf_editpage.cpp - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/fpdfsdk/fpdf_editpage.cpp
- platform/external/pdfium - Git at Google - Android GoogleSource, accessed November 4, 2025, https://android.googlesource.com/platform/external/pdfium/+/03925281cf25fec70318bf2225356d022b12b566
- 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
- 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/
- 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
- Making global static variables multithread safe - Stack Overflow, accessed November 4, 2025, https://stackoverflow.com/questions/2662575/making-global-static-variables-multithread-safe
- Google C++ Style Guide for Drake - MIT, accessed November 4, 2025, https://drake.mit.edu/styleguide/cppguide.html
- 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/
- Non-Send Futures When? - matklad, accessed November 4, 2025, https://matklad.github.io/2023/12/10/nsfw.html
- 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
- cef_base.h, accessed November 4, 2025, https://magpcss.org/ceforum/apidocs3/projects/(default)/cef_base.h.html
- 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
- pypdfium2-team/pypdfium2: Python bindings to PDFium, reasonably cross-platform. - GitHub, accessed November 4, 2025, https://github.com/pypdfium2-team/pypdfium2
- lcms2.h - PDFium, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/5110c4743751145c4ae1934cd1d83bc6c55bb43f/core/src/fxcodec/lcms2/lcms2-2.6/include/lcms2.h
- Transform in lcms2 - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/lcms2/latest/lcms2/struct.Transform.html
- lcms2 - Rust - Docs.rs, accessed November 4, 2025, https://docs.rs/lcms2
- Re: [Libjpeg-turbo-users] Is the TurboJPEG API thread-safe? - SourceForge, accessed November 4, 2025, https://sourceforge.net/p/libjpeg-turbo/mailman/message/30336128/
- 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
- Documentation / Official Binaries - libjpeg-turbo, accessed November 4, 2025, https://libjpeg-turbo.org/Documentation/OfficialBinaries
- Thread Safety, accessed November 4, 2025, https://galfar.vevb.net/imaging/smf/index.php?topic=840.0
- Implementing a flexible network stack - DTU Informatics, accessed November 4, 2025, https://www2.imm.dtu.dk/pubdb/edoc/imm6627.pdf
- public/fpdf_edit.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/main/public/fpdf_edit.h
- public/fpdfview.h - pdfium - Git at Google, accessed November 4, 2025, https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/4016/public/fpdfview.h
- 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
- 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
- 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
- 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/
- 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
- 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
- API Backwards Compatibility Best Practices | Zuplo Learning Center, accessed November 4, 2025, https://zuplo.com/learning-center/api-versioning-backward-compatibility-best-practices
- ThreadSanitizer: data race detection in practice - Google Research, accessed November 4, 2025, https://research.google.com/pubs/archive/35604.pdf
- Debugging Race Conditions in C++ & C Programming with ThreadSanitizer (TSan), accessed November 4, 2025, https://www.youtube.com/watch?v=SiI3-pJ6MMU
- ThreadSanitizer — Clang 22.0.0git documentation - LLVM, accessed November 4, 2025, https://clang.llvm.org/docs/ThreadSanitizer.html
- ThreadSanitizerCppManual · google/sanitizers Wiki - GitHub, accessed November 4, 2025, https://github.com/google/sanitizers/wiki/threadsanitizercppmanual
- 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
- (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
- Selectively Uniform Concurrency Testing - Dylan Wolff, accessed November 4, 2025, https://dylanjwolff.com/assets/surw.pdf