C++ std::counting_semaphore, std::binary_semaphore

2025 年 2 月 11 日 | 阅读 8 分钟

概述

在 C++20 中,初始库进行了多项改进,在整个编程过程中实现了并发同步,特别是 std::counting_semaphore 和 std::binary_semaphore。 上述每种主要的同步方法都旨在帮助线程集成,同时在整体并发方案中提供一定的优势。

std::counting_semaphore, std::binary_semaphore in C++

std::counting_semaphore 作为一种信号量,它维护着可用资源和许可证总数的匿名记录。知情同意数据库以足够可接受的计数开始,线程可以通过调用 acquire() 来获取特权,从而减少计数,并通过调用 release() 来释放特权,从而增加可能性。如果计数为零,线程将阻塞,直到有特权可用。

另一方面,std::binary_semaphore 是一种专有的信号量类型,仅支持零或一计数,因此充当使用二进制数据的信号量。它适用于需要线程之间进行信号通信的简单场景,包括组织任务的启动或直接实现互斥。这种特定的同步类型通常用作更复杂的同步模式的起点,例如,建立锁或在生产者-消费者系统中的线程之间进行信号通信。

语法

它具有以下语法:

说明

  • std::counting_semaphore: 它初始化时具有任何非负整数的计数。它提供获取或释放多个许可证的方法。支持带有可选超时的非阻塞操作。
  • std::binary_semaphore: 它初始化为可用(true)或不可用(false)。它提供简单的 acquire 和 release 方法。也支持带有超时的非阻塞操作。

std::counting_semaphore 和 std::binary_semaphore 都通过提供更具表现力和灵活性的线程协调管理工具来增强现代 C++ 处理并发的能力。它们融入了 C++20 现代化标准库并为多线程编程提供强大支持的更广泛背景中。

性质

自 C++20 以来,协调原语 std::counting_semaphore 和 std::binary_semaphore 具有许多不同的特性,使其适用于各种并发需求。

1. std::counting_semaphore

std::counting_semaphore 的独特性在于它能够处理可用资源和许可证的数量,因此在许多不同的同步场景中都非常有用。它最初以一个正整数开头,表示可用许可证的数量。线程可以使用两种基本操作:acquire() 和 release() 与信号量进行通信。信号量寄存器已设置为非负整数计数,表示可用许可证的数量。

  • 获取: 当线程调用 acquire() 时,信号量会降低其内部计数。如果允许的总数为零,线程将暂停,直到允许可用。当运行的线程使用 release() 时,信号量会增加其内部计数。如果计数为零,线程将阻塞,直到允许可用。
  • 释放: 当线程调用 release() 时,信号量会增加因此,内部计数可能会唤醒阻塞的线程。
  • 灵活性: 这种特殊的信号量非常适合管理并发访问,例如限制线程访问共享资源或协调对池的访问。

2. std::binary_semaphore

  • 顾名思义,std::binary_semaphore 以二进制状态运行:零或一。这使其更简单但对于信号通信或互斥等特定用例非常有效。
  • 初始化过程: 二进制值的信号量已被分配为值一(可用)和零(不可用)。其二进制性质使其成为信号操作的理想选择。
  • 调用 release() 将内部线程计数减少到一,这使得信号量工具可供其他线程使用。
  • 简单性: 这种信号量可以用于基本的信号通信或互斥,包括限制线程执行到关键部分或在线程之间同步事件。

总而言之,std::counting_semaphore 提供了管理多个许可证和控制对资源的访问的灵活机制,而 std::binary_semaphore 提供了更简单的二进制信号通信机制。两者都是现代 C++ 编程中有效线程管理和同步的重要组成部分。

示例

在 C++20 中,引入了两个新的同步原语:std::counting_semaphore 和 std::binary_semaphore。这些信号量是 <semaphore> 头文件的一部分,并提供了在并发编程中控制对资源的访问的方法。

以下是每种信号量的简要概述

  • std::counting_semaphore: 计数信号量允许固定数量的线程访问共享资源。您可以指定一个初始计数,表示可用资源的数量。线程可以获取或释放资源,信号量将跟踪可用资源。
  • std::binary_semaphore: 二进制信号量是计数信号量的特例,其中计数仅限于 0 或 1。它有效地表现得像一个互斥锁,但具有一些不同的语义。

以下是一个演示如何使用 std::counting_semaphore 和 std::binary_semaphore 的简单示例

std::counting_semaphore 示例

让我们举一个例子来说明 C++ 中的 std::counting_semaphore() 函数。

输出

 
Thread Thread 14 is waiting for a permit.
Thread 4 has acquired a permit.
Thread 3 is waiting for a permit.
Thread 3 has acquired a permit.
Thread 2 is waiting for a permit.
Thread 2 has acquired a permit.
 is waiting for a permit.
Thread 4 is releasing the permit.
Thread 2 is releasing the permit.
Thread 1 has acquired a permit.
Thread 3 is releasing the permit.
Thread 1 is releasing the permit.   

std::binary_semaphore 示例

让我们举一个例子来说明 C++ 中的 std::binary_semaphore() 函数。

输出

 
Thread 1 is waiting for the semaphore.
Thread 1 has acquired the semaphore.
Thread 2 is waiting for the semaphore.
Thread 1 is releasing the semaphore.
Thread 2 has acquired the semaphore.
Thread 2 is releasing the semaphore.   

说明

std::counting_semaphore 示例中,程序演示了多个线程如何与管理固定数量许可证的信号量进行交互。信号量初始化时最多有 3 个许可证,这意味着最多可以有三个线程同时获取一个许可证。

程序创建了四个线程,每个线程都尝试从信号量获取一个许可证。线程 1、2 和 3 由于有三个可用许可证,因此能够立即获取许可证。一旦线程获取了许可证,它将继续模拟一些工作,以睡眠时间表示。完成工作后,线程将许可证释放回信号量,使其可供其他线程使用。

然而,线程 4 必须等待其中一个许可证被释放,因为线程 1、2 和 3 最初占用了所有许可证。一旦其中一个线程释放了许可证,线程 4 就可以获取它并执行其工作。输出显示线程 4 仅在其中一个其他线程完成并释放许可证后才开始其工作。此行为说明了 std::counting_semaphore 如何有效地管理对有限数量资源的访问。

复杂度

C++ 中 std::counting_semaphore 和 std::binary_semaphore 的复杂过程通常与其获取和释放二进制信号量所需的时间成正比,这使得它们成为其性能的关键组成部分。让我们更深入地研究一下。

1. Std::counting_semaphore

在获取过程时,您可以使用 acquire()和 acquire_for() 方法。

时间复杂度: 通常,平均而言,std::counting_semaphore() 函数的时间复杂度为 O(1)。上述命令会终止线程,阻止其继续,如果信号量的数量为零,并在计数为正时恢复。在大多数情况下,有效执行的信号量所涉及的复杂性是连续的时间。尽管如此,在边缘情况或当情况涉及大量反对时,它可能需要更复杂的管理。

使用 Release 操作 (release())。

时间复杂度: O(1)。

部署模拟信号量需要增加字符数,并可能唤醒任何等待的线程。

2. Std::binary_semaphore

在获取操作时,您可以使用 acquire() 和 acquire_for() 方法。通常,时间复杂度为 O(1)。二进制信号量仅包含两个可能的结果(0 或 1);因此,获取它是一个简单的弯曲检查,如果被检查的不是可用的,最终可能会阻塞。由于它只是在验证和可能暂停线程,因此时间和复杂性保持恒定。

Release 操作 (release()) 的时间复杂度为 O(1)。要释放二进制信号量,请将当前状态设置为 1(如果之前为 0),并在必要时唤醒等待的线程。由于 std::counting_semaphore 的原因,该过程本身大约需要恒定的时间。

结论

  • 便利性和效率: 两种信号量都提供了易于理解的 API,通过对常见用例进行有效处理来处理并发。
  • 用例: std::counting_semaphore 对于控制对特定数量资源的访问非常有利,例如限制并发连接或管理工作线程池。std::binary_semaphore 非常适合实现线程之间的信号通信,例如建立生产者-消费者安排或编排任务的完成。
  • 实现和性能: 尽管同步操作保持理论上的不变时间复杂度,但其各自的位置和实际性能取决于实现特性和嵌入到系统中的考虑因素,包括线程调度和争用。

总之,std::counting_semaphore 和 std::binary_semaphore 为现代 C++ 程序中的并发和同步管理提供了强大而高效的工具。它们的恒定时间操作和简单的语义使其在广泛的并发编程场景中具有价值。