【linux】多线程(2)

news2024/11/17 0:27:43

文章目录

  • 线程的应用
    • 生产消费者模型
      • 自制锁
      • 生产消费队列
        • 成员参数
        • 生产函数
        • 消费函数
      • 任务处理方式
        • 主函数
  • POSIX信号量
    • sem_wait()
    • sem_post()
  • 线程池
    • 应用场景
    • 示例
  • 单例模式
    • 饿汉实现单例
  • 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
    • 懒汉实现单例
  • 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

线程的应用

生产消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而
通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者
要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队
列就是用来给生产者和消费者解耦的。

在这里插入图片描述
其中生产则会与消费者相当于两个进程,进行生产与消费。

自制锁

通过封装线程锁,达到智能指针的效果,作用域结束,则自动释放锁,以防忘记释放锁,造成线程死锁!

#pragma  once
#include <pthread.h>
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};
class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        :_mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
      _mutex.Unlock();
    } 
private:
     Mutex _mutex;
};

生产消费队列

成员参数
 std::queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; // 给生产者的
    pthread_cond_t _c_cond; // 给消费者的
生产函数

判断队列是否是满的,如果满的那么则进行等待,

void Push(const T &in)
    {
        LockGuard lockguard(&_mutex);//加锁。
        while (IsFull())//判断队列是否为满?
        {
            pthread_cond_wait(&_p_cond,&_mutex);    
        }
        _q.push(in);
        pthread_cond_signal(&_c_cond);
    }
消费函数

判断队列是否为空,若为空,则进行等待

void Pop(T *out)
    {
        LockGuard lockguard(&_mutex);
        while(IsEmpty())
        {
            pthread_cond_wait(&_c_cond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_p_cond);
    }

整体:

#pragma  once

#include <iostream>
#include <queue>
#include "LockGuard.hpp"
const int defaultcap = 5;
template <class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap)
        : _capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }
    bool IsFull()
    {
        return _q.size() == _capacity;
    }

    bool IsEmpty()
    {
        return _q.size() == 0;
    }
    void Push(const T &in)
    {
        LockGuard lockguard(&_mutex);
        while (IsFull())
        {
            pthread_cond_wait(&_p_cond,&_mutex);    
        }
        _q.push(in);
        pthread_cond_signal(&_c_cond);
    }
    void Pop(T *out)
    {
        LockGuard lockguard(&_mutex);
        while(IsEmpty())
        {
            pthread_cond_wait(&_c_cond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_p_cond);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    std::queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; // 给生产者的
    pthread_cond_t _c_cond; // 给消费者的
};

任务处理方式

#pragma  once
#include <iostream>
#include <string>
#include <unistd.h>
const int defaultvalue = 0;
const std::string opers = "+-*/%)(&";
enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unknow
};

class Task
{
public:
    Task() {}
    Task(int x, int y, char op)
        : data_x(x),
          data_y(y),
          oper(op),
          result(defaultvalue),
          code(ok)
    {
    }
    void Run()
    {
        switch (oper)
        {
        case '+':
            result = data_x + data_y;
            break;
        case '-':
            result = data_x - data_y;
            break;
        case '*':
            result = data_x * data_y;
            break;
        case '/':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }
        case '%':
        {
            if (data_y == 0)
                code = mod_zero;
            else
                result = data_x % data_y;
        }

        break;
        default:
            code = unknow;
            break;
        }
    }
    void operator()()
    {
        Run();
        sleep(2);
    }
    std::string PrintTask()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=?";
        return s;
    }
    std::string PrintResult()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += std::to_string(result);
        s += '[';
        s += std::to_string(code);
        s += ']';
        return s;
    }
    ~Task()
    {
    }

private:
    int data_x;
    int data_y;
    char oper;
    int result;
    int code;
};
主函数
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
class ThreadData
{
public:
    BlockQueue<Task> *bq;
    std::string name;
};
void *consumer(void *args)
{
    ThreadData *td = (ThreadData *)args;
    while (true)
    {
        Task t;
        td->bq->Pop(&t);
        t();
        std::cout << "consumer data: " << t.PrintResult() << ", " << td->name << std::endl;
    }
    return nullptr;
}
void *productor(void *args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        int data1 = rand() % 10;
        usleep(rand() % 123);
        int data2 = rand() % 10;
        usleep(rand() % 123);
        char oper = opers[rand() % (opers.size())];
        Task t(data1, data2, oper);
        std::cout << "productor task: " << t.PrintTask() << std::endl;
        bq->Push(t);
        sleep(1);
    }
    return nullptr;
}

