【多线程】七、POSIX信号量 环形队列的生产者消费者模型

文章目录

  • Ⅰ. 信号量
    • 一、POSIX 信号量的概念
    • 二、POSIX 信号量的类型区别
    • 三、POSIX 信号量与 SystemV 信号量的区别
  • Ⅱ. 线程信号量基本原理
    • 一、为什么要引入信号量❓
    • 二、PV 操作
    • 三、POSIX 信号量的实现原理
    • 四、CAS操作介绍
  • Ⅲ. POSIX未命名信号量接口
    • 一、初始化无名信号量
    • 二、销毁无名信号量
    • 三、阻塞减少信号量(相当于P操作)
    • 四、非阻塞减少信号量(相当于P操作)
    • 五、增加信号量(相当于V操作)
    • 六、读取当前信号量值
    • 测试代码:利用信号量实现互斥锁功能
  • Ⅳ. 基于环形队列的生产者消费者模型
    • 一、环形队列的应用场景
    • 二、基于环形队列的生产者消费者模型的实现
      • ① 无保存者版本
      • ② 有保存者版本
  • Ⅳ. 效率高在哪里呢❓❓❓

在这里插入图片描述

Ⅰ. 信号量

一、POSIX 信号量的概念

POSIX信号量是一种进程间通信机制,它允许进程在共享资源上进行同步和互斥访问。POSIX信号量是一种计数信号量,它可以被多个进程共享,并且可以通过系统调用进行控制。POSIX 信号量是一种强大的进程间通信机制,它可以帮助程序员实现复杂的同步和互斥操作,确保多个进程之间共享资源的正确性和安全性。

​ 信号量通常用来协调对资源的访问,其中信号计数会初始化为可用资源的数目。然后线程在资源增加时会增加计数,在删除资源时会减小计数这些操作都以原子方式执行的!如果信号计数变为零,则表明已无可用资源。计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止。

POSIX 信号量具有以下特点:

  • 可以被多个进程共享,或者被同一进程中的线程共享。
  • 可以被用来进行同步和互斥访问共享资源。
  • 具有 原子性的计数功能,允许多个进程或者线程同时访问资源。

信号量就好比电影院的票数,而我们去买票的操作就是下面会讲的 P 操作,退票的操作就是 V 操作,这些操作的过程中是原子性的,并且对于电影票数,我们是可以提前得知的,也就意味着我们不需要提前加锁去判断,因为对电影票的操作是原子性的,所以不需要关心线程安全的问题!其实这都利用了一些技术比如 CASCompare-And-Swap 等,这些下面会介绍到!

二、POSIX 信号量的类型区别

POSIX信号量有两种类型:命名信号量未命名信号量。它们有以下区别:

  1. 命名方式不同:命名信号量通过一个字符串名字来标识,可以被多个进程共享,而未命名信号量只能被同一进程内的线程共享,不需要名字。
  2. 创建方式不同:创建命名信号量时需要使用 sem_open() 函数,创建未命名信号量时需要使用 sem_init() 函数。
  3. 销毁方式不同:销毁命名信号量时需要使用 sem_unlink() 函数,销毁未命名信号量时需要使用 sem_destroy() 函数。
  4. 访问权限不同:命名信号量可以通过文件系统的权限机制来控制对其的访问权限,而对于未命名信号量,访问权限只能通过进程间的 UIDGID 来控制。
  5. 在系统中的存储位置不同:命名信号量被存储在文件系统中的一个特殊目录中,而未命名信号量被存储在进程的地址空间中。

​ 其中最重要的区别就是 命名信号量通过一个字符串名字来标识,可以被多个进程共享未命名信号量只能被同一进程内的线程共享。而接下来我们主要学习的是未命名信号量,因为其可以帮助我们实现同一进程内的线程共享,达到多线程编程目的!

三、POSIX 信号量与 SystemV 信号量的区别

