模拟 TLS(线程本地存储)和 TP(线程池)功能
- 来源: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgEmulateTlsTp
- 最后更新: 2025-01-14
如何使用 FreeBASIC 模拟一种 TLS(线程本地存储)和一种 TP(线程池)功能。
前言:
TLS(线程本地存储)
静态变量通常在所有线程之间共享。如果修改一个静态变量,所有线程都能看到该修改。
与普通静态变量不同,如果创建一个 TLS 静态变量,每个线程必须有其自己的变量副本(但使用相同的访问名称),即对变量的任何更改对该线程是局部的(本地存储)。
这允许创建线程安全的过程,因为对此过程的每次调用都获得相同声明的静态变量的其自己的副本。
在具有静态变量的普通过程中,这些变量的内容可以被多个线程更新,但使用 TLS,我们可以将这些视为静态数据,但对每个线程是本地的。
TLS 数据类似于静态数据,但唯一的区别是 TLS 数据对每个线程是唯一的。
TP(线程池)
线程池是一组线程,可根据用户需求用于运行任务。
线程池通过 Type 结构体访问。
创建新线程是一个昂贵的操作,无论是从处理器(CPU)角度还是从内存角度来看。
此外,如果程序需要执行许多任务,为每个任务创建和删除一个线程将大大降低应用程序的性能。
因此,能够共享线程的创建将很有价值,这样完成一个任务的线程就可用于执行未来的任务。
1. 如何使用 FreeBASIC 模拟一种 TLS(线程本地存储)功能
这种 FreeBASIC TLS 模拟的原理是为每个请求的 TLS 变量使用一个静态数组,其中每个线程都有其自己的唯一索引(隐藏)来访问数组元素。
与线程相关的这个唯一索引从线程句柄值推导得出:
对于 fbc 版本 >= 1.08,线程句柄值可以从任何线程中简单地通过调用
Threadself()函数(新函数)返回。对于 fbc 版本 < 1.08,代码更为复杂:
线程句柄值只能在创建时从父(或主)线程的
ThreadCreate()返回值中访问。没有办法正确模拟
Threadself()函数,只能通过一种迂回方法。在下面的示例中(针对 fbc 版本 < 1.08),
Threadself()函数(通过引用返回)的值在每次线程使用之前(用其自己的线程句柄)初始化,所有这些(初始化+使用)都像其对应的ThreadCreate()一样受互斥锁保护。
在下面的示例中,TLS 静态变量是一个整数,用于单一且通用的计数过程(counter(),无传递参数)。此计数过程由每个线程调用(因此每个线程相互独立地计数,但通过调用相同的单一计数过程)。
单个宏允许定义任何类型的任何 TLS 变量(数组除外)。
- 根据 fbc 版本使用预处理器条件指令的代码:
2. 如何使用 FreeBASIC 模拟一种 TP(线程池)功能
线程池的目标是池化线程,以避免不适时地创建或删除线程,从而允许重用它们。
因此,当需要执行一个任务时,检查线程池是否包含可用线程将更有效地利用资源。
如果有,则在任务运行时使用它,然后在任务完成时释放。
如果没有可用线程,可以创建新线程,在任务结束时,该线程将在线程池中可用。
以下首先提出两种 Type 结构体:
这两种结构体使得每个创建的实例使用一个线程,并在此专用线程上一个接一个地链式执行用户过程,但线程在每个过程之间不停止:
ThreadInitThenMultiStart结构体在初始化后需要手动启动(以及手动等待完成),用于在线程中顺序执行的每个用户过程。ThreadPooling结构体允许将一系列用户线程过程提交注册到队列中,同时用户过程开始在线程中执行而无需等待(最后注册的等待命令足以测试完整序列的完成)。
通过创建和使用多个实例,这两种结构体使得在多个线程中执行用户过程序列成为可能,因此并行(时间上)执行。
最后提出一种结构体:
这最后一种结构体是 ThreadPooling 结构体的上层结构,将用户线程过程分派到给定最大数量的辅助线程上。
然后从以下角度比较这 3 种不同的结构体:
- ThreadInitThenMultiStart 类型:
- ThreadPooling 类型:
- ThreadDispatching 类型,ThreadPooling 类型的上层结构,将用户线程过程分派到给定最大数量的辅助线程上:
- 使用 ThreadInitThenMultiStart、ThreadPooling 和 ThreadDispatching 类型检查执行时间增益:
- 通过过程调用方法、基本线程方法或各种线程池方法运行用户任务时浪费的时间:
另请参阅
返回 目录