【C++ 20 信号量 】C++ 线程同步新特性 C++ 20 std::counting_semaphore 信号量的用法 控制对共享资源的并发访问

本文主要是介绍【C++ 20 信号量 】C++ 线程同步新特性 C++ 20 std::counting_semaphore 信号量的用法 控制对共享资源的并发访问,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录标题

  • 简介
  • counting_semaphore
  • 信号量的值
  • 上限和下限的处理
    • 为零时阻塞,
    • 上限值
  • 其他常用的信号量
    • POSIX 信号量
    • QSemaphore
  • 多种信号量的对比
  • 结语


简介

信号量(Semaphore)是一种轻量级的同步原语,用于限制对共享资源的并发访问。与条件变量相比,在某些情况下,使用信号量可以更高效。

在C++标准库的头文件中,定义了以下两种类型的信号量:

counting_semaphore:这是一个模型非负资源计数的信号量类型。它是一个类模板,可以用于实现具有不同计数值的信号量。
binary_semaphore:这是一个只有两个状态的信号量类型。它是一个类型重载,通常用于实现具有互斥访问共享资源的信号量。
这些信号量类型提供了用于等待、释放和获取资源的方法,具体取决于信号量的类型和操作。使用信号量可以有效地控制并发访问,以避免竞争条件和资源泄漏。


counting_semaphore

std::counting_semaphore是C++20标准库中的一个类模板,它实现了一个计数信号量。信号量是一种同步原语,用于控制对共享资源的并发访问,或者在多个线程之间进行同步。

std::counting_semaphore的主要方法包括:

  • acquire(): 如果信号量的内部计数大于0,那么这个方法会减少计数并立即返回。否则,这个方法会阻塞,直到其他线程调用release()方法增加了计数。

  • try_acquire(): 这个方法尝试减少信号量的计数。如果计数大于0,那么这个方法会减少计数并返回true。否则,这个方法不会阻塞,而是立即返回false

  • release(n = 1): 这个方法增加信号量的计数。参数n指定了要增加的数量,默认为1。如果有其他线程在等待信号量(即它们调用了acquire()方法并被阻塞),那么这个方法会唤醒那些线程。

这是一个简单的使用std::counting_semaphore的例子:

#include <iostream>
#include <thread>
#include <semaphore>std::counting_semaphore<1> sema(0);  // 创建一个初始值为0的信号量void worker() {sema.acquire();  // 等待信号量std::cout << "Hello from worker!\n";
}int main() {std::thread t(worker);  // 创建一个新线程std::cout << "Hello from main!\n";sema.release();  // 增加信号量,唤醒worker线程t.join();  // 等待worker线程结束return 0;
}

在这个例子中,worker线程在开始执行前会等待信号量。main线程在输出一条消息后会增加信号量,从而唤醒worker线程。因此,这个程序总是先输出"Hello from main!“,然后输出"Hello from worker!”。

以下是一个使用std::counting_semaphore的另一个示例代码

#include <thread>
#include <semaphore>
#include <iostream>class MyClass {
public:MyClass() : sem(0) {}  // 初始化信号量为0void sleepThread() {std::cout << "Sleep thread waiting...\n";sem.acquire();  // 如果信号量为0,这将阻塞线程std::cout << "Sleep thread woke up!\n";}void playThread() {std::cout << "Play thread releasing semaphore...\n";sem.release();  // 增加信号量的值,如果有线程在等待,它将被唤醒}private:std::counting_semaphore<1> sem;  // 信号量
};int main() {MyClass myClass;std::thread t1(&MyClass::sleepThread, &myClass);std::thread t2(&MyClass::playThread, &myClass);t1.join();t2.join();return 0;
}

在这个代码中,我们在MyClass中声明了一个std::counting_semaphore<1>成员sem,并在构造函数中将其初始化为0。然后我们在sleepThread方法中调用sem.acquire(),这将阻塞线程直到信号量的值大于0。在playThread方法中,我们调用sem.release()来增加信号量的值,这将唤醒任何正在等待的线程。

main函数中,我们创建了一个MyClass实例,并在两个不同的线程中分别调用sleepThreadplayThread方法。

std::counting_semaphore<1>中的1是模板参数,它表示信号量的最大值。这个参数决定了信号量可以同时允许多少个线程访问共享资源。

在这个例子中,我们使用1作为模板参数,创建了一个二元信号量(binary semaphore)。二元信号量的值只能为0或1,因此它只能允许一个线程访问共享资源。这对于互斥(mutex)操作非常有用,例如当你需要保护对共享资源的访问时。

如果你需要允许多个线程同时访问共享资源,你可以使用一个大于1的值作为模板参数。例如,std::counting_semaphore<3>会创建一个可以同时允许三个线程访问共享资源的信号量。

请注意,这只是一个简单的示例,实际的代码可能需要根据你的具体需求进行修改。

信号量的值

在计算机科学中,信号量(semaphore)是一种用于控制多线程访问共享资源的同步机制。信号量有一个与之关联的计数值,这个计数值表示可以同时访问某个资源的线程数量。

信号量的计数值并不一定只能是0和1。实际上,信号量的计数值可以是任何非负整数。当一个线程尝试获取信号量时,如果信号量的计数值大于0,那么线程可以继续执行,并且信号量的计数值会减1。如果信号量的计数值为0,那么线程会被阻塞,直到信号量的计数值大于0。

当我们说std::counting_semaphore<1>是一个二元信号量,我们是指它的计数值只能是0或1。这意味着它只能允许一个线程访问共享资源。这种类型的信号量通常用于互斥(mutex)操作,即保护对共享资源的访问。

然而,如果我们创建一个std::counting_semaphore<3>,那么它的计数值可以是0,1,2或3。这意味着它可以允许最多三个线程同时访问共享资源。这种类型的信号量可以用于更复杂的同步场景,例如当你需要允许多个线程同时读取某个资源,但只允许一个线程写入该资源时。

所以,信号量的模板参数和初始值决定了可以同时访问共享资源的线程数量。

上限和下限的处理

为零时阻塞,

std::counting_semaphore 中,acquire 方法会尝试减少信号量的值。如果信号量的值大于0,那么 acquire 将成功,并且信号量的值将减1。如果信号量的值为0,那么 acquire 将阻塞,直到信号量的值大于0。

当你调用 semaphore.acquire() 并且信号量的值为0时,线程将会阻塞。如果在此期间,另一个线程调用了 semaphore.release(),那么信号量的值将增加1,原先阻塞的 acquire 操作将继续执行,信号量的值将再次减1,变为0,然后 acquire 操作完成。

所以,当 acquire 操作完成时,信号量的值将是0(假设没有其他线程在此期间改变信号量的值)。如果此时再次调用 acquire,线程将再次阻塞,直到另一个线程调用 release

这就是信号量的基本工作原理:它允许一定数量的线程同时访问某个资源,当资源被全部占用(即信号量的值为0)时,其他线程将被阻塞,直到有资源被释放。

上限值

std::counting_semaphore::release函数会增加信号量的计数。如果信号量的计数已经达到最大值,继续调用release函数会导致未定义行为(undefined behavior)。在C++中,未定义行为意味着程序的行为没有被C++标准规定,可能会导致任何结果,包括程序崩溃、产生错误的结果或者看似正常的行为。因此,你应该避免在信号量已经达到最大值时再次调用release函数。

这是因为std::counting_semaphore的设计目标是用来同步线程,而不是用来做计数器。如果你需要一个可以安全溢出的计数器,你可能需要使用其他的数据结构或者算法。

其他常用的信号量

POSIX 信号量

POSIX 信号量

QSemaphore

Qt框架提供了一个名为QSemaphore的类,它实现了信号量的功能。QSemaphore的使用方式与std::counting_semaphore非常相似,但是它提供了一些额外的功能,例如可以尝试获取信号量而不阻塞线程,或者在一段时间内等待信号量。

以下是一个QSemaphore的基本使用示例:

#include <QSemaphore>
#include <QThread>
#include <QDebug>class MyThread : public QThread
{
public:MyThread(QSemaphore *semaphore) : sem(semaphore) {}void run() override {sem->acquire();qDebug() << "Thread woke up!";}private:QSemaphore *sem;
};int main() {QSemaphore semaphore(0);MyThread thread(&semaphore);thread.start();qDebug() << "Releasing semaphore...";semaphore.release();thread.wait();return 0;
}

在这个示例中,我们创建了一个初始值为0的QSemaphore,然后在一个新线程中调用acquire方法。主线程稍后调用release方法,唤醒新线程。

QSemaphorestd::counting_semaphore的主要区别在于它们的接口和功能。QSemaphore提供了tryAcquire方法,它尝试获取信号量,如果信号量的计数值为0,它将立即返回而不是阻塞线程。此外,QSemaphore还提供了一个可以在一段时间内等待信号量的tryAcquire重载。

另一个区别是QSemaphore没有模板参数,它的计数值可以是任何非负整数,而不仅仅是0和1。这意味着你可以创建一个可以同时允许多个线程访问共享资源的信号量。

最后,QSemaphore是Qt框架的一部分,因此它可以与Qt的其他类和功能(例如信号和槽)一起使用。而std::counting_semaphore是C++标准库的一部分,它与C++的其他特性(例如线程和锁)更好地集成。


多种信号量的对比

以下是一个比较QSemaphorestd::counting_semaphore,POSIX信号量和System V信号量的表格:

QSemaphorestd::counting_semaphorePOSIX SemaphoreSystem V Semaphore
接口提供了高级的、面向对象的接口,如acquire()release()方法提供了类似的接口,但是作为C++标准库的一部分提供了较低级别的、C风格的接口,如sem_wait()sem_post()函数提供了C风格的接口,但是使用了不同的函数,如semop()
上限值可以是任何非负整数可以是任何非负整数可以是任何非负整数可以是任何非负整数
行为如果信号量的值为0,则阻塞调用线程;如果值增加,则解除一个或多个线程的阻塞如果信号量的值为0,则阻塞调用线程;如果值增加,则解除一个或多个线程的阻塞如果信号量的值为0,则阻塞调用线程;如果值增加,则解除一个或多个线程的阻塞如果信号量的值为0,则阻塞调用线程;如果值增加,则解除一个或多个线程的阻塞
底层原理是Qt框架的一部分,使用Qt的线程和同步原语是C++标准库的一部分,使用C++运行时提供的线程和同步原语是Unix API的一部分,使用内核的线程和同步原语是Unix API的一部分,使用内核的线程和同步原语
限制受C++运行时和Qt框架的能力限制受C++运行时的能力限制受Unix内核的能力限制,并且可能在非Unix系统上不可用受Unix内核的能力限制,并且可能在非Unix系统上不可用
以下是对QSemaphorestd::counting_semaphore,POSIX信号量和System V信号量的额外比较:
  1. 平台兼容性std::counting_semaphore是C++标准库的一部分,因此在任何支持C++的平台上都可以使用。QSemaphore是Qt框架的一部分,因此需要Qt库的支持。POSIX信号量和System V信号量是Unix API的一部分,因此在Unix和Unix-like系统(如Linux和macOS)上可用,但在Windows等非Unix系统上可能不可用。

  2. 错误处理std::counting_semaphoreQSemaphore都使用C++的异常处理机制来报告错误。POSIX信号量和System V信号量则使用返回值和errno变量来报告错误。

  3. 超时支持QSemaphore提供了一个可以在一段时间内等待信号量的tryAcquire重载。POSIX信号量也提供了一个可以在一段时间内等待信号量的函数sem_timedwaitstd::counting_semaphore和System V信号量没有内置的超时支持。

  4. 资源管理std::counting_semaphoreQSemaphore都是类,因此可以利用C++的构造函数和析构函数进行资源管理。POSIX信号量和System V信号量是通过函数调用进行管理的,需要手动创建和销毁。

  5. 集成QSemaphore可以与Qt的其他类和功能(例如信号和槽)一起使用。std::counting_semaphore可以与C++的其他特性(例如线程和锁)更好地集成。POSIX信号量和System V信号量可以与Unix API的其他部分一起使用。

  6. 命名信号量:POSIX信号量和System V信号量支持命名信号量,这允许不相关的进程共享信号量。std::counting_semaphoreQSemaphore不支持命名信号量。

在这里插入图片描述

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。


阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页
在这里插入图片描述

这篇关于【C++ 20 信号量 】C++ 线程同步新特性 C++ 20 std::counting_semaphore 信号量的用法 控制对共享资源的并发访问的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/221788

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)