POSIX 信号量和 SystemV 信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的,但POSIX可以用于线程间同步。除此之外,还有一些其它的区别,下面列举出来:

  1. 编程接口不同: POSIX 信号量使用函数 sem_open()sem_close()sem_wait()sem_post() 等来实现,而 SystemV 信号量使用函数 semget()semctl()semop() 等来实现。
  2. 信号量命名方式不同: POSIX 信号量采用类似于文件路径的命名方式,而 SystemV 信号量采用整数 key 值的命名方式。
  3. 信号量数量不同: POSIX 信号量可以创建多个信号量,而 SystemV 信号量只有一组信号量。
  4. 处理进程终止的方式不同: POSIX 信号量可以通过 sem_close()sem_unlink() 函数来释放信号量资源,而 SystemV 信号量需要使用 semctl() 函数来进行释放。
  5. 信号量操作的方式不同: POSIX 信号量的操作更加简洁明了,使用 sem_wait()sem_post() 两个函数即可,而 SystemV 信号量需要通过 semop() 函数来实现信号量操作。

​ 总之, POSIX 信号量和 SystemV 信号量在使用方式、命名方式、数量、释放方式和操作方式等方面存在一些不同,程序员在选择使用哪一种信号量时需要根据具体的情况来决定。

Ⅱ. 线程信号量基本原理

一、为什么要引入信号量❓

​ 在引入一种新方法之前,肯定是因为我们之前使用的一些方法比如说条件变量与互斥量,存在一些短板,所以我们通过新方法可以解决这些存在的问题!

​ 还记得我们之前学条件变量的时候,为了实现互斥和同步,我们需要这样子使用互斥量和条件变量:

// 伪代码:
pthread_mutex_lock(&mutex);
while(condition_is_false)
{pthread_cond_wait(&cond, &mutex);
}
// 操作临界资源
// 修改条件
pthread_mutex_unlock(&mutex);

​ 上述代码中,我们每次要去操作临界资源的时候,必须先上锁,因为我们不知道条件变量是否满足,如果不提前上锁就去访问条件变量和临界资源的话,就有很大可能导致线程不安全的问题,而我们也无法在条件变量已有的资源情况下去解决这个问题。

​ 所以对于条件变量的使用每次我们就得上锁,但是我们知道,上锁是有消耗的,每次我们要去判断条件是否成立之前,如果这个条件已经不成立了,但是我们还得先上锁才能得知不成立,这不是明显消耗了不必要的资源了吗。

​ 所以我们就要学习信号量,信号量说简单一点也是一个共享资源,但是对它的操作,是原子性的,从而达到了不需要加锁就能实现判断是否需要满足信号量的要求从而对临界资源进行操作!

​ 但我们要明白的是,其实互斥锁本质也是一种信号量,只不过它只有 01 两种状态,表示是否被锁定,所以我们是可以用信号量来代替互斥锁的

二、PV 操作

PV 操作也称为 wait()signal() 操作,是操作系统中用于操作信号量的基本操作,它们通常与信号量一起使用来进行进程或者线程间的同步和互斥访问。

PV 操作分为两种:

  1. P操作(proberen荷兰语:尝试降低),即 wait() 操作:它用于获取(获取或占用)一个资源或者等待某个事件的发生,其作用是 将信号量的值减一,如果 信号量的值为0,则调用进程或者线程会阻塞,等待资源或事件的发生。
  2. V操作(verhogen荷兰语:增长),即 signal() 操作:它用于释放一个资源或者发出某个事件,其作用是 将信号量的值加一,如果 有其他进程或者线程在等待该信号量,则唤醒其中一个进程,让它可以继续执行。

​ 例如,当多个进程需要访问共享资源时,可以使用 PV操作 来实现互斥访问,其中 P操作 用于 占用资源V操作 用于 释放资源。又例如,当多个进程需要等待某个事件的发生时,可以使用 PV操作实现等待和唤醒的操作,其中 P操作 用于等待事件的发生,V操作 用于发出事件的信号。

​ 需要注意的是,在使用 PV 操作时,需要考虑信号量的初始值,以及多个进程同时访问时可能出现的竞争条件,从而保证操作的正确性和安全性。

三、POSIX 信号量的实现原理

POSIX信号量的实现通常是通过原子操作来保证多个进程之间对信号量的操作是原子性的。

​ 具体地说,POSIX信号量的实现使用了一些底层硬件原语或操作系统提供的原子操作,比如 CASCompare-and-Swap 等,以确保对信号量的操作是原子性的,从而避免了竞态条件和死锁等问题的出现。

​ 原子操作是一种不可中断的操作,可以在多线程或多进程并发访问共享资源的情况下,保证对共享资源的访问是原子性的。因此,使用原子操作来实现信号量操作,可以有效地避免因多进程并发访问而导致的数据竞争和互斥访问等问题。

