【Linux操作系统】多线程(二)

news2024/11/23 11:01:47

文章目录

    • 4. 线程池
    • 5. 单例模式
      • 5.1 饿汉模式
      • 5.2 懒汉模式
    • 6. STL、智能指针和线程安全
      • 6.1 STL中的容器是否是线程安全的
      • 6.2 智能指针是否是线程安全的
      • 6.3 其他常见的各种锁
    • 7. 读者写者模型
      • 7.1 基本概念
      • 7.2 读写锁
      • 7.3 基本操作
      • 7.4 优先级

4. 线程池

介绍

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

应用场景

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

示例

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

Task.hpp

#pragma

#include <iostream>

namespace ns_task
{
    class Task
    {
    private:
        int _x;
        int _y;
        char _op;

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

    public:
        int run()
        {
            int res = 0;
            switch (this->_op)
            {
            case '+':
                res = this->_x + this->_y;
                break;
            case '-':
                res = this->_x - this->_y;
                break;
            case '*':
                res = this->_x * this->_y;
                break;
            case '/':
                res = this->_x / this->_y;
                break;
            case '%':
                res = this->_x % this->_y;
                break;
            default:
                std::cout << "Calculation error..." << std::endl;
                break;
            }
            std::cout << "The calculation result is " << res << std::endl;
            return res;
        }
    };
}

ThreadPool.hpp

#pragma once

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

namespace ns_threadpool
{
    const int default_num = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;
        pthread_mutex_t _mtx;
        pthread_cond_t _cond;

    public:
        ThreadPool(int num = default_num) : _num(num)
        {
            pthread_mutex_init(&this->_mtx, nullptr);
            pthread_cond_init(&this->_cond, nullptr);
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&this->_mtx);
            pthread_cond_destroy(&this->_cond);
        }
        void lock()
        {
            pthread_mutex_lock(&this->_mtx);
        }
        void unlock()
        {
            pthread_mutex_unlock(&this->_mtx);
        }
        bool isEmpty()
        {
            return this->_task_queue.empty();
        }
        void wait()
        {
            pthread_cond_wait(&this->_cond, &this->_mtx);
        }
        void wake()
        {
            pthread_cond_signal(&this->_cond);
        }

    public:
        // 线程无法直接执行类内的方法,因为类中的方法参数列表会隐含一个this指针,需要定义为静态成员函数
        static void *runtime(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *_this = (ThreadPool<T> *)args;

            while (true)
            {
                _this->lock();
                while (_this->isEmpty())
                {
                    _this->wait();
                }

                T *task = new T();
                _this->popTask(task);
                _this->unlock();
                task->run();
            }
        }
        void initThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < this->_num; i++)
            {
                pthread_create(&tid, nullptr, runtime, (void *)this);
            }
        }
        void pushTask(const T &in)
        {
            lock();
            this->_task_queue.push(in);
            unlock();
            this->wake();
        }
        void popTask(T *out)
        {
            *out = this->_task_queue.front();
            this->_task_queue.pop();
        }
    };
}

Main.cc

#include "Task.hpp"
#include "ThreadPool.hpp"

#include <ctime>

using namespace ns_threadpool;
using namespace ns_task;

int main()
{
    srand((long long)time(nullptr));

    ThreadPool<Task> *tp = new ThreadPool<Task>();
    tp->initThreadPool();

    while (true)
    {
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
        tp->pushTask(t);

        sleep(1);
    }

    return 0;
}

5. 单例模式

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

  • 语义上只有一个
  • 该对象内部存在大量的空间,保存了大量的数据,如果允许该对象存在多份,或者允许发生各种拷贝,内存中会存在很多冗余数据

5.1 饿汉模式

在加载对象时候,对象就会创建实例

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

5.2 懒汉模式

懒汉方式最核心的思想是"延时加载",从而能够优化服务器的启动速度

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

但这样的懒汉模式存在一个严重的问题——线程不安全,第一次调用GetInstance的时候,如果多个线程同时调用,可能会创建出多份T对象的实例,这里第一次调用时的T对象也是临界资源,如果后续再次调用,就没有问题

线程安全的懒汉方式实现的线程池

ThreadPool.hpp

#pragma once

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

namespace ns_threadpool
{
    const int default_num = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;
        pthread_mutex_t _mtx;
        pthread_cond_t _cond;
        static ThreadPool<T> *instance;

    private:
        ThreadPool(int num = default_num) : _num(num)
        {
            pthread_mutex_init(&this->_mtx, nullptr);
            pthread_cond_init(&this->_cond, nullptr);
        }
        ThreadPool(const ThreadPool<T> &tp) = delete;
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;

    public:
        static ThreadPool<T> *getInstance()
        {
            pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            if (instance == nullptr)
            {
                pthread_mutex_lock(&mtx);
                if (instance == nullptr)
                {
                    instance = new ThreadPool<T>();
                    instance->initThreadPool();
                }
                pthread_mutex_unlock(&mtx);
            }
            return instance;
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&this->_mtx);
            pthread_cond_destroy(&this->_cond);
        }
        void lock()
        {
            pthread_mutex_lock(&this->_mtx);
        }
        void unlock()
        {
            pthread_mutex_unlock(&this->_mtx);
        }
        bool isEmpty()
        {
            return this->_task_queue.empty();
        }
        void wait()
        {
            pthread_cond_wait(&this->_cond, &this->_mtx);
        }
        void wake()
        {
            pthread_cond_signal(&this->_cond);
        }

    public:
        // 线程无法直接执行类内的方法,因为类中的方法参数列表会隐含一个this指针,需要定义为静态成员函数
        static void *runtime(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *_this = (ThreadPool<T> *)args;

            while (true)
            {
                _this->lock();
                while (_this->isEmpty())
                {
                    _this->wait();
                }

                T *task = new T();
                _this->popTask(task);
                _this->unlock();
                task->run();
            }
        }
        void initThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < this->_num; i++)
            {
                pthread_create(&tid, nullptr, runtime, (void *)this);
            }
        }
        void pushTask(const T &in)
        {
            lock();
            this->_task_queue.push(in);
            unlock();
            this->wake();
        }
        void popTask(T *out)
        {
            *out = this->_task_queue.front();
            this->_task_queue.pop();
        }
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::instance = nullptr;
}

6. STL、智能指针和线程安全

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

不是

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

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

对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题

对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题,但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效、原子的操作引用计数

6.3 其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,被阻塞挂起
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作
    • 版本号机制:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功
    • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等,如果相等则用新值更新,若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试
  • 自旋锁:线程会反复检查锁变量是否可用,由于线程在这一过程中保持执行,因此是一种忙等待,一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁
    • 公平锁:多个线程按照申请锁的顺序来获取锁
    • 非公平锁:多个线程获取的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能会造成优先级后传或者饥饿想象

考虑线程访问临界资源的时长问题,因为将线程挂起等待是有成本的

  • 如果花费的时间非常短,就比较适合自旋锁
  • 如果花费的时间比较长,就比较适合挂起等待锁

自旋锁

  • 初始化

    int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
    
  • 销毁

    int pthread_spin_destroy(pthread_spinlock_t *lock);
    
  • 加锁与解锁

    int pthread_spin_lock(pthread_spinlock_t *lock);
    
    int pthread_spin_trylock(pthread_spinlock_t *lock);
    

7. 读者写者模型

7.1 基本概念

读者写者模型

  • 对数据,大部分的操作是读取,少量的操作是写入
  • 判断依据是,进行数据读取(消费)的一端,是否会将数据取走,如果不取走,就可以考虑读者写者模型

321原则

  • 三种关系

    • 读者和读者:没有关系

      生产者消费者模型 vs 读者写者模型

      不一样的原因:读者不会取走资源,而消费者会拿走数据

    • 写者和写者:互斥、同步

    • 读者和写者:互斥关系

  • 两种角色:读者和写者,由线程承担

  • 一个交易场所:一段缓冲区(自己申请的,或者STL容器)

7.2 读写锁

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

在这里插入图片描述

注意:写独占,读共享,读锁优先级高

7.3 基本操作

  • 初始化

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
    *restrict attr);
    
  • 销毁

    int 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);
    
  • 设置读写优先

    int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
    
    • pref有三种选择
      • PTHREAD_RWLOCK_PREFER_READER_NP:默认设置,读者优先,可能会导致饥饿情况
      • PTHREAD_RWLOCK_PREFER_WRITER_NP:写者优先,目前有 BUG,导致表现行为和
      • PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP:写者优先,但写者不能递归加锁

7.4 优先级

读者优先:读者和写者同时到来的时候,让读者先进入访问

写者优先:当读者和写者同时到来的时候,比当前写者晚来的所有读者,都不要再进入临界区访问了,等临界区中没有读者的时候,让写者先写入

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

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

相关文章

论文分享 | MnTTS2: 开源的多说话人蒙古语TTS数据集

本次分享内蒙古大学蒙古文信息处理重点实验室、蒙古文智能信息处理技术国家地方联合工程研究中心及语音理解与生成实验室 (S2LAB) 共同发布的开源多说话人蒙古语语音合成数据集及其基线模型。相关论文《MnTTS2: An Open-Source Multi-Speaker Mongolian Text-to-Speech Synthes…

【Java编程进阶】Java异常详解

推荐学习专栏&#xff1a;Java 编程进阶之路【从入门到精通】&#xff0c;从入门到就业精通&#xff0c;买不了吃亏&#xff0c;买不了上当&#xff01;&#xff01; 文章目录1. 异常2. 异常的体系3. Error4. 异常产生的过程5. throw 关键字6. 异常处理6.1 throws 关键字6.2 tr…

基于Node.js和vue的师生互助平台

摘 要随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&am…

Jetpack Compose中的软键盘与焦点控制

FocusRequester 与 FocusManager 在 Compose 中&#xff0c;可以通过 FocusRequester 与 FocusManager 这两个对象可以主动在代码中控制焦点获取和取消焦点&#xff0c;其中FocusRequester可以用来获取焦点&#xff0c;通过调用它的requestFocus()方法来实现&#xff0c;而 Fo…

脚手架搭建Vue项目

以上创建的方式发现一直存在config目录 换种方式 卸载脚手架命令 npm uninstall vue-cli -g 重新安装 npm install vue/cli -g 1.vue create 项目名 2.模板选择&#xff0c;通过键盘上下键来选择&#xff0c;我们选择第三个 自定义 这三个选择分别是 vue2 / vue3 默认模板…

12.Isaac教程--未来工厂中的搬运车

未来工厂中的搬运车 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录未来工厂中的搬运车运行模拟器搬运车送货申请自动小车运输的行为树导航与感知互通仅限自主导航申请仅适用于感知训练模型物体检测模型&#xff08;DetectNetv2&#x…

JSP SSM家教管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 JSPSSM家教管理系统 是一套完善的系统源码&#xff0c;对理解JSP java SrpingMVC mybiats 框架 MVC编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;以及相应配套的设计文档&#xff0c;系 统主要采用B/S模式开发。 研究的基本内容…

5.5、TCP 的拥塞控制

在某段时间若对网络中某一资源的需求超过了该资源所能提供的可用部分&#xff0c;网络性能就要变坏\color{red}对网络中某一资源的需求超过了该资源所能提供的可用部分&#xff0c;网络性能就要变坏对网络中某一资源的需求超过了该资源所能提供的可用部分&#xff0c;网络性能就…

CSDN第22场周赛

1.写在前面的话22场周赛的详情总比赛第7名了&#xff0c;hhhCSDN周赛非常能够锻炼码代码的能力&#xff0c;无论是在平常的练习题目当中&#xff0c;还是每次的周赛中&#xff0c;题目有难有易&#xff0c;每次周赛的题目出的十分具有代表性&#xff0c;参加了将近20场的周赛&a…

批量PDF文件合并用什么软件?这两个宝藏软件赶快收藏起来

我们在工作中经常有很多处理过的PDF文件&#xff0c;我们经常会将这些文件进行保存&#xff0c;以防日后需要使用&#xff0c;但是太多的PDF文件真的会占用很多存储空间&#xff0c;所以我们可以将各类PDF文件合并在一起&#xff0c;这样也方便以后观看&#xff0c;但是逐个合并…

Docker容器实时日志查看器Dozzle

什么是 Dozzle&#xff1f; Dozzle 是一个小型轻量级应用程序&#xff0c;具有基于 Web 的界面来监控 Docker 日志。Dozzle不存储任何日志文件&#xff0c;仅用于实时监控您的容器日志。 先看个官方的动图 老苏已转成了视频&#xff0c;源文件地址&#xff1a;https://github.c…

制造型企业离不开MES?MES系统有什么应用场景

随着工业物联网的迅速发展&#xff0c;设备监测也成为MES系统中的一个关键环节。过去我们所收集到的资料&#xff0c;也许只是一种记录的作用&#xff0c;随着联网的设备越来越多以及大数据、云计算等技术的发展&#xff0c;数据的价值越来越高。数据收集不再仅仅是一种简单的记…

Codeforces Round #841 (Div. 2) (A--D)

[TOC](Codeforces Round #841 (Div. 2)(A–D)) A、 Joey Takes Money 1、题目 2、思路 3、代码 #include<iostream>#include<algorithm>#include<cstring>using namespace std;typedef unsigned long long ll;int main(){ll t;cin>>t;while(t--){int…

新书赠送丨《中国金融科技发展概览:创新与应用前沿》

我国金融科技发展日新月异&#xff0c;人工智能、云计算、大数据等新兴数字技术与实体经济及金融业的深度融合&#xff0c;推动我国数字经济快速发展&#xff0c;也深刻改变着我国金融业的服务业态和经营模式。过去的一年&#xff0c;金融机构实现核心技术自主可控成为热点&…

影响因子14.65:16S全长测序+低丰度简化菌群,提供根腐病防控新视角

背景介绍当土壤中病原体入侵时&#xff0c;植物可以动态调节其根际微生物并适应这种生物胁迫。植物招募的保护性微生物群落中通常包含一些低丰度的类群&#xff0c;其作用尚不清楚。本研究首先分析了健康和患病黄芪之间根系微生物群落结构的差异&#xff0c;依据患病黄芪根部的…

增加模拟前端的动态范围

1、光电接收电路 下面两张图分别在sensor正偏置和负偏置时的接收电路&#xff0c;这里我们关注一下输出的波形特征为一个脉冲信号&#xff0c;脉冲信号的共模电压为5V分压得到&#xff0c;信号的摆幅为Iout*RT&#xff0c;Iout为光电流&#xff0c;在应用在雷达接收的中时&…

JVM调试命令与调试工具

一、JDK自带命令Sun JDK监控和故障处理命令如&#xff1a;1、jpsJVM Process Status Tool&#xff0c;显示指定系统内所有的HotSpot虚拟机进程。jsp命令格式&#xff1a;jps [ options ] [ hostid ] 扩展参数&#xff1a;jps -l&#xff1b;jps -mlv&#xff1b;各参数说明如下…

tkinter 实现倒计时(1小时)

使用python标准GUI库tkinter实现倒计一小时效果。 废话少说。 效果图&#xff1a; 要不然看个 动态效果 图&#xff1a; 代码&#xff1a; from tkinter import * from tkinter.messagebox import showerrorroot Tk() root.title("倒计时") root.geometry("3…

解析|当前企业OA系统面对的困难与解决方案

近年来&#xff0c;由于疫情爆发&#xff0c;线下的企业办公效率难以保证&#xff0c;不少企业逐渐转向远程办公。尝试过后&#xff0c;远程办公的优势凸显&#xff0c;使得有越来越多的企业开始逐渐深入了解在线办公软件、协同办公OA系统。据统计&#xff0c;2021年数字化办公…

监控docker

当前&#xff0c;容器的使用已经非常普及&#xff0c;将服务迁移到容器上正成为了越来越多公司的选择。而对于运维人员而言&#xff0c;熟悉容器的使用与监控&#xff0c;也已成为一项必不可少的专业技能。关于容器的开源产品&#xff0c;目前知名的有Docker、Containerd、Core…