Linux操作系统学习(线程池)

news2024/11/11 5:43:51

文章目录

  • 线程池
    • 线程池原理
    • 代码示例
  • 单例模式
    • 饿汉模式
    • 懒汉模式
    • 饿汉懒汉对比
  • 其他的锁

线程池

线程池原理

​ 线程池是一种线程使用模式。在多线程应用中,若每有一个任务,线程就去调度相应的函数去创建,当任务过多时,每次都去调度且每次用完销毁,影响效率,加重CPU的负载;

​ 而线程池是提前创建好的一批线程(不固定长度),没任务时就挂起等待,有任务分配时就被唤醒,等待分配任务,但也要具体分场景,例如任务时间短,且任务量大的时候

线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量

应用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。

    但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了

  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求

  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用

    突发性大量客户请求,在没 有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程 可能使内存到达极限,出现错误

代码示例

  1. 创建一个任务队列和线程池
  2. 外部向任务队列Push任务,任务队列唤醒线程池,并Pop给线程池,线程池分配出线程去执行任务
/*****************************************任务队列*************************************************/
#pragma once
#include <string>
#include <queue>
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <cstdlib>
#include "Task.hpp"

namespace dd
{
    const int Num = 5;
    template <class T>
    class ThreadPool
    {
public:

        ThreadPool(int num = Num) 
            : _num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        ~ThreadPool() 
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        }
 		//加锁
        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }
		//解锁
        void Unlock()
        {
            pthread_mutex_unlock(&_mtx);
        }
		//判断空
        bool isEmpty()
        {
            return _task_queue.empty();
        }
        //唤醒条件变量的等待队列
        void WakeUp()
        {
            pthread_cond_signal(&_cond);
        }   
        //条件变量,挂起等待
        void wait()
        {
            pthread_cond_wait(&_cond,&_mtx);
        }

        
        //创建线程池
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < num_; ++i)
            {
                pthread_create(&tid, nullptr, Rountine, (void*)this);
            }
        }

        // 在类内要让线程执行类内成员函数是不可行的
        // 普通成员函数中,参数默认一个this指针
        // 需要调用静态方法
        static void* Rountine(void* args)
        {
            //static函数不能访问类内成员和函数,所以让外面实例化的对象,调用Init通过this传进来
            ThreadPool<T>* tp = (ThreadPool<T>*)args;
            pthread_detach(pthread_self());
            while (true)
            {
                // 从任务队列中拿任务 为了能拿任务队列里的东西,需要传递this指针
                tp->Lock();
                while (tp->isEmpty()) 
                {
                    tp->wait();
                }
                //拿任务
                T t;
                tp->Poptask(&t);
                tp->Unlock();
                //拿完任务,任务就属于调用Pop的那个线程了,不是临界资源了
                //执行任务,执行任务时就不用占着锁了
                t.Run();
            }
        }        
        
        
		//放任务,放任务时访问的是临界资源:任务队列,所以要上锁
        void Pushtask(const T& in)
        {
            Lock();
            task_queue_.push(in);
            Unlock();
            WakeUp();
        }
		//拿任务,拿任务时的上下文是有锁的,所以不需要在这里上锁
        void Poptask(T* out)
        {
            *out = task_queue_.front();
            task_queue_.pop();
        }  
        
private:
        int _num;// 线程池中的线程数量
        std::queue<T> _task_queue;
        pthread_mutex_t _mtx;
        pthread_cond_t _cond;
    };
}

/**********************************************************************************************/
#pragma once
#include <iostream>
#include <pthread.h>
namespace dd
{

class Task
{
public:
    Task()
    {}
    Task(int x,int y,char op)
        :_x(x)
        ,_y(y)
        ,_op(op)
    {}

    int Run()
    {
        int ret = 0;
        switch(_op)
        {
            case '+':
                ret = _x + _y;
                break;
            case '-':
                ret = _x - _y;
                break;
            case '*':
                ret = _x * _y;
                break;
            case '/':
                ret = _x / _y;
                break;
            case '%':
                ret = _x % _y;
                break;
            default:
                break;
        }
        //std::cout << _x << _op << _y << " = " << ret << std::endl;
        std::cout << pthread_self() << ": " << _x << _op << _y << " = " << ret << std::endl;
    }
private:
    int _x;
    int _y;
    char _op;

};


}

/**********************************************************************************************/
#include "thread_pool.hpp"
#include "Task.hpp"
using namespace dd;
int main()
{
    ThreadPool<Task>* tp = new ThreadPool<Task>();
    //创建线程
    tp->InitThreadPool();
    srand((unsigned int)time(nullptr));
    while (true)
    {
        // 主线程push任务 真实情况中 任务一般是从网络来的
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
        tp->pushtask(t);
        // sleep(1);
    }

    return 0;
}

单例模式

单例模式是指在整个系统生命周期内,保证在内存中,一个类只会创建且仅创建一次对象的设计模式,确保该类的唯一性

  • 可以节省内存,节约资源,对于一般频繁创建和销毁对象的可以使用单例模式

  • 可以避免对资源的多重占用

    ​ 例如在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据;也就是这么大的数据在内存只需要有一份,避免数据冗余(例如动态库)

单例类的特点

  • 构造函数和析构函数为私有类型,目的是禁止外部构造和析构
  • 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性
  • 类中的成员变量是静态的,静态的无论实例化多少对象,都只有(公用)一个静态成员变量
  • 类中要有一个获取实例的静态方法,可以全局访问

单例模式可以分为两种模式:饿汉模式、懒汉模式

饿汉模式

注意几点细节:

  • 需要将构造函数,拷贝构造函数,赋值运行符重载函数屏蔽,防止在外部实例化对象

  • 在类中创建一个静态类对象,该静态类对象,会在程序运行时创建,需要在类外初始化

  • 类中写一个接口(静态),返回静态类对象的地址。因为无法调用拷贝构造,不能直接返回类对象

template <typename T>
class Singleton 
{
	static T data;
    //static Singleton<T> data
    SingelTon() = delete;
	SingelTon(const SingelTon& st) = delete;;
	SingelTon& operator=(const SingelTon& st) = delete;
    
public:
	static T* GetInstance() 
    {
		return &data;
	}
};
template<class T>
T Singleton::data = /**/;
//Singleton<T> Singleton::data = /**/;

​ 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭;也就是系统一运行,就初始化创建实例,当需要时,直接调用即可。这种方式本身就可以保证线程安全,没有多线程的线程安全问题

懒汉模式

懒汉模式注意的细节:

  • 需要将构造函数,拷贝构造函数,赋值运行符重载函数屏蔽,防止在外部实例化对象。
  • 在类中成员包含一个类对象指针,而不是对象。
  • 类中写一个接口(静态),用户返回创建后的对象(但只能调用一次)
  • 因为是需要时才会创建,所以需要考虑线程安全问题,保证多线程下只能创建一个对象
template <class T>
class Singleton 
{
	volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
	static std::mutex lock;
    Singleton()
    {
        pthread_mutex_init(&lock,nullptr);
    }
    ~Singleton()
    {
        pthread_mutex_destroy(&lock);
    }
    Singleton(const Singleton<T>& sl) = delete;
    Singleton<T>& operator=(Singleton<T> sl) = delete;
    
public:
	static T* GetInstance() 
    {
		if (inst == NULL) 
        { 				// 双重判定空指针, 降低争锁冲突, 提高性能.
			lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
			if (inst == NULL) 
            {
				inst = new T();
			}
			lock.unlock();
		}
		return inst;
	}
};
//类外初始化静态成员
template <class T>
T* Singleton::inst = nullptr;

​ 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式;也就是系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例这种方式要考虑线程安全问题

饿汉懒汉对比

饿汉模式

  • 优点:

    • 实现简单,不需要考虑线程安全问题。
  • 缺点:

    • 启动慢。如果实例的对象占用资源很多,在启动时需要加载。

    • 如果多个单例类对象,在程序启动时实例对象的顺序不确定。如果对象之间有依赖关系,就麻烦了。

懒汉模式

  • 优点:

    • 启动快。在需要时才会实例化对象,加载资源。

    • 多个单例类对象,实例化的顺序可以确定。取决于调用类的函数的顺序。

  • 缺点:

    • 实现复杂。需要考虑线程安全问题。

其他的锁

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

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

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

相关文章

CCF大数据专家委员会十周年纪念庆典纪实:拥抱数字时代,展望科技未来

山河远阔&#xff0c;奋进十年&#xff0c;作为国内大数据领域最权威的学术组织&#xff0c;CCF大数据专家委员会&#xff08;以下简称“大专委”&#xff09;不忘初心&#xff0c;凝心聚力&#xff0c;见证并推动了过去10年来大数据技术生态在中国的建立、发展和成熟。 2023年…

HBase安装

文章目录一、安装Zookeeper二、安装HBase三、启动Hbase步骤四、关闭进程顺序五、简单使用Hbase在开始安装HBase之前&#xff0c;请确保您已经安装了Java运行环境和Hadoop分布式文件系统。如果您还没有安装这些软件&#xff0c;请查看之前博文介绍安装。 HBase安装包&#xff1a…

谷歌广告投放步骤流程是什么?一文带你全方位了解实操细节

谷歌&#xff0c;大家都不陌生吧&#xff0c;一个人们很常用的搜索引擎。而谷歌还可以打广告&#xff0c;即谷歌广告&#xff0c;那这跟跨境电商有什么关心呢&#xff1f;东哥告诉大家&#xff0c;关系大了去了&#xff0c;毕竟如果用户搜索与我们相关的关键词&#xff0c;就有…

streaming systems 第二章

The What, Where, When, and How of Data Processing 第一章主要关注三个领域:术语&#xff0c;准确定义我使用重载术语时的意思&#xff0c;如“流”;批处理和流处理&#xff0c;比较两种类型系统的理论能力&#xff0c;并假设使流处理系统超越批处理系统只有两件事是必要的:…

【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程

【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程 文章目录【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程一、Channel & Buffer二、Selector三、ByteBuffer1.ByteBuffer 使用方式2.ByteBuffer 结构3.ByteBuffer 常用方法4.Scattering Reads4.Gathering Write5…

Java SPI机制了解与应用

1. 了解SPI机制 我们在平时学习和工作中总是会听到Java SPI机制&#xff0c;特别是使用第三方框架的时候&#xff0c;那么什么是SP机制呢&#xff1f;SPI 全称 Service Provider Interface&#xff0c;是 Java 提供的一套用来被第三方实现或者扩展的接口&#xff0c;它可以用来…

【java】Java连接mysql数据库及mysql驱动jar包下载和使用

文章目录JDBCJDBC本质&#xff1a;JDBC作用&#xff1a;跟数据库建立连接发送 SQL 语句返回处理结果操作流程和具体的连接步骤如下&#xff1a;操作步骤&#xff1a;需要导入驱动jar包 mysql-connector-java-8.0.22.jar注册驱动获取数据库连接对象 Connection定义sql获取执行sq…

第十届CCF大数据与计算智能大赛总决赛暨颁奖典礼在苏州吴江顺利举办

2月24日-25日&#xff0c;中国计算机学会&#xff08;CCF&#xff09;主办、苏州市吴江区人民政府支持&#xff0c;苏州市吴江区工信局、吴江区东太湖度假区管理办公室、苏州市吴江区科技局、CCF大数据专家委员会、CCF自然语言处理专业委员会、CCF高性能计算专业委员会、CCF计算…

深度剖析C语言符号篇

致前行的人&#xff1a; 人生像攀登一座山&#xff0c;而找寻出路&#xff0c;却是一种学习的过程&#xff0c;我们应当在这过程中&#xff0c;学习稳定冷静&#xff0c;学习如何从慌乱中找到生机。 目录 1.注释符号&#xff1a; 2.续接符和转义符&#xff1a; 3.回车与换行…

独立产品灵感周刊 DecoHack #050 - 基于 ChatGPT 的 API 能做什么产品

ChatGPT 的 API 3.5 开放过去了一周&#xff0c;果然和预想的一样&#xff0c;接入 API 的产品爆炸式地出现了非常多&#xff0c;很多产品已经做的非常好用了&#xff0c;在很多场景方便了太多。本期整理了一些做的很不错的产品。 本周刊记录有趣好玩的独立产品设计开发相关内容…

一款好的风险管理软件可以做什么

风险管理软件哪个好&#xff1f;使用Zoho Projects易于使用的项目风险管理软件&#xff0c;最大限度地减少收入损失并快速调整您的投资组合&#xff0c;保护您的项目投资。Zoho Projects的高级风险管理软件可在您最需要的时候安全的保护您的业务。使用Zoho Projects强大的风险管…

【springmvc】视图

SpringMVC的视图 SpringMVC中的视图是View接口&#xff0c;视图的作用渲染数据&#xff0c;将模型Model中的数据展示给用户 SpringMVC视图的种类很多&#xff0c;默认有转发视图和重定向视图 当工程引入jstl的依赖&#xff0c;转发视图会自动转换为JstlView 若使用的视图技…

内核性能评估测试及具体修改操作步骤记录

步骤记录前言一、查看环境配置二、LRU缓存空间调整三、进程扫描时间间隔四、与其他内核对比的工作负载测试&#xff08;另一个内核的编译&#xff09;总结前言 记录的相关操作有&#xff1a;查看服务器硬件环境、LRU缓存大小修改、内核命名、内核编译以及进程执行周期的设置。…

操作系统——17.进程同步与进程互斥

这篇文章我们来讲一下进程同步与进程互斥的问题 目录 1.概述 2.什么是进程同步 3.什么是进程互斥 4.小结 1.概述 首先&#xff0c;我们来看一下本篇内容的大体框架&#xff1a; 2.什么是进程同步 知识点回顾:进程具有异步性的特征。 异步性是指&#xff0c;各并发执行的…

vue里使用driver.js实现项目功能向导指引

介绍 https://github.com/kamranahmedse/driver.js driver.js 是一个轻量级、无依赖的原生JavaScript引擎&#xff0c;在整个页面中驱动用户的注意力&#xff0c;强大的、高度可定制的原生JavaScript引擎&#xff0c;无外部依赖&#xff0c;支持所有主流浏览器。 安装 npm …

CHAPTER 1 Linux Filesystem Management

Linux Filesystem Management1 文件系统是什么2 文件系统的组成3 inode详解1. inode到底是什么2. inode的内容3. inode的大小4. inode的号码5. 硬链接6. 软链接4 存储区域5 常见文件系统的类型1. 根文件系统2. 虚拟文件系统3. 真文件系统4. 伪文件系统5. 网络文件系统1 文件系统…

2023年网络安全最应该看的书籍,弯道超车,拒绝看烂书

学习的方法有很多种&#xff0c;看书就是一种不错的方法&#xff0c;但为什么总有人说&#xff1a;“看书是学不会技术的”。 其实就是书籍没选对&#xff0c;看的书不好&#xff0c;你学不下去是很正常的。 一本好书其实不亚于一套好的视频教程&#xff0c;尤其是经典的好书…

Java 面试常见项目问题回答

之前整理了好几期&#xff0c;我面试时遇到的面试候选人&#xff0c;我是如何我去筛选的&#xff0c;这一期&#xff0c;我们来看下一些 面试常问的业务性的问题 你们公司权限认证是如何实现的? 这其实是个通用性的问题&#xff0c;大部分公司 小型公司&#xff0c;或者中型公…

Praat之基频分析

Praat之基频分析 测量基频F0的方法 自相关 Autocorrelation&#xff08;易出现pitch-halving\pitch-double&#xff09;窄带谱图 Narrowband spectrogram&#xff08;第一谐波就是基频&#xff09;倒谱分析 Cepstral analysis测量声门波 glottal pluse&#xff08;通过波形&a…

拥抱数字时代 | 第十六届中国大数据技术大会成功举办

2月26日&#xff0c;由中国计算机学会&#xff08;CCF&#xff09;主办&#xff0c;苏州市吴江区人民政府支持&#xff0c;CCF大数据专家委员会、苏州市吴江区工信局、吴江区东太湖度假区管理办公室、苏州市吴江区科技局、苏州大学未来科学与工程学院、DataFountain数联众创承办…