​ 需要注意的是,原子操作在一些平台上的实现方式可能会有所不同,程序员在使用时需要根据具体的平台和操作系统来选择适当的原子操作实现。

四、CAS操作介绍

Compare-and-SwapCAS是一种常见的原子操作,也称为“比较-交换”操作,是一种比较后数据若无改变则交换数据的一种无锁操作(乐观锁)。用于实现多线程或多进程并发访问共享资源的原子性操作。

CAS 操作通常有三个参数:共享变量的内存地址、期望值和新值。CAS 操作首先比较共享变量的值是否等于期望值,如果相等,则将共享变量的值替换为新值;如果不相等,则不执行任何操作。在这个过程中,CAS操作具有原子性,即其他线程或进程无法同时访问共享变量,从而避免了竞态条件和数据竞争等问题。

CAS 操作常见于并发控制算法中,如锁、信号量、读写锁等,它可以用来保证对共享资源的访问是原子性的,从而实现并发控制的正确性和安全性。

​ 需要注意的是,CAS 操作虽然可以保证对共享变量的原子性操作,但是由于 CAS 操作涉及到对共享变量的读取和修改,因此仍然可能存在ABA问题,即在多线程或多进程并发访问共享变量的情况下,共享变量的值在某个时刻先变成了 A,然后又变成了 B,最后再变回了 A,从而导致一些问题。

​ 为了避免上述的问题,通常需要使用带有版本号等标识的 CAS 操作来解决。即在进行 CAS 操作时,不仅要比较共享变量的值是否等于期望值,还需要比较版本号是否匹配。这样可以确保即使共享变量的值在中间被修改过,但由于版本号不匹配,CAS 操作依然会失败,从而避免了上述问题的出现。

CAS的伪代码如下:

template bool CAS(T* addr, T expected, T value)
{if(*addr == expected){*addr=value;return true;}return false;
}
int count=0;
void count_atomic_inc(int*addr)
{int oldval = 0;int newval = 0;do{oldval = *addr;newval = oldval + 1;}until CAS(addr, oldval, newval)
}// 简化之后,CAS比较与交换的伪代码可以表示为:
do{备份旧数据;基于旧数据构造新数据;}while(!CAS( 内存地址,备份的旧数据,新数据 ))

Ⅲ. POSIX未命名信号量接口

​ 我们主要是为了完成线程间的通信,所以下面主要介绍的是未命名信号量的接口,至于命名信号量接口,可以自行上网查阅!

一、初始化无名信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);// 功能:初始化一个无名信号量
// 返回值:成功返回0,失败返回-1,并设置errno变量表示出错的原因

其中参数:

  • sem:指向要初始化的信号量的指针。
  • pshared0 表示线程间共享,此时为未命名信号量;非 0 表示进程间共享,此时为命名信号量。
  • value:信号量初始值,最大可以设置为 2147483647SEM_VALUE_MAX

​ 对于 sem_t 类型结构体的定义如下:

typedef struct
{struct _pthread_fastlock __sem_lock; // 用于保护信号量的互斥锁,是一个pthread_fastlock类型的对象。int __sem_value; // 表示信号量的当前值,即信号量中的可用资源数。_pthread_descr __sem_waiting; // 表示正在等待信号量的线程或进程的列表,是一个_pthread_descr类型的对象。
} sem_t;

二、销毁无名信号量

#include <semaphore.h>
int sem_destroy(sem_t *sem);// 功能:销毁一个无名信号量
// 返回值:成功返回0,失败返回-1,并设置errno变量表示出错的原因
// 参数:sem是指向要销毁的信号量的指针。

三、阻塞减少信号量(相当于P操作)

#include <semaphore.h>
int sem_wait(sem_t *sem);// 功能:阻塞减少信号量,直到 sem 所指示的信号量计数大于零为止,之后以原子方式减小计数。
// 返回值:成功返回0,失败返回-1,并设置errno变量表示出错的原因。
// 参数:sem是指向要阻塞减少的信号量的指针。

四、非阻塞减少信号量(相当于P操作)

#include <semaphore.h>
int sem_trywait(sem_t *sem);// 功能:在计数大于零时,尝试以原子方式减小sem所指示的信号量计数。
// 返回值:成功返回0,失败返回-1,并设置errno变量表示出错的原因。
// 参数:sem是指向要非阻塞减少的信号量的指针。

