Linux POSIX信号量 线程池

news2024/11/25 2:52:45

Linux POSIX信号量 线程池

  • 一. 什么是POSIX信号量?
  • 二. POSIX信号量实现原理
  • 三. POSIX信号量接口函数
  • 四. 基于环形队列的生产消费模型
  • 五. 线程池

一. 什么是POSIX信号量?

POSIX信号量是一种用于同步和互斥操作的机制,属于POSIX(Portable Operating System Interface) 标准的一部分。这一标准定义了操作系统应该为应用程序提供的接口,而POSIX信号量是在多线程和多进程环境下实现同步的一种方式。

信号量本质上是一个计数器,用于描述临界资源的数量。在多线程或多进程的情况下,当多个执行单元(线程或进程)需要访问共享资源时,使用信号量可以有效地协调它们的行为,避免竞争条件和提高程序的可靠性。

二. POSIX信号量实现原理

POSIX信号量的实现原理基于一个计数器和一个等待队列。关键的操作包括P操作和V操作

P操作:申请信号量,如果信号量计数器大于零,表示资源可用,计数器减一;如果计数器为零,线程将被阻塞,并加入等待队列。
V操作:释放信号量计数器加一,并唤醒等待队列中的一个线程。

具体实现原理如下:

  1. 信号量结构包括计数器和等待队列。
  2. 当计数器为零时,表示资源不可用,线程申请信号量时将被阻塞,并放入等待队列。
  3. 当计数器大于零时,表示资源可用,线程申请信号量时计数器减一,线程获得资源。
  4. 当释放信号量时,计数器加一,如果等待队列不为空,唤醒等待队列中的一个线程。

三. POSIX信号量接口函数

  1. sem_t 是一个数据类型,用于表示信号量。作为同步机制的一部分,信号量用于协调共享资源的访问。用户通过提供的接口函数(如 sem_init、sem_wait、sem_post)来操作信号量,

  2. 初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

sem: 指向要初始化的信号量的指针。
pshared: 0表示信号量在线程间共享,非零表示在进程间共享。
value: 信号量的初始值。
返回值:成功时返回0,失败时返回-1。

  1. 等待信号量
#include <semaphore.h>
int sem_wait(sem_t *sem); // P()

sem: 指向要等待的信号量的指针。
功能:等待信号量,将信号量的值减1。如果信号量的值为0,线程将被阻塞
返回值:成功时返回0,失败时返回-1。

  1. 释放信号量
#include <semaphore.h>
int sem_post(sem_t *sem); // V()

sem: 指向要发布的信号量的指针。
功能:释放信号量,表示资源使用完毕,将信号量的值加1。通常用于释放信号
返回值:成功时返回0,失败时返回-1。

  1. 销毁信号量
#include <semaphore.h>
int sem_destroy(sem_t *sem);

sem: 要销毁的信号量的指针。
返回值:成功时返回0,失败时返回-1。

四. 基于环形队列的生产消费模型

  1. 环形队列简介
    环形队列是一种基于数组或链表的数据结构,具有循环特性。其关键特点包括循环性、高效性和固定大小。常用于缓冲区、循环缓存和生产者-消费者模型等场景。由于采用模运算,插入和删除操作的时间复杂度为O(1),使得其在实时系统和有限资源的应用中得以广泛应用。
    在这里插入图片描述

在这里插入图片描述
放数据操作:

等待生产者信号量: 通过 P(prodSemaphore),生产者等待信号量,确保有足够的空间可供数据生产。
将数据放入缓冲区: 数据被放入环形缓冲区的当前生产者索引位置 (buffer_[prodIndex])。 生产者索引 prodIndex 被更新,并通过取模操作确保索引在缓冲区容量内循环。
发送消费者信号量: 通过 V(consSemaphore),生产者通知消费者有新的数据可供消费。

拿数据操作:

等待消费者信号量: 通过 P(consSemaphore),消费者等待信号量,确保有足够的数据可供消费。
从缓冲区取出数据: 数据被从环形缓冲区的当前消费者索引位置取出 (buffer_[consIndex])。 消费者索引 consIndex
被更新,并通过取模操作确保索引在缓冲区容量内循环。
发送生产者信号量: 通过 V(prodSemaphore),消费者通知生产者有空间可供数据生产。

五. 线程池

什么是线程池?
线程池是一种常见的多线程使用模式。它通过维护一组线程,等待监督管理者分配可并发执行的任务。这种设计避免了在处理短时间任务时创建与销毁线程的开销,提高了系统性能。线程池通过保证内核的充分利用,同时防止过度调度,对于某些应用场景尤其有效。

实例:创建一个简单的固定数量线程池

#include <iostream>
#include <queue>
#include <vector>
#include <pthread.h>
#include <unistd.h>
using namespace std;

// 线程数据结构
struct threaddata
{
    pthread_t tid;  // 线程ID
};

// 任务类
class Task
{
public:
    Task(int data) : data_(data)
    {}
    // 重载运算符,用于执行任务
    void operator()()
    {
        run();
    }
private:
    // 任务执行函数
    void run()
    {
        cout << "数据:" << data_ << endl;
    }
    int data_;
};

// 线程池模板类
template<class T>
class ThreadPool
{
public:
    // 构造函数,默认线程数为6
    ThreadPool(int n = 6) : td(6)
    {
        pthread_mutex_init(&mutex_, nullptr);  // 初始化互斥锁
        pthread_cond_init(&cond_, nullptr);    // 初始化条件变量
    }

    // 析构函数
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);    // 销毁互斥锁
        pthread_cond_destroy(&cond_);      // 销毁条件变量
    }

    // 线程处理函数
    static void* handler(void* args)
    {
        ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args);
         while (1)
        {
            tp->lock();  // 加锁,保护临界区
            while (tp->isQueueEmpty())
            {
                tp->wait();  // 当任务队列为空时,等待条件变量
            }
            
            T data = tp->queueFront();  // 获取任务队列的队首元素
            tp->queuePop();  // 弹出任务队列的队首元素
            tp->unlock();  // 解锁,释放临界区

            data();  // 执行任务
        }
    }

    // 启动线程池
    void start()
    {
        for (int i = 0; i < td.size(); i++)
        {
            pthread_create(&(td[i].tid), nullptr, handler, static_cast<void*>(this));  // 创建线程
        }
    }

    // 将任务放入任务队列
    void push(T& data)
    {
        lock();               // 加锁,确保线程安全
        task.push(data);      // 将任务加入队列
        wakeup();             // 唤醒等待的线程
        unlock();             // 解锁,释放锁,允许其他线程访问任务队列
    }

    // 判断任务队列是否为空
    bool isQueueEmpty()
    {
        return task.empty();
    }

    // 获取任务队列的队首元素
    T& queueFront()
    {
        return task.front();
    }

    // 弹出任务队列的队首元素
    void queuePop()
    {
        task.pop();
    }

public:
    // 加锁操作
    void lock()
    {
        pthread_mutex_lock(&mutex_);
    }

    // 解锁操作
    void unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }

    // 等待条件变量
    void wait()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }

    // 唤醒等待条件变量的线程
    void wakeup()
    {
        pthread_cond_signal(&cond_);
    }

private:
    vector<threaddata> td;  // 线程数据
    queue<T> task;          // 任务队列

    pthread_mutex_t mutex_; // 互斥锁
    pthread_cond_t cond_;   // 条件变量
};

int main()
{
    srand(time(nullptr));
    ThreadPool<Task> thread(6);  // 创建线程池,设置线程数为6
    thread.start();  // 启动线程池

    while (1)
    {
        Task d(rand() % 100);
        thread.push(d);  // 将任务放入线程池
        sleep(1);
    }

    return 0;
}

线程池模板类 ThreadPool: 创建了一个线程池类,模板参数为任务类型 T,默认线程数为6。 使用 pthread 库提供的互斥锁和条件变量来实现线程同步。 提供了启动线程池的 start 函数,创建指定数量的线程,并在这些线程中执行 handler
函数。 提供了将任务推送到任务队列的 push 函数,该函数会将任务加入队列,唤醒等待中的线程。

任务类 Task: 任务类用于封装线程池中执行的具体任务,其中包含一个整数类型的数据。 通过重载 () 运算符实现了任务的执行函数,输出任务的数据。

线程处理函数 handler: 作为线程的入口函数,不断从任务队列中取出任务并执行。 使用互斥锁保护任务队列,条件变量用于在任务队列为空时等待新任务。 通过调用线程池的成员函数来实现任务的执行、入队、出队等操作。

主函数 main: 在主函数中,创建了一个 ThreadPool 对象,设置线程数为6,并启动线程池。 进入无限循环,每次循环生成一个随机数,创建一个包含该随机数的 Task 对象,并通过线程池的 push 函数将任务推送到任务队列中。
程序不断创建新的任务,由线程池中的线程执行。

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

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

相关文章

C++入门篇——命名空间

在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存 在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c; 以避免命名冲突或名字污染&#xff0c;namespace关键…

Calendar的使用(Java)

直接从需求来理解&#xff1a;将2024年2月16日增加一个月 如果不使用Calendar的话&#xff0c;我们需要定义字符串记住这个日期&#xff0c;然后把字符串解析成Date日期对象&#xff0c;通过Date日期对象获取其毫秒值&#xff0c;然后增加一个月的毫秒值&#xff0c;再格式化时…

社区养老|社区养老服务系统|基于springboot社区养老服务系统设计与实现(源码+数据库+文档)

社区养老服务系统目录 目录 基于springboot社区养老服务系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员部分功能 &#xff08;1&#xff09; 用户管理 &#xff08;2&#xff09;服务种类管理 &#xff08;3&#xff09;社区服务管理 &#xff08…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第五天-ARM Linux编程之字符设备驱动(物联技术666)

链接&#xff1a;https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd1688 提取码&#xff1a;1688 教学内容&#xff1a; 1、内核模块的简单框架&#xff1a; __init __exit执行完后就释放空间 简单框架&#xff1a;包含三个部分 1&#xff09;模块初始化和模块退出函数…

题目:3.神奇的数组(蓝桥OJ 3000)

问题描述&#xff1a; 解题思路&#xff1a; 官方&#xff1a; 我的总结&#xff1a; 利用双指针遍历每个区间并判断是否符合条件&#xff1a;若一个区间符合条件则该区间在其左端点不变的情况下的每一个子区间都符合条件&#xff0c;相反若一个区间内左端点固定情况下有一个以…

排序算法---桶排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 桶排序&#xff08;Bucket Sort&#xff09;是一种排序算法&#xff0c;它将待排序的数据分到几个有序的桶中&#xff0c;每个桶再分别进行排序&#xff0c;最后将各个桶中的数据按照顺序依次取出&#xff0c;即可得到有序序…

MySQL免安装版安装教程

官网下载安装包 MySQL :: Download MySQL Community Server (Archived Versions) 选择mysql版本下载 安装配置MySQL 将下载完的Mysql安装包解压到指定目录 打开windos系统的cmd&#xff0c;以管理员身份运行 进入mysql文件夹中的bin目录 安装MySQL的服务mysqld --install 初…

【教程】C++语言基础学习笔记(五)——Vector向量

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【C语言基础学习】系列文章 第一章 《项目与程序结构》 第二章 《数据类型》 第三章 《运算符》 第四章 《流程控制》 第五章…

姿态传感器MPU6050模块之陀螺仪、加速度计、磁力计

MEMS技术 微机电系统&#xff08;MEMS, Micro-Electro-Mechanical System&#xff09;&#xff0c;也叫做微电子机械系统、微系统、微机械等&#xff0c;指尺寸在几毫米乃至更小的高科技装置。微机电系统其内部结构一般在微米甚至纳米量级&#xff0c;是一个独立的智能系统。 微…

C语言指针(初阶)

文章目录 1:内存与地址1.1内存1.2:如何理解编址 2:指针变量与地址2.1:指针变量与解引用操作符2.1.1:指针变量2.1.2:如何拆解指针类型2.1.3:解引用操作符 2.2:指针变量的大小 3:指针变量类型的意义代码1解引用修改前解引用修改后 代码2解引用修改前解引用修改后 4:const修饰指针…

【C语言期末项目-通讯录】-终级版本-可动态申请内存、可存储数据到文件(手把手详细过程,期末评分A+的项目,答辩辅助神博文,建议三连点赞收藏)

目录 ​编辑 前言&#xff1a; 1.项目功能需求分析 2.文件框架说明 3.程序主框架实现 4.创建联系人结构体类型和通讯录结构体类型 4.1创建通讯录 5.程序功能实现--封装功能函数实现不同功能 5.1通讯录初始化 5.2增加联系人 5.3显示所有联系人的信息 5.4删除指定…

BBC英式口语~发音练习~笔记整理

参考资料 原视频地址&#xff1a; https://www.bilibili.com/video/BV1D7411n7bS/?spm_id_from333.1245.0.0&vd_source5986fc7c8e6d754f3ca44233573aeaff 笔记图片

root MUSIC 算法补充说明

root MUSIC 算法补充说明 多项式求根root MUSIC 算法原理如何从 2 M − 2 2M-2 2M−2 个根中确定 K K K 个根从复数域上观察 2 M − 2 2M-2 2M−2 个根的分布 这篇笔记是上一篇关于 root MUSIC 笔记的补充。 多项式求根 要理解 root MUSIC 算法&#xff0c;需要理解多项式求…

DSA 经典数据结构与算法 学习心得和知识总结(三) |有向无环图及其应用

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《算法导论》第三版 就是这本被封神的杰作&#xff0c;就是它&#x1f926; 2、参考书籍&#xff1a;《数据结构》严奶奶版 3、参考书…

Java学习笔记------static

static 创建Javabean类 public class student {private int age;private String name;private String gender;public student() {}public student(int age, String name, String gender) {this.age age;this.name name;this.gender gender;}/*** 获取* return age*/public…

论文解读:Masked Generative Distillation

文章汇总 话题 知识蒸馏 创新点 带掩盖的生成式蒸馏 方法旨在通过学生的遮罩特征来生成老师的特征(通过遮盖学生部分的特征来生成老师的特征)&#xff0c;来帮助学生获得更好的表现 输入:老师:&#xff0c;学生:&#xff0c;输入:&#xff0c;标签:&#xff0c;超参数: 1:使…

CTFshow web(文件上传158-161)

web158 知识点&#xff1a; auto_append_file 是 PHP 配置选项之一&#xff0c;在 PHP 脚本执行结束后自动追加执行指定的文件。 当 auto_append_file 配置被设置为一个文件路径时&#xff0c;PHP 将在执行完脚本文件的所有代码后&#xff0c;自动加载并执行指定的文件。 这…

【springboot+vue项目(十四)】基于Oauth2的SSO单点登录(一)整体流程介绍

场景&#xff1a;现在有一个前后端分离的系统&#xff0c;前端框架使用vue-element-template&#xff0c;后端框架使用springbootspringSecurityJWTRedis&#xff08;登录部分&#xff09;现在需要接入到已经存在的第三方基于oauth2.0的非标准接口统一认证系统。 温馨提示&…

【STM32 CubeMX】I2C查询方式

文章目录 前言一、CubeMX配置IIC二、查询方式的使用2.1 分析一种情况2.2 Master模式2.3 Mem模式 总结 前言 在STM32 CubeMX环境中&#xff0c;I2C&#xff08;Inter-Integrated Circuit&#xff09;通信协议的查询方式是一种简单而常见的通信方式。通过查询方式&#xff0c;微…

代码随想录 Leetcode45. 跳跃游戏 II

题目&#xff1a; 代码(首刷看解析 2024年2月15日&#xff09;&#xff1a; class Solution { public:int jump(vector<int>& nums) {if (nums.size() 1) return 0;int res 0;int curDistance 0;int nextDistance 0;for (int i 0; i < nums.size(); i) {nex…