[Linux]------线程池的模拟实现和读者写者锁问题

news2024/10/7 18:26:22

文章目录

  • 前言
  • 一、线程池
  • 二、线程安全的单例模式
    • 什么是单例模式
    • 什么是设计模式
    • 单例模式的特点
  • 三、STL,智能指针和线程安全
    • STL中的容器是否是线程安全的?
    • 智能指针是否是线程安全的?
  • 四、其他常见的各种锁
  • 五、读者写者问题
    • 读写锁
    • 读写锁接口
      • 初始化和销毁
      • 加锁和解锁
    • 读写锁的案例
  • 总结


前言


正文开始!

一、线程池

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

线程池的应用场景:

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

线程池的种类:
线程池示例:

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

在这里插入图片描述

//log.hpp
#pragma once

#include<iostream>
#include<ctime>
#include<pthread.h>

std::ostream& Log()
{
   std::cout << "For Debug |" << " timestamp: " << (uint64_t)time(nullptr) 
   << " | "<<"Thread: ["<<pthread_self()<<"] | ";
    return std::cout;
}
//Task.hpp
#pragma once
#include <iostream>
#include <string>

class Task
{
public:
    Task(int one = 0, int two = 0, char op = '+')
        : _elemOne(one), _elemTwo(two), _operator(op)
    {}
    int operator()()
    {
        return run();
    }
    int run()
    {
        int result = 0;
        switch (_operator)
        {
        case '+':
            result = _elemOne + _elemTwo;
            break;
        case '-':
            result = _elemOne - _elemTwo;
            break;
        case '*':
            result = _elemOne * _elemTwo;
            break;
        case '/':
        {
            if (_elemTwo == 0)
            {
                std::cout << "div zero,abort" << std::endl;
                result = -1;
            }
            else
            {
                result = _elemOne / _elemTwo;
            }
        }
        break;
        case '%':
        {
            if (_elemTwo == 0)
            {
                std::cout << "mod zero,abort" << std::endl;
                result = -1;
            }
            else
            {
                result = _elemOne % _elemTwo;
            }
        }
        break;
        default:
            std::cout << "非法操作: " << _operator << std::endl;
            break;
        }
        return result;
    }
    void get(int& one,int& two,char& op)
    {
        one=_elemOne;
        two=_elemTwo;
        op=_operator;
    }
private:
    int _elemOne;
    int _elemTwo;
    char _operator;
};
//ThreadPool.hpp
template <class T>
class ThreadPool
{
public:
    ThreadPool(int threadNum = gThreadNum)
        : _threadNum(threadNum), _isStart(false)
    {
        assert(_threadNum > 0);
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    //类内成员,成员函数都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            T tmp = tp->pop();
            tp->unlockQueue();

            //for debug
            int one, two;
            char oper;
            tmp.get(one, two, oper);
            //规定所有的任务都必须要有一个run方法
            Log() << "新线程完成计算任务: " << one << oper << two << "="
                  << tmp.run() << "\n";
        }
    }
    void start()
    {
        assert(!_isStart);
        for (int i = 0; i < _threadNum; i++)
        {
            pthread_t tmp;
            pthread_create(&tmp, nullptr, threadRoutine, this);
        }
        _isStart = true;
    }
    void push(const T &in)
    {
        lockQueue();
        _taskQueue.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }

private:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool haveTask() { return !_taskQueue.empty(); }
    void waitForTask() { pthread_cond_wait(&_cond, &_mutex); }
    void choiceThreadForHandler() { pthread_cond_signal(&_cond); }
    T pop()
    {
        T tmp = _taskQueue.front();
        _taskQueue.pop();
        return tmp;
    }

private:
    bool _isStart;
    int _threadNum;
    queue<T> _taskQueue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

};

//main.cc
int main()
{
    unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    tp->start();
    srand((unsigned long)time(nullptr)^getpid()^pthread_self());
    //派发任务的线程
    while(true)
    {
        int one=rand()%50;
        int two=rand()%20;
        char oper=opers[rand()%opers.size()];
        Log()<<"派发计算任务: "<<one<<oper<<two<<"=?"<<"\n";
        Task t(one,two,oper);
        tp->push(t);
        sleep(1);
    }
    return 0;
}

派发任务慢一点
在这里插入图片描述
派发任务不加访问控制!
打出来的东西很乱!

在这里插入图片描述

二、线程安全的单例模式

什么是单例模式

单例模式是一种"经典的,常用的,常考的"设计模式

什么是设计模式

IT行业这么或,涌入的人很多。俗话说林子大了啥鸟都有。大佬和菜鸡们两极分化越来越严重。为了让菜鸟们不太拖大佬的后退,于是大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是设计模式

单例模式的特点

某些类,应该只有一个对象(实例),就称之为单例。
在很多服务器开发场景中,经常需要让服务器加载很多的数据(上百G)到内存中。此时往往需要用一个单例的类来管理这些数据。

线程池改为单例模式

#pragma once

#include <iostream>
#include <queue>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include<sys/prctl.h>
#include "Log.hpp"
#include "Task.hpp"
#include "lock.hpp"
using namespace std;

const int gThreadNum = 5;

//设计为懒汉模式
template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum)
        : _threadNum(threadNum), _isStart(false)
    {
        assert(_threadNum > 0);
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance)//仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex);//进入代码块,加锁,退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }
        return instance;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    //类内成员,成员函数都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        prctl(PR_SET_NAME,"follower");
        while (true)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            T tmp = tp->pop();
            tp->unlockQueue();

            // for debug
            int one, two;
            char oper;
            tmp.get(one, two, oper);
            //规定所有的任务都必须要有一个run方法
            Log() << "新线程完成计算任务: " << one << oper << two << "="
                  << tmp.run() << "\n";
        }
    }
    void start()
    {
        assert(!_isStart);
        for (int i = 0; i < _threadNum; i++)
        {
            pthread_t tmp;
            pthread_create(&tmp, nullptr, threadRoutine, this);
        }
        _isStart = true;
    }
    void push(const T &in)
    {
        lockQueue();
        _taskQueue.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }

private:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool haveTask() { return !_taskQueue.empty(); }
    void waitForTask() { pthread_cond_wait(&_cond, &_mutex); }
    void choiceThreadForHandler() { pthread_cond_signal(&_cond); }
    T pop()
    {
        T tmp = _taskQueue.front();
        _taskQueue.pop();
        return tmp;
    }

private:
    bool _isStart;
    int _threadNum;
    queue<T> _taskQueue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    static ThreadPool<T> *instance;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
#include"ThreadPool.hpp"
#include<memory>
#include<ctime>

const string opers="+-*/%";

int main()
{
    prctl(PR_SET_NAME,"master");
    unique_ptr<ThreadPool<Task>> tp(ThreadPool<Task>::getInstance());
    tp->start();
    srand((unsigned long)time(nullptr)^getpid()^pthread_self());
    //派发任务的线程
    while(true)
    {
        int one=rand()%50;
        int two=rand()%20;
        char oper=opers[rand()%opers.size()];
        Log()<<"派发计算任务: "<<one<<oper<<two<<"=?"<<"\n";
        Task t(one,two,oper);
        tp->push(t);
        sleep(1);
    }
    return 0;
}

三、STL,智能指针和线程安全

STL中的容器是否是线程安全的?

不是!
原因是STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,回对性能造成巨大的影响。
而且对于不同的容器,加锁的方式不同,性能可能也不同(例如hash表的锁表和锁桶)
因此STl默认不是线程安全的。如果需要在多线程下使用,往往需要调用者自行保证线程安全。

智能指针是否是线程安全的?

对于unqiue_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。
对于shared_ptr,多个对象需要公用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效,原子的引用操作计数。

四、其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据的时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CSA操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败即重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁…

在这里插入图片描述

自旋锁的接口和互斥锁的接口使用方法是类似的!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

五、读者写者问题

读写锁

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

写者和写者是互斥关系!
读者和读者没有关系!
读者和写者是互斥和关系!

在这里插入图片描述
**注意:**写独占,读共享,读锁优先级高

读者写者 VS 生产者消费者本质区别:消费者会把数据拿走,而读者不会!
在这里插入图片描述

读写锁接口

初始化和销毁

在这里插入图片描述

加锁和解锁

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

读写锁的案例

读者写者进行操作的时候,读者非常多,频率特别高。写者比较少,频率高!
默认读写锁是读者优先的!
这样就可能会造成写者的饥饿问题!

读者优先

写者优先

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

int board = 0;

pthread_rwlock_t rw;

void *reader(void *args)
{
    const char *name = static_cast<const char *>(args);
    sleep(2);
    cout << "sleep down" << endl;
    cout << "run..." << endl;
    while (true)
    {
        pthread_rwlock_rdlock(&rw);
        cout << "reader read: " << board << endl;
        pthread_rwlock_unlock(&rw);
    }
}
void *writer(void *args)
{
    const char *name = static_cast<const char *>(args);
    while (true)
    {
        pthread_rwlock_wrlock(&rw);
        board++;
        cout << "I am writer" << endl;
        pthread_rwlock_unlock(&rw);
    }
}

int main()
{
    pthread_rwlock_init(&rw, nullptr);
    pthread_t r1, r2, r3, r4, r5, w;
    pthread_create(&r1, nullptr, reader, (void *)"reader");
    pthread_create(&r2, nullptr, reader, (void *)"reader");
    pthread_create(&r3, nullptr, reader, (void *)"reader");
    pthread_create(&r4, nullptr, reader, (void *)"reader");
    pthread_create(&r5, nullptr, reader, (void *)"reader");
    pthread_create(&w, nullptr, writer, (void *)"writer");

    pthread_join(r1, nullptr);
    pthread_join(r2, nullptr);
    pthread_join(r3, nullptr);
    pthread_join(r4, nullptr);
    pthread_join(r5, nullptr);
    pthread_join(w, nullptr);
    pthread_rwlock_destroy(&rw);

    return 0;
}

这个现象不是很好的去控制,我也就不给大家操作了!


总结

至此,Linux的线程内容结束!下章开始网络的原理和网络编程!
(本章完!)

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

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

相关文章

云开发智能家居客户案例详解(内附拓扑图)

万物互联&#xff0c;大至全世界&#xff0c;小至一间房&#xff0c;物联网和云计算技术的高速发展使得住宅变得愈发智能化。 在“互联网”时代&#xff0c;智能家居开始走入千家万户&#xff0c;不断提升着家居生活的安全性、舒适型、便利性和环保性&#xff0c;逐渐变成人们…

Linux 用户权限

用户权限1、访问权限2、chmod 命令3、chown 命令4、chgrp命令5、权限掩码6、lsattr 命令7、chattr命令8、文件的特别权限suid权限set位权限粘滞位权限&#xff08;Sticky&#xff09;9、ACL访问控制列表setfacl命令getfacl命令示例10、sudo11、SELinux1、访问权限 shell在创建…

SpringBoot2学习笔记--入门及HelloWorld

SpringBoot2学习笔记--入门及HelloWorld1 系统要求1.1、maven设置2、HelloWorld2.1、创建maven工程2.2、引入依赖2.3、创建主程序2.4、编写业务2.5、测试2.6、简化配置2.7、简化部署1 系统要求 ● Java 8 & 兼容java14 . ● Maven 3.3 ● idea 2019.1.2 1.1、maven设置 …

Java版 剑指offer笔记(一)

1.数组中重复的数字 思路1&#xff1a; 使用哈希表&#xff0c;哈希表是一种根据关键码&#xff08;key&#xff09;直接访问值&#xff08;value&#xff09;的一种数据结构。而这种直接访问意味着只要知道key就能在O(1)时间内得到value&#xff0c;因此哈希表常用来统计频率…

软件测试有哪些常用的测试方法?

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

4.MyBatis映射

需求分析 1.订单商品数据模型 (1).表 用户表user:记录了购买商品的用户信息 订单表orders:记录了用户所创建的订单信息 订单明细表orderdetail:记录了订单的详细信息 商品表item:记录了商品详细信息 (2).表与表之间的业务关系 在分析表与表之间的业务关系时&#xff0c;需要建…

Nginx的反向代理和负载均衡

Nginx&#xff1a; Nginx作为面试中的大…小头目&#xff0c;自然是不能忽视的&#xff0c;而以下两点就是它能成为面试中头目的招牌。 反向代理和负载均衡 在此之前&#xff0c;我们先对Nginx做一个简单的了解 Nginx概述&#xff1a; Nginx (engine x) 是一个高性能的HTTP…

Ansible——inventory 主机清单

Ansible——inventory 主机清单Ansible——inventory 主机清单inventory简介ansible配置文件的优先级ansible命令常用参数主机清单文件hosts&#xff08;/etc/ansible/hosts&#xff09;通过列表的方式标识主机范围指定主机端口使用主机名表示主机范围inventory 中的变量主机变…

JS 数组方法 every 和 some 的区别

1. 前言 2. every 和 some 相同点 3. every 和 some 的区别 4. every 和 some 总结 1. 前言 JS 数组方法 every 和 some 的区别 &#xff1f; 这是某位前端玩家遇到的面试题 特定场景合理的使用 JS 方法&#xff0c;不仅可以减少我们的代码量&#xff0c;还能更轻松的阅读…

宇航服,真正的“科技”与“狠活”!

千百年的探索仰望和摘星的遐想&#xff0c;已照进现实&#xff0c;浩瀚的天宫&#xff0c;我们亦可置身其中。 北京时间2022年12月4日20时09分&#xff0c;神舟十四号载人飞船返回舱在东风着陆场成功着陆&#xff0c;标志着太空出差183天的宇航员正式回家&#xff01;据悉&…

基于PCA 和迭代 Canny Edge皮肤病变分割算法研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

NLP_learning 中文基本任务与处理(分词、停用词、词性标注、语句依存分析、关键词抽取、命名实体识别)介绍、jieba工具库

文章目录1、分词2、停用词和N-gram停用词N-gram3、 更多任务&#xff08;词性标注、依赖分析、NER、关键词抽取&#xff09;词性标注句法依存分析命名实体识别关键词抽取4、 jieba工具库使用&#xff08;1&#xff09;基本分词函数与用法&#xff08;2&#xff09;词性标注&…

【LeetCode】C++:数组类算法-双索引技巧-对撞指针

目录 167. 两数之和 II - 输入有序数组 125.验证回文串 345.反转字符串中的元音字母 11.盛最多水的容器 209.长度最小的数组 167. 两数之和 II - 输入有序数组 给你一个下标从1开始的整数数组 numbers &#xff0c;该数组已按非递减顺序排列 &#xff0c;请你从数组中找出…

视频剪辑软件哪个好用?快把这些软件收好

现如今自媒体行业正在如火如荼的发展&#xff0c;越来越多的人加入进视频剪辑的队伍中。小伙伴们也有萌生想要剪辑视频的念头吗&#xff1f;大家是否苦于不知道该如何视频剪辑呢&#xff1f;为了帮助大家解决这个问题&#xff0c;今天我就来为大家教几种不错的剪辑方法&#xf…

YOLOv5图像分割中的NMS处理

在上一篇文章YOLOv5图像分割--SegmentationModel类代码详解有讲到图像经过YOLOv5网络后得到的输出形式&#xff0c;主要是调用了BaseModel类下的forward得到的输出&#xff0c;输出的shape为【batch,25200,117】&#xff0c;这里的25200相当于总的anchors数量【以640*640的输入…

vuex原理和下载

vuex&#xff1a;状态管理模式 vue全家桶&#xff1a;vue-cli&#xff08;脚手架&#xff09;、vue-router&#xff08;路由管理器&#xff09;、vuex&#xff08;状态管理模式&#xff09; 原理图示&#xff1a; 原理描述&#xff1a; vuex在vue组件外面进行组件状态的管理…

引用的小细节内联函数

1.引用的细节 引用&#xff0c;简单来说就是“取别名”。既然是别名&#xff0c;那么引用就一定具有以下的特点 引用在定义时必须初始化。 就好比起别名起码得告诉别人是给谁起的别名吧 一个变量可以有多个引用 就好比一个人可以有多个别名。比如张某某&#xff0c;有两个外号…

智慧农业创造新兴业态,推动农业产业现代化步伐

农业是国民经济的基础&#xff0c;在国家经济发展中起着不可替代的作用&#xff0c;随着物联网、人工智能、信息技术的快速发展&#xff0c;农业逐渐走向智能化、现代化和自动化&#xff0c;智慧农业已经深入到农业生产的各个环节&#xff0c;成为了现代农业发展新的方向。 所谓…

JAVA12_08学习总结(CSS)

今日内容 1. frameset 框架集标签frameset框架集标签不能放在body中rows--划分页面为上下部分cols--划分页面为左右部分框架标签frame框架的名称name属性<frame src"#" name"#" />src后代表这个框架中打开的页面链接name后代表这个被打开页面的nam…

JavaScript -- 11. BOM及常用对象介绍

文章目录BOM对象1 BOM2 navigator3 location3.1 常用方法3.2 url各部分名称4 historyBOM对象 1 BOM 浏览器对象模型 BOM为我们提供了一组对象&#xff0c;通过这组对象可以完成对浏览器的各种操作 BOM对象&#xff1a; Window —— 代表浏览器窗口&#xff08;全局对象&…