int main()
{
    srand((uint16_t)time(nullptr) ^ getpid() ^ pthread_self());
    BlockQueue<Task> *bq = new BlockQueue<Task>();
    pthread_t c[3], p[2];

    ThreadData *td = new ThreadData();
    td->bq = bq;
    td->name = "thread_1";
    pthread_create(&c[0], nullptr, consumer, td);

    ThreadData *td1 = new ThreadData();
    td1->bq = bq;
    td1->name = "thread_2";
    pthread_create(&c[1], nullptr, consumer, td1);

    ThreadData *td2 = new ThreadData();
    td2->bq = bq;
    td2->name = "thread_3";
    pthread_create(&c[2], nullptr, consumer, td2);

    pthread_create(&p[0],nullptr,productor,bq);
    pthread_create(&p[1],nullptr,productor,bq);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(c[2], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    return 0;
}

POSIX信号量

POSIX信号量(POSIX semaphores)是POSIX(Portable Operating System Interface)标准定义的一种同步原语,用于协调多线程或多进程之间的资源访问。它们提供了一种机制,允许一个或多个线程或进程等待某个条件成立,或者通知其他线程或进程某个条件已经成立。

sem_wait()

sem_wait 函数用于等待信号量 sem 变为非零值。具体来说,它执行以下操作:
检查信号量 sem 的当前值是否大于零。
如果大于零,则将其值减一,并立即返回。
如果等于零,则调用线程会被阻塞,直到信号量的值变为非零(通常是由另一个线程或进程通过 sem_post 操作增加信号量的值)。

sem_post()

sem_post 函数用于增加信号量 sem 的值。具体来说,它执行以下操作:
将信号量 sem 的值加一。
如果有线程在等待该信号量(即之前被 sem_wait 阻塞),则唤醒其中一个线程(通常是按照先进先出(FIFO)的顺序)。

拿上面的消费生产这模型举例:
将生产者的信号量设置为队列的大小,消费者的信号量设置为0.
当进行生产时,先进行sem_wait判断是否还有空位置进行生产,当生产完则将消费者的信号量进行增加,告诉消费者可以进行消费。
当进行消费时,则与之相反。

#pragma once
const int defaultsize = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }

public:
    RingQueue(int size = defaultsize)
        : _ringqueue(size), _size(size), _p_step(0), _c_step(0)
    {
        sem_init(&_space_sem, 0, size);
        sem_init(&_data_sem, 0, 0);

        pthread_mutex_init(&_p_mutex, nullptr);
        pthread_mutex_init(&_c_mutex, nullptr);
    }
    void Push(const T &in)
    {
        // 生产
        P(_space_sem);
        {
            LockGuard lockGuard(&_p_mutex);
            _ringqueue[_p_step] = in;
            _p_step++;
            _p_step %= _size;
        }
        V(_data_sem);
    }
    void Pop(T *out)
    {
        // 消费
        P(_data_sem);
        {
            LockGuard lockGuard(&_c_mutex);
            *out = _ringqueue[_c_step];
            _c_step++;
            _c_step %= _size;
        }
        V(_space_sem);
    }
    ~RingQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);

        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

private:
    std::vector<T> _ringqueue;
    int _size;

    int _p_step; // 生产者的生产位置
    int _c_step; // 消费位置

    sem_t _space_sem; // 生产者
    sem_t _data_sem;  // 消费者

    pthread_mutex_t _p_mutex;
    pthread_mutex_t _c_mutex;
};

线程池

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着
监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利
用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

应用场景

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个
    Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情
    况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

示例

  1. 创建固定数量线程池,循环从任务队列中获取任务对象
  2. 获取到任务对象后,执行任务对象中的任务接口

单例模式

某些类, 只应该具有一个对象(实例), 就称之为单例。

饿汉实现单例

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.

template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};

懒汉实现单例

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

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

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

相关文章

CAN总线简介

1. CAN总线概述 1.1 CAN定义与历史背景 CAN&#xff0c;全称为Controller Area Network&#xff0c;是一种基于消息广播的串行通信协议。它最初由德国Bosch公司在1983年为汽车行业开发&#xff0c;目的是实现汽车内部电子控制单元&#xff08;ECUs&#xff09;之间的可靠通信。…

类的继承和方法重载

想象一下&#xff0c;有一个相亲想爱的一家人家族树。在这个家族树中&#xff0c;有一个祖先&#xff08;父类&#xff09;&#xff0c;它拥有一些基本的特征和行为&#xff0c;比如家族的传统、姓氏、某些共同的技能或知识。 现在&#xff0c;这个祖先有多个后代&#xff08;…

vue3模板语法以及attribute

模板语法​ Vue 使用一种基于 HTML 的模板语法&#xff0c;使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML&#xff0c;可以被符合规范的浏览器和 HTML 解析器解析。 在底层机制中&#xff0c;Vue 会将模板编译成高度优化…

【每日刷题】Day49

【每日刷题】Day49 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 110. 平衡二叉树 - 力扣&#xff08;LeetCode&#xff09; 2. 501. 二叉搜索树中的众数 - 力扣&…

XSS+CSRF攻击

一、前言 在DVWA靶场的XSS攻击下结合CSRF攻击完成修改密码 也就是在具有XSS漏洞的情况下实施CSRF攻击 二、实验 环境配置与上一篇博客一致&#xff0c;有兴趣可以参考CSRF跨站请求伪造实战-CSDN博客 首先登录DVWA&#xff0c;打开XSS模块 name随便输入&#xff0c;message…

.lib .a .dll库互转

编译 mingw工具&#xff0c;gendef.exe转换dll为a&#xff0c;reimp转换lib为adlltool.exe --dllname python38.dll --def python38.def --output-lib libpython38.adlltool -k -d crypto.lib -l crypto.a 创作不易&#xff0c; 小小的支持一下吧&#xff01;

【数据结构与算法 经典例题】求带环链表的入口

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录

DragonKnight CTF2024部分wp

DragonKnight CTF2024部分wp 最终成果 又是被带飞的一天&#xff0c;偷偷拷打一下队里的pwn手&#xff0c;只出了一题 这里是我们队的wp web web就出了两个ez题&#xff0c;确实很easy&#xff0c;只是需要一点脑洞(感觉)&#xff0c; ezsgin dirsearch扫一下就发现有ind…

人工智能为犯罪地下世界带来了巨大的生产力提升

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

使用docker完整搭建前后端分离项目

1、docker的优势&#xff0c;为啥用docker 2、docker的核心概念 镜像【Image】- 只读模板 容器【Container】- 运行镜像的一个外壳&#xff0c;相当于一个独立的虚拟机 仓库【repository】- 镜像的管理工具&#xff0c;可公开&#xff0c;可私有&#xff1b;类似git仓库 3、c…

考研数学|线代跟谁好,李永乐,汤家凤还是张宇?

如果线代基础不好&#xff0c;那建议开刚开始的时候听汤家凤老师的线代课程 汤家凤教授的线性代数课程常被忽视&#xff0c;多数人倾向于去听李永乐。然而&#xff0c;在我考研的过程中&#xff0c;我曾尝试听李永乐教老师的课&#xff0c;可能是由于我自身基础薄弱&#xff0…

基于Vue的前端自定义询问弹框与输入弹框组件的设计与实践

基于Vue的前端自定义询问弹框与输入弹框组件的设计与实践 摘要 随着技术的不断进步&#xff0c;前端开发面临越来越多的挑战&#xff0c;其中之一就是如何有效管理复杂的业务逻辑和用户体验。传统的整块应用开发方式在面对频繁的功能变更和用户体验优化时&#xff0c;往往显得…

各大模型厂商API使用:百度、阿里、豆包、kimi、deepseek

百度ERNIE(支持requests接口) ERNIE Speed、ERNIE Lite免费 免费测试下来模型ernie_speed输出吞吐量计算20-30来个,“{length/cost} tokens/s” 输出总长度/耗时 https://qianfan.cloud.baidu.com/ 文档: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/dltgsna1o a…

数据结构——链式二叉树知识点以及链式二叉树数据操作函数详解!!

引言&#xff1a;该博客将会详细的讲解二叉树的三种遍历方法&#xff1a;前序、中序、后序&#xff0c;也同时会讲到关于二叉树的数据操作函数。值得一提的是&#xff0c;这些函数几乎都是建立在一个函数思想——递归之上的。这次的代码其实写起来十分简单&#xff0c;用不了几…

【C++】二分查找:在排序数组中查找元素的第一个和最后一个位置

1.题目 难点&#xff1a;要求时间复杂度度为O(logn)。 2.算法思路 需要找到左边界和右边界就可以解决问题。 题目中的数组具有“二段性”&#xff0c;所以可以通过二分查找的思想进行解题。 代码&#xff1a; class Solution { public:vector<int> searchRange(vect…

【传知代码】无监督动画中关节动画的运动表示-论文复现

文章目录 概述动画技术的演进原理介绍核心逻辑环境配置/部署方式小结 本文涉及的源码可从无监督动画中关节动画的运动表示该文章下方附件获取 概述 该文探讨了动画在教育和娱乐中的作用&#xff0c;以及通过数据驱动方法简化动画制作的尝试。近期研究通过无监督运动转移减少对…

【数据结构与算法 经典例题】判断链表是否带环

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录

数据结构和算法|排序算法系列(三)|插入排序(三路排序函数std::sort)

首先需要你对排序算法的评价维度和一个理想排序算法应该是什么样的有一个基本的认知&#xff1a; 《Hello算法之排序算法》 主要内容来自&#xff1a;Hello算法11.4 插入排序 插入排序的整个过程与手动整理一副牌非常相似。 我们在未排序区间选择一个基准元素&#xff0c;将…

社交媒体数据恢复:聊天宝

请注意&#xff0c;本教程仅针对聊天宝应用程序&#xff0c;而非其他聊天软件。以下是详细的步骤&#xff1a; 首先&#xff0c;请确保您已经登录了聊天宝应用程序。如果您尚未登录&#xff0c;请使用您的账号登录。 在聊天宝主界面&#xff0c;找到您希望恢复聊天记录的对话框…

深度学习复盘与小实现

文章目录 一、查漏补缺复盘1、python中zip()用法2、Tensor和tensor的区别3、计算图中的迭代取数4、nn.Modlue及nn.Linear 源码理解5、知识杂项思考列表6、KL散度初步理解 二、处理多维特征的输入1、逻辑回归模型流程2、Mini-Batch (N samples) 三、加载数据集1、Python 魔法方法…