五、增加信号量(相当于V操作)

#include <semaphore.h>
int sem_post(sem_t *sem);// 功能:以原子方式增加sem所指示的信号量,或者说给信号量解锁。
// 返回值:成功返回0,失败返回-1,并设置errno变量表示出错的原因。
// 参数:sem是指向要增加的信号量的指针。

六、读取当前信号量值

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);// 功能:读取当前信号量的值。
// 返回值:成功返回0,并将该信号量当前值存储在sval变量中;失败返回-1。
// 参数:
//     sem:指向要增加的信号量的指针。
//     sval:输出型参数,存放信号量值

测试代码:利用信号量实现互斥锁功能

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>int global_value = 20;
sem_t mutex; // 使用信号量来实现代替互斥锁void* thread_routine(void* args)
{char* namebuffer = static_cast<char*>(args);while(global_value){sem_wait(&mutex); // 将mutex减一,变成0,相当于加锁,其它线程会阻塞if(global_value > 0)std::cout << namebuffer << " the global_value is " << global_value-- << std::endl;sem_post(&mutex); // 将mutex加一,变成1,相当于解锁,其它线程就能竞争该信号量usleep(10000);}return nullptr;
}int main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES基本使用方式

ES基本使用 文章目录 ES基本使用1.es的访问使用URL访问 2.mapping的理解Dynamic Mappingkeyword 与 text的区别‌基础定义与核心差异主字段&#xff0c;子字段 创建mapping 3.创建索引4.查看索引列表5.删除索引6.添加数据7.查询数据 重置es密码&#xff0c;初始用户elastic el…

玩转Docker | 使用Docker部署AI证件照工具

玩转Docker | 使用Docker部署AI证件照工具 前言一、HivisionIDPhotos介绍项目简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署HivisionIDPhotos服务下载镜像创建容器创建容器检查容器状态检查服务端口安全设置四、访问HivisionIDPhotos服务访…

【掌握 DDL】:SQL 中的数据库与表管理

掌握 DDL&#xff1a;SQL 中的数据库与表管理 掌握 DDL&#xff1a;SQL 中的数据库与表管理数据库 DDL创建数据库查看数据库查看所有数据库查看数据库创建语句 进入数据库删除数据库备份数据库备份恢复 查看数据库连接深入理解数据库创建与删除数据库字符集与校验规则 表 DLL创…

基于「骑手外卖系统」串联7大设计原则

你说得对&#xff01;这些设计原则听起来都很抽象、很“玄”&#xff0c;如果不是实际开发过系统&#xff0c;很难理解“到底为什么要这样设计”。 那我现在就用一个你能想象得很清楚的真实例子&#xff0c;帮你把这7个设计原则一一落地到具体情境里&#xff0c;你会一眼明白。…

基于轻量化YOLO的车载设备实时视频目标检测方案

一、背景与需求 在智能汽车后装市场,设备的视觉感知能力日益成为差异化竞争点。传统后装产品主要关注车辆诊断和位置跟踪,但在智能辅助驾驶(ADAS)与车联网(V2X)大潮下,如果能在已有硬件平台上新增“视频级行人、车辆、交通标志检测”功能,不仅可提升安全预警,也能为后…

HTTPS协议:更安全的HTTP

目录 1. 前言 2. HTTP 与 HTTPS&#xff1a;安全的分水岭 2.1 HTTP 的安全隐患 2.2 HTTPS 的安全提升 3. HTTPS 的核心概念 3.1 加密三剑客&#xff1a;对称加密、非对称加密与哈希算法 3.2 SSL/TLS 握手过程&#xff1a;建立安全通道的关键步骤 3.3 数字证书&#xff…

使用 Go 和 Gorgonia 实现图像验证码识别系统

本文将介绍如何使用 Go 语言结合 Gorgonia 构建一个简单的图像验证码识别模型。Gorgonia 是一个专为机器学习打造的计算图库&#xff0c;在 Go 中支持自动微分与深度学习构建。 1. 项目依赖 首先安装 Go 语言环境和 Gorgonia&#xff1a; 登录后复制 go install gorgonia.org/g…

list的两种设计

1. 内存布局对比 (1) MSVC 的实现 cpp class _List_node {_List_node* _Next; // 指向下一个节点_List_node* _Prev; // 指向前一个节点_Value_type _Value; // 存储的数据 }; 特点&#xff1a; 每个节点包含两个指针和一个数据成员。 Debug 模式&#xff1a;可能添加迭代…

多多铃声 7.4| 拥有丰富的铃声曲库,满足不同用户的个性化需求,支持一键设置手机铃声

多多铃声是一款提供丰富铃声资源的应用程序&#xff0c;它拥有广泛的铃声曲库&#xff0c;涵盖各种风格和类型&#xff0c;能够满足不同用户的个性化需求。该应用程序支持分类浏览和热门榜单功能&#xff0c;让用户可以轻松找到当前最流行或自己感兴趣的铃声。此次分享的版本为…

Day04 新增套餐

###今天的任务主要是自主完成套餐管理的模块### 1.新增套餐 在前端页面接口中我们可以看到在新增套餐的时候需要选择添加到菜单中的菜品 因此我们需要设计一个接口可以通过根据分类id&#xff08;category_id&#xff09;来查询该分类下的菜品 1.1根据分类id查询分类下的菜…

数据赋能(208)——质量管理——及时性原则

概述 在数据处理、分析和应用过程中&#xff0c;数据及时性原则确保了数据在需要时能够迅速、准确地被获取、更新和传递&#xff0c;为决策和业务需求提供了时效性保障。能够反映当前的真实状况&#xff0c;为决策提供最新、最准确的信息支持。这种及时性不仅有助于企业快速响…

华为OD机试真题——告警抑制(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式&#xff1b; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析&#xff1b; 本文收录于专栏&#xff1a;《2025华为OD真题目录…

ASP.NET MVC​ 入门指南四

21. 高级路由配置 21.1 自定义路由约束 除了使用默认的路由约束&#xff0c;你还可以创建自定义路由约束。自定义路由约束允许你根据特定的业务逻辑来决定一个路由是否匹配。例如&#xff0c;创建一个只允许特定年份的路由约束&#xff1a; csharp public class YearRouteCo…

测试基础笔记第十八天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、web自动化1.xpath定位1.属性定位2.属性与逻辑结合3.属性和层级结合 2.常见元素定位方法&#xff08;面试题&#xff09;3.常见元素定位失败原因4.cookie1.验证码…

(笔记)List

一、List的介绍和使用 1.List的介绍 1.1 list是可以在任意常数范围内插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 1.2 list底层是双向链表结构&#xff0c;双向链表中每个元素都储存在互不相关的独立节点中&#xff0c;在节点中通过指针指向前其前一个…

重载和覆写有什么区别?

重载&#xff08;Overload&#xff09;和覆写&#xff08;Override&#xff0c;也叫重写 &#xff09;是面向对象编程中的重要概念&#xff0c;它们有以下区别&#xff1a; 定义 - 重载&#xff1a;在同一类中&#xff0c;允许存在多个方法名相同&#xff0c;但参数列表&#x…

flask 获取各种请求数据:GET form-data x-www-form-urlencoded JSON headers 上传文件

在 Flask 里&#xff0c;能使用多种方法获取不同类型的请求数据&#xff0c;下面详细介绍常见请求数据的获取方式。 获取查询字符串参数&#xff08;GET 请求&#xff09; 查询字符串参数一般在 URL 里&#xff0c;以 ?key1value1&key2value2 这种形式存在。可通过 requ…

人工智能助力工业制造:迈向智能制造的未来

在当今数字化转型的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐渐成为推动工业制造领域变革的核心力量。智能制造作为工业 4.0 的重要组成部分&#xff0c;通过将 AI 技术与传统制造工艺深度融合&#xff0c;正在重塑整个生产流程&#xff0c;提高生产效率、…

【java八股文】深入浅出synchronized优化原理

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 synchronized优化原理 synchronized即使悲观锁也是乐观锁&#xff0c;拥有自适应性。 jvm内部会统计每个锁的竞争激烈程度&…

生成式 AI 的重要性

在科技飞速发展的今天,我们正站在一个前所未有的变革节点上。生成式 AI,宛如一颗突然划破夜空的耀眼流星,以其强大的创造力和无限的可能性,迅速成为全球瞩目的焦点。它究竟有何等魔力,能在如此短的时间内引发如此巨大的轰动?这背后又隐藏着怎样的时代密码,等待着我们去解…