C 语言 tss_create 函数2025年1月7日 | 阅读 12 分钟 在多线程编程中,控制特定线程的数据有时会有些困难。以前的解决方案(将数据从一个线程传递到另一个线程)包括使用全局变量/“垃圾箱”或线程安全变量,这需要额外的同步时间。这就是 **_线程特定存储 (TSS)_** 适用的场景。 TSS 使每个线程拥有自己的数据,这些数据彼此不同,因此无需同步。回到 C 变量,以这种方式共享数据,每个线程都被分配一个 C 变量,它们只能读写该变量,并且不会相互干扰。这在线程需要状态或上下文敏感数据的情况下特别有用,例如线程特定的日志记录器、随机数生成器或缓存机制。 当使用默认情况下不是线程安全的库或 API 时,TSS 最有用。这些库现在可以使用 TSS 进行状态维护,在涉及多个线程时,这种维护既安全又高效。需要注意的是,封装线程特定状态的能力对于开发高度可靠和高性能的多线程应用程序至关重要。 - C 编程语言在 C11 之后的版本中也包含了一组上面描述的相关线程函数。其中一个函数是 tss_create,它在实际处理线程特定存储时确实非常重要。
- 首先,tss_create 函数旨在创建一个键,该键有助于访问线程特定数据。此键有助于确定与特定线程相关的数据,这些数据在其他线程中不存在。创建键后,其他线程可以将它们的变量链接到此键,并在不干扰其他线程数据的情况下获取键的数据。
- 因此,tss_create 是在 C 语言中使用 TSS 时必须调用的第一个官方函数,它提供了一个每个线程数据槽的标识符,用于给定线程的生命周期。tss_create 创建一个键,然后与其他函数一起使用,例如 tss_set 用于存储数据,tss_get 用于检索数据。从上述定义中可以看出,tss_create 与其他函数结合使用,以存储和检索数据。
- 目前,tss_create 具有使其如此受用户欢迎的优点之一——使用析构函数的机会。当线程被销毁(即线程退出时)时,将调用此析构函数;这允许释放线程特定数据指向的内存。这种自动清理对于防止内存泄漏等问题甚至处理资源至关重要。
函数签名为了完全理解 tss_create 的工作原理,重要的是要查看其函数签名和关联类型:为了完全理解 tss_create 的工作原理,重要的是要查看其函数签名和关联类型 以下是参数的 breakdown:- tss_t *key:这是一个指向 tss_t 类型的指针,其中将存储创建的键。tss_t 是一个不透明类型,用作标识和访问线程特定存储区域的键。此键是为每次 tss_create 调用创建的,并在线程的整个生命周期中永久分配给数据。
- tss_dtor_t destructor:此参数是指向析构函数的指针。析构函数在线程终止时调用,并有助于释放与 TSD 相关的所有资源。如果不需要析构函数,此参数可以设置为 0。
- 返回值:函数返回零表示成功,这意味着键创建成功。如果失败,则生成一个非零错误代码,可以对其进行测试以了解失败原因。
应定义每个参数的用途,以正确利用 tss_create。此函数生成键,此键与其他 TSS 函数一起使用,因此是完成任何线程特定存储操作的骨干。 实际应用让我们考虑一个实际场景 - 假设您正在开发一个多线程应用程序,其中每个线程都需要其单独的随机数生成器。
- 使用 TSS,可以使用命令 tss_create 构建一个键,然后每个线程可以将其随机选择的整数实例链接到此键。
- 通过这种方式,我们让每个线程都使用自己的生成器,这使得不同线程之间的随机数序列是随机且独立的。
- 析构函数可用于在线程销毁时释放随机数生成器的资源,并且不会留下内存泄漏。通过这种方式,将可能实现更好、更安全的多线程,多亏了这一点,您可以在 TSS 中实现给定功能并编写更清晰、更高效的代码。
创建线程特定数据高级全局变量 tss_create 的原理提供了在多线程模式下运行的 C 程序中获取键以进一步访问线程特定数据的机会。此键用于标识包含与特定讨论线程相关的数据的记录。当使用 tss_create 创建键时,每个线程都可以通过利用键来保存自己的数据实例,从而使数据与线程的数据保持区别。 当您调用 tss_create 时,实际上是在创建一个利基,其中每个线程都能够存储其数据。此槽使用由 tss_create 命令创建的键打开。让我们逐步了解此过程的工作原理 - 创建键:如果您决定使用 tss_create,您将获得一个使用 tss_t 类型创建的键。此键是线程特定的,用作指向调用线程的指示数据的令牌。tss_t 类型,您可能已经注意到,是一个不透明类型,这意味着您无法看到其内容。您与它的唯一联系是通过标准库中的函数,以增强抽象和用法。
- 将数据与键关联:如果您有一个键,您可以使用 tss_set 函数将数据存储到此键中,用于特定线程。程序的每个线程都可以使用一些数据调用 tss_set,并且每次执行线程时,这些数据都将存储在进程的 TSS 中,位于键指向的位置。例如
这里,数据可以是线程希望存储的任何数据指针。TSS set 函数确认数据与属于特定线程的键实例一起。 - 使用键检索数据:线程可以使用 tss_get 函数查找与键对应的数据
基本上,此函数返回相同线程先前使用 tss_set 存储的值。这两个调用表明,调用 tss_create 等函数(例如键)对于线程来说是唯一的;也就是说,在另一个线程中将无法使用 tss_get 修改或检索与上述键对应的数据。 - 使用析构函数进行清理: tss_create 的第二个参数是一个析构函数,在线程退出时调用。此函数使您可以执行任何所需的清理活动,例如释放内存甚至关闭线程特定数据的文件句柄。如果返回时无需清理,则可以将析构函数设置为 NULL。
示例 1:管理线程特定缓冲区输出
Thread 140327897642752: Thread 140327897642752 buffer initialized.
Thread 140327889250048: Thread 140327889250048 buffer initialized.
Thread 140327880857344: Thread 140327880857344 buffer initialized.
Buffer destroyed for thread: 140327897642752
Buffer destroyed for thread: 140327889250048
Buffer destroyed for thread: 140327880857344
说明- 键创建:然后使用 tss_create 函数创建以 buffer_key 变量为头的线程特定存储。至于内存分配,提供了析构函数 buffer_destructor,以便在线程完成后可以释放内存。
- 缓冲区初始化:每个线程都使用 initialize_buffer 获取内存空间来存储消息,并在其中存储消息。上述缓冲区在 tss_set 的帮助下链接到特定线程。
- 数据检索:线程使用 tss_get 获取其特定缓冲区,然后将其内容打印到屏幕上。
- 清理:线程终止后,调用 buffer_destructor 函数释放为缓冲区分配的内存。这可以防止内存泄漏。
示例 2:线程特定随机数生成器输出
Thread 140080769615616 generated random number: 45
Thread 140080769615616 generated random number: 12
Thread 140080769615616 generated random number: 86
Thread 140080769615616 generated random number: 27
Thread 140080769615616 generated random number: 34
Thread 140080761222912 generated random number: 77
Thread 140080761222912 generated random number: 4
Thread 140080761222912 generated random number: 59
Thread 140080761222912 generated random number: 31
Thread 140080761222912 generated random number: 92
...
RNG destroyed for thread: 140080769615616
RNG destroyed for thread: 140080761222912
...
说明- 键创建:一个带有析构函数 rng_destructor 的局部键 rng_key 用于线程的随机数的线程特定存储。
- RNG 初始化:为了解决同步问题,每个线程通过调用 initialize_rng 函数创建其 RNG:它以当前时间和线程 ID 作为种子。现在使用 tss_set 函数存储已存储的 RNG。
- 随机数生成:每个字符串都利用一个单独的不可预测的数生成器生成一组随机数。generate_random_number 函数使用 tss_get 获取 RNG,然后使用 rand_r 获取随机数。
- 清理:当每个线程终止时,调用 rng_destructor 函数释放为 RNG 分配的内存,以避免内存泄漏。
使用 tss_create 的优点- 线程特定数据管理: tss_create 的首要优点是它能很好地处理多线程应用程序中与每个线程对应的数据。这有助于避免在同步数据访问时需要合并互斥量和信号量等技术。每个线程都有自己的数据实例,这意味着线程拥有各自独立的数据,它们可以编辑这些数据,而无需与其他线程使用的数据进行交互,从而最大限度地减少竞争条件。
- 自动资源管理: tss_create 函数允许您提供一个析构函数,该函数在线程死亡时调用。此析构函数在您想要释放内存、文件句柄或任何其他特定于该线程的资源时非常有用。自我管理资源可以减少“内存泄漏”的可能性,并且在不再需要资源时立即管理释放资源方面具有影响力。
- 改进代码模块化:由于利用 tss_create,这意味着能够提出更模块化和封装的代码。例如,您可以创建库或模块,其中整个线程特定状态都在库或模块内部处理,程序的其余部分无需知道。这使得代码的可维护性和可重用性更好,因为每个模块的状态不需要与另一个模块同步。
- 性能优势:在线程需要频繁访问数据的情况下,使用 tss_create 提供了性能增益。每个线程都有自己的数据实例这一事实意味着此过程不需要同步,这有时在性能方面可能非常昂贵。这在超实时或实时应用程序中特别有利,其中每个周期计数都很重要。
使用 tss_create 的缺点- 增加内存使用: 调用 tss_create 的另一个缺点是它还可能导致创建新的 TLB,因为它修改了 MBR 中 cr3 位置的值。导致更多的内存使用。这种方法的主要缺点是每个线程都有自己的数据实例,因此内存总使用量随着工作线程数量的增加而增加。这反过来可能在内存资源稀缺的情况下导致性能下降或内存耗尽。
- 有限的便携性: tss_create 函数基于 C11 标准的选项,这些选项可能不受旧编译器或完全不符合 C11 标准的平台支持。这限制了使用 tss_create 创建线程特定存储的代码的便携性,尤其是在旧系统或无法重新编译的环境中进行开发时。
- 管理不当的可能性: 然而,借助 tss_create 简化线程特定数据管理的工作,也可能出现管理不当的情况。因此,如果析构函数不存在或数据传递给 tss_set 不正确,则可能出现资源泄漏或简单的未定义行为。为此,开发人员在处理线程特定数据时需要非常谨慎,以避免在线程生命周期中污染其他相关线程的数据。
- 析构函数管理的开销: 尽管使用析构函数对于清除资源很有利,但它也有一个不希望的副作用,即增加了额外的复杂性。这会影响开发人员验证析构函数是否易于使用,并将释放与线程指定数据相关的所有资源。有趣的是,在具有许多线程和各种资源的复杂应用程序中,管理析构函数变得棘手且容易出错。
结论总之,**_tss_create_** 是 C 语言中用于存储线程特定数据的重要函数,其优点包括同步开销低、资源处理和代码分离。然而,它也存在一些问题,例如 CPU 内存消耗增加、潜在的移植问题以及析构函数使用控制的必要性。尽管它有这些缺点,但由于它通过提供线程局部数据分离机制降低了多线程编程的复杂性,因此开发人员是否选择将其用于特定应用程序取决于他们自己。因此,正确实现 tss_create 并理解其权衡对于在多线程使用中发挥其效率至关重要。
|