linux线程池、基于线程池的单例模式、读者写者问题

news2024/9/26 5:13:28

线程池&单例模式

  • 一、什么是线程池
  • 二、设计思路
  • 三、代码实现
  • 四、基于线程池的单例模式
    • 4.1 懒汉模式设计思路
  • 五、读者写者问题及读写锁

一、什么是线程池

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

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
    在这里插入图片描述

二、设计思路

在这里插入图片描述

三、代码实现

注:由于我个人不注意,将ThreadPool写成了ThredaPool!!!

//Thread.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <pthread.h>
#include <cassert>

namespace ThreadCat
{
typedef std::function<void *(void *)> func_t;
const int num = 1024;

    class Thread
    {
    private:
        // 使用静态方法
        static void *start_rountine(void *args)
        {

            Thread *this_ = static_cast<Thread *>(args);
            return this_->callback();
        }
    public:
        Thread() 
        {
            char namebuffer[num];
            snprintf(namebuffer, sizeof namebuffer, "thread-%d", thread_num++);
            _name=namebuffer;   
        }

        void start(func_t func, void *args=nullptr)
        {
            _func=func;
            _args=args;
            int n = pthread_create(&_tid, nullptr, start_rountine, this);
            assert(n == 0);
            (void)n;
        }
        

        void join()
        {
            int n = pthread_join(_tid, nullptr);
            assert(n == 0);
            (void)n;
        }

        std::string threadname()
        {
            return _name;
        }

        void *callback()
        {
            return _func(_args);
        }

        ~Thread(){}

    private:
        std::string _name;
        pthread_t _tid;
        func_t _func;
        void *_args;
        static int thread_num;
    };
    int Thread::thread_num=1;
    

}
//Task.hpp
#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <functional>
class Task//计算任务
{

public:
    using func_t =std::function<int(int,int,char)>;
    Task(){}
    Task(int x,int y,char op,func_t callback)
        :x_(x),y_(y),op_(op),callback_(callback)
    {

    }
    std::string operator()()
    {
        int result=callback_(x_,y_,op_);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d=%d",x_,op_,y_,result);
        return buffer;
    }

    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d=?",x_,op_,y_);
        return buffer;
    }
private:
    int x_;
    int y_;
    char op_;
    func_t callback_;
};
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        break;
    }
    return result;
}

//lockguard.hpp
#pragma once
#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t * lock_p=nullptr)
        :lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_) pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_) pthread_mutex_unlock(lock_p_);
    }
    ~Mutex(){}
private:
    pthread_mutex_t* lock_p_;    
};


//RAII思想管理资源
class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock_p)//用传过来的锁去初始化Mutex
        :mutex(lock_p)
    {
        mutex.lock();
    }
    ~LockGuard()
    {
        mutex.unlock();
    }
private:
    Mutex mutex;
};
//ThreadPool.hpp
#pragma once
#include "Thread.hpp"
#include "lock_guard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace ThreadCat;

const int gnum = 5;

template <class T>
class ThredaPool;

template <class T>
class ThreadData
{
public:
    ThreadData(ThredaPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    }

public:
    ThredaPool<T> *threadpool;
    std::string name;
};

template <class T>
class ThredaPool
{
private:
    static void *handlerTask(void *args)
    {
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
            T t;
            {
                LockGuard lock(td->threadpool->mutex());
                //td->threadpool->lockQueue();
                while (td->threadpool->isQueueEmpty())
                {
                    td->threadpool->threadWait();
                }
                // pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
                t = td->threadpool->pop();
                //td->threadpool->unlockQueue();
            }
            std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
        }
        delete td;
        return nullptr;
    }

public:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool isQueueEmpty() { return task_queue_.empty(); }
    void threadWait() { pthread_cond_wait(&cond_, &mutex_); }
    T pop()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
    pthread_mutex_t* mutex()
    {
        return &mutex_;
    }
public:
    ThredaPool(const int &num = gnum) : num_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);

        for (int i = 0; i < num_; ++i)
        {
            //创建多个线程并传递参数,由于在类内调用,需要用静态方法
            threads_.push_back(new Thread());
        }
    }

    void run()
    {
        for (const auto &t : threads_)
        {
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            std::cout << t->threadname() << std::endl;
        }
    }

    void push(const T &in)
    {
        LockGuard lock(&mutex_);
        //pthread_mutex_lock(&mutex_);
        task_queue_.push(in);
        pthread_cond_signal(&cond_);
        //pthread_mutex_unlock(&mutex_);
    }

    ~ThredaPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);

        for (const auto &t : threads_)
        {
            delete t;
        }
    }

private:
    int num_;
    std::vector<Thread *> threads_;
    std::queue<T> task_queue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;
};
//main.cc
#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"

#include <unistd.h>
#include <memory>
using std::cout;
using std::endl;

int main()
{
    std::unique_ptr<ThredaPool<Task>> tp(new ThredaPool<Task>());
    tp->run();

    srand((size_t)time(0) ^ 0x22116652);
    int x, y;
    char op;
    while (true)
    {
        x = rand() % 10 + 1;
        y = rand() % 5 + 1;
        op = oper[rand() % oper.size()];
        Task t(x, y, op, mymath);
        tp->push(t);
        sleep(1);
    }
    return 0;
}

在以上代码中,我们首先封装好了线程库Thread,任务类Task,以及RAII风格的锁,然后编写ThreadPool文件。当我们在main.cc启动的时候,首先创建一个线程池对象然后调用其run方法,run方法中会调用线程库中的start方法,传递线程要执行的函数(handlerTask)以及喂给函数的参数(ThreadData *);在Thread库中,start方法会用pthread_create()创建一个线程,并实现回到函数,(其实这里绕了一圈就是为了解决调用类内方法存在this指针的问题,我们需要把方法设置成静态的,但是静态的方法无法使用非静态成员,需要一个大号结构体(ThreadData)包含一个ThreadPool的对象)。接下来主线程往任务队列中不断push任务,线程池中的线程执handlerTask函数,判断有无任务(无任务阻塞等待)有任务就从公共队列中拿到自己独立的栈中去执行。

四、基于线程池的单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了全局访问点来访问这个实例。这种模式通常用于控制资源的访问,例如数据库连接、线程池等等。单例模式的核心思想是将一个类的实例化过程限制在单个对象中,并提供一个全局的访问点来获取该对象。这个访问点通常是一个静态方法,可以在程序的任何地方调用。

单例模式的优点包括:

  1. 确保类只有一个实例,可以节省系统资源。

  2. 提供了全局访问点,方便程序的调用和管理。

  3. 保证了对象的一致性,避免了多个实例造成的数据不一致问题。

  4. 可以对实例化过程进行控制,保证对象的安全性和正确性。

4.1 懒汉模式设计思路

  1. 私有化构造函数,禁止拷贝构造和赋值运算符重载
  2. 在线程池基础上增加成员变量:静态互斥锁,静态线程池指针()
  3. 在线程池基础上增加静态方法:负责获取唯一对象
#include <mutex>
const int gnum = 5;

template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
public:
    ThreadData(ThreadPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    }

public:
    ThreadPool<T> *threadpool;
    std::string name;
};

template <class T>
class ThreadPool
{
private:
    ThreadPool(const int &num = gnum) : num_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);

        for (int i = 0; i < num_; ++i)
        {
            // 创建多个线程并传递参数,由于在类内调用,需要用静态方法
            threads_.push_back(new Thread());
        }
    }

    ThreadPool &operator=(const ThreadPool &) = delete;
    ThreadPool(const ThreadPool &) = delete;

    static void *handlerTask(void *args)
    {
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
            T t;
            {
                LockGuard lock(td->threadpool->mutex());
                // td->threadpool->lockQueue();
                while (td->threadpool->isQueueEmpty())
                {
                    td->threadpool->threadWait();
                }
                // pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
                t = td->threadpool->pop();
                // td->threadpool->unlockQueue();
            }
            std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
        }
        delete td;
        return nullptr;
    }

public:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool isQueueEmpty() { return task_queue_.empty(); }
    void threadWait() { pthread_cond_wait(&cond_, &mutex_); }
    T pop()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
    pthread_mutex_t *mutex()
    {
        return &mutex_;
    }

public:
    void run()
    {
        for (const auto &t : threads_)
        {
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            std::cout << t->threadname() << std::endl;
        }
    }

    void push(const T &in)
    {
        LockGuard lock(&mutex_);
        // pthread_mutex_lock(&mutex_);
        task_queue_.push(in);
        pthread_cond_signal(&cond_);
        // pthread_mutex_unlock(&mutex_);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);

        for (const auto &t : threads_)
        {
            delete t;
        }
    }

    static ThreadPool<T> *GetInstance()
    {
        if (tp == nullptr)
        {
            //std::lock_guard<mutex> lock(siglock);
            siglock.lock();
            if (nullptr == tp)
            {
                tp = new ThreadPool<T>();
            }
            siglock.unlock();
        }
        return tp;
    }

private:
    int num_;
    std::vector<Thread *> threads_;
    std::queue<T> task_queue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    volatile static ThreadPool<T> *tp;
    static std::mutex siglock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;

template <class T>
std::mutex  ThreadPool<T>::siglock;
//main.cc
ThreadPool<Task>::GetInstance()->run();

五、读者写者问题及读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。

321原则也适用于读者写者问题
3种关系:读者与写者互斥&&同步,读者与读者没有关系,写者与写者互斥
2种角色:读者与写者
1个交易场所

读者写者问题和生产者消费者模型的本质区别就是消费者会取走数据,而读者不会取走数据。

读写锁的行为

当前锁状态读锁请求写锁请求
无锁可以可以
读锁可以阻塞
写锁阻塞阻塞
//初始化读写锁
pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//读者加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写者加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解除锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

在这里插入图片描述
感谢阅读,linux系统编程到此结束,接下来将慢慢更新网络编程及C++方面的博客。

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

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

相关文章

flashback database 深入学习

一.FlashbackDatabase 和 Restore Points 说明 OracleFlashback Database and restore points are related data protectionfeatures that allow you to rewind data back in time tocorrect any problems caused by logical data corruption or user errors within adesignate…

gopher 初探

文章目录 gopher协议简介发送GET请求curlgopher gopher发POST请求 gopher协议 简介 Gopher是Internet上一个非常有名的信息查找系统&#xff0c;它将Internet上的文件组织成某种索引&#xff0c;很方便地将用户从Internet的一处带到另一处。在WWW出现之前&#xff0c;Gopher是…

【AI绘画】——Midjourney关键词格式解析(常用参数分享)

目前在AI绘画模型中&#xff0c;Midjourney的效果是公认的top级别&#xff0c;但同时也是相对较难使用的&#xff0c;对小白来说比较难上手&#xff0c;主要就在于Mj没有webui&#xff0c;不能选择参数&#xff0c;怎么找到这些隐藏参数并且触发它是用好Mj的第一步。 今天就来…

Motrix 全能型下载神器(时隔两年,重新焕发生机)

简介 Motrix 是一款功能强大的下载管理器&#xff0c;支持 HTTP、FTP、BT、磁力链等多种下载协议&#xff0c;同时还具备浏览器插件和跨平台&#xff08;Windows、macOS、Linux&#xff09;的特点。它拥有多线程下载、断点续传、任务调度、自动更新等多种实用特性&#xff0c;…

学习笔记:基于SpringBoot的牛客网社区项目实现(六)之发表帖子 + 评论 + 私信

一、过滤敏感词 二、发布帖子 业务流程&#xff1a; 1、先创建帖子的实体DiscussPost&#xff0c;属性有帖子id&#xff0c;user_id&#xff0c;title&#xff0c;content&#xff0c;type&#xff0c;status&#xff0c;create_time&#xff0c;comment_count&#xff0c;scor…

【Python入门知识】NumPy 数组搜索,案例+理论讲解

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 搜索数组 可以在数组中搜索&#xff08;检索&#xff09;某个值&#xff0c;然后返回获得匹配的索引。 要搜索数组&#xff0c;请使用 where() 方法。 实例 查找值为 4 的索引&#xff1a; import numpy as nparr np.…

[ tool ] Xpath选择器和selenium工具基本使用

XPath xpath介绍 是一门在XML文档中查找信息的语言 html文档准备 doc <html><head><base hrefhttp://example.com/ /><title>Example website</title></head><body><div idimages><a hrefimage1.html aabb>Name: My…

车载激光雷达原理+双激光雷达方案+纯激光雷达感知3Dbonding box

集度汽车透露了首款汽车机器人概念车车头部位的设计细节&#xff0c;车前灯和车前盖处激光雷达的设计清晰可见。至此&#xff0c;集度汽车的车前盖、前向双激光雷达自动驾驶技术方案首次曝光。 集度汽车官方也称&#xff0c;“双激光雷达”设计方案极具创新性&#xff0c;激光…

求中位数?

文章目录 如何求一个中位数&#xff1f;[295. 数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/)常规思路&#xff1a;解决思路&#xff1a; 如何求一个中位数&#xff1f; 如果输入一个数组&#xff0c;让你求中位数&#xff0c;简单的解决方法就…

不合格机器人工程讲师如何坦然面对失败

能坦然面对的重要原因是我自己从一开始就知道注定要失败的&#xff0c;或者说失败率接近99.9%。 能够从失败中成长&#xff0c;不断挑战自我&#xff0c;收获更多更大的失败也是一种体验。 失败是客观事实&#xff0c;坦然是主观上的正能量的情绪。 用客观态度对待客观事实。 …

JavaScript class和继承的原理

&#xff08;对于不屈不挠的人来说&#xff0c;没有失败这回事。——俾斯麦&#xff09; class 相关链接 MDN链接 有关类的详细描述 关于构造函数&#xff0c;原型和原型链的说明 类的概述 类是用于创建对象的模板。他们用代码封装数据以处理该数据。JS 中的类建立在原型上…

4月跳槽进字节跳动了,面试真简单...

前言: 最近金三银四跳槽季&#xff0c;相信很多小伙伴都在面试找工作&#xff0c; 怎样才能拿到大厂的offer&#xff0c;没有掌握绝对的技术&#xff0c;那么就要不断的学习&#xff0c;没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;…

Detecting Kernel Memory Leaks in Specialized Modules with Ownership Reasoning

Detecting Kernel Memory Leaks in Specialized Modules with Ownership Reasoning 背景&#xff1a; 内存泄漏&#xff1a;A memory leak happens when an allocated memory region is not released even though it will never be used again.分配的内存未被正常释放。 内存…

Java高阶数据结构 图 图的表示与遍历

高阶数据结构&#xff01; 文章目录 Java高阶数据结构 & 图的概念 & 图的存储与遍历1. 图的基本概念1.1 图的属性1.2 无向图与有向图1.3 完全图1.4 简单路径和回路1.5 子图1.6 连通图 2. 图的存储&#xff08;理论&#xff09;2.1 ※邻接矩阵2.2 邻接链表3. 图的存储&a…

新唐NUC980使用记录(5.10.y内核):u-boot linux rootfs 编译与烧录测试(基于SD1位置SD卡)

文章目录 目的基础准备烧录环境开发编译环境SD卡分区 制作和设置编译工具链制作toolchain和rootfs拷贝rootfs内容到SD卡设置编译工具链 u-boot编译与测试下载、配置与编译烧录u-boot与环境变量 linux kernel编译与测试下载、配置与编译系统运行测试 总结设备树文件内容 目的 从…

【STM32】基础知识 第十一课 sys, delay usart 文件夹

【STM32】基础知识 第十一课 sys, delay & usart 文件夹 sys 文件介绍delay 文件夹函数简介SysTickSysTick 工作原理SysTick 寄存器介绍 delay_init() 函数delay_us() 函数usart 文件夹介绍printf 的使用常用输出控制符表常用转椅字符表 半主机模式简介 sys 文件介绍 函数…

浅谈Linux epoll机制

前言 概述 epoll是一种当文件描述符的内核缓冲区非空的时候&#xff0c;发出可读信号进行通知&#xff0c;当写缓冲区不满的时候&#xff0c;发出可写信号通知的机制&#xff1b; 关键函数 int epoll_create(int size);创建 eventpoll 对象&#xff0c;并将 eventpoll 对象…

Python+Yolov5墙体桥梁裂缝识别

程序示例精选 PythonYolov5墙体桥梁裂缝识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov5墙体桥梁裂缝识别>>编写代码&#xff0c;代码整洁&#xff0c;规则&am…

【安装Nginx】

Linux上安装Nginx 文章目录 Linux上安装NginxUbuntuCentOS查看已安装的软件 Ubuntu 在 Ubuntu 上安装 Nginx 非常简单。只需按照以下步骤操作&#xff1a; 打开终端&#xff0c;更新软件包索引&#xff1a; sudo apt update安装 Nginx&#xff1a; sudo apt install nginx安…

2023世界超高清视频产业发展大会博冠8K明星展品介绍

2023世界超高清视频产业发展大会博冠8K明星展品介绍&#xff1a; 一、博冠8K全画幅摄像机B1 这是一款面向广电应用的机型&#xff0c;可适配外场ENG制作轻量化需求&#xff0c;应用于8K单边机位、新闻、专题的拍摄工作&#xff0c;也可应用于体育转播、文艺节目等特殊机位及各…