Linux之 线程池 | 单例模式的线程安全问题 | 其他锁

目录

一、线程池

1、线程池

2、线程池代码

3、线程池的应用场景

二、单例模式的线程安全问题

1、线程池的单例模式

2、线程安全问题

三、其他锁


一、线程池

1、线程池

线程池是一种线程使用模式。线程池里面可以维护一些线程。

为什么要有线程池?

因为在我们使用线程去处理各种任务的时候,尤其是一些执行时间短的任务,我们必须要先对线程进行创建然后再进行任务处理,最后再销毁线程,效率是比较低的。而且有的时候线程过多会带来调度开销,进而影响缓存局部性和整体性能。

于是,我们可以通过线程池预先创建出一批线程,线程池维护着这些线程,线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。

线程池不仅能够保证内核的充分利用,还能防止过分调度。

2、线程池代码

我们先对线程进行封装:Thread.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>

using namespace std;
typedef void *(*fun_t)(void *);

class ThreadData
{
public:
    void *arg_;
    string name_;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void *arg)
        : func_(callback)
    {
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "Thread-%d", num);
        name_ = buffer;
        tdata_.name_ = name_;
        tdata_.arg_ = arg;
    }

    void start()
    {
        pthread_create(&tid_, nullptr, func_, (void *)&tdata_);
    }

    void join()
    {
        pthread_join(tid_, nullptr);
    }

    string &name()
    {
        return name_;
    }

    ~Thread()
    {
    }

private:
    pthread_t tid_;
    string name_;
    fun_t func_;
    ThreadData tdata_;
};

线程池代码:threadPool.hpp:

#pragma once
#include <vector>
#include <queue>
#include "thread.hpp"

#define THREAD_NUM 3

template <class T>
class ThreadPool
{
public:
    bool Empty()
    {
        return task_queue_.empty();
    }

    pthread_mutex_t *getmutex()
    {
        return &lock;
    }

    void wait()
    {
        pthread_cond_wait(&cond, &lock);
    }

    T gettask()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }

public:
    ThreadPool(int num = THREAD_NUM) : num_(num)
    {
        for (int i = 0; i < num_; i++)
        {
            threads_.push_back(new Thread(i, routine, this));
        }
        pthread_mutex_init(&lock, nullptr);
        pthread_cond_init(&cond, nullptr);
    }

    static void *routine(void *arg)
    {
        ThreadData *td = (ThreadData *)arg;
        ThreadPool<T> *tp = (ThreadPool<T> *)td->arg_;
        while (true)
        {
            T task;
            {
                pthread_mutex_lock(tp->getmutex());
                while (tp->Empty())
                    tp->wait();
                task = tp->gettask();
                pthread_mutex_unlock(tp->getmutex());
            }
            cout << "x+y=" << task() << " " << pthread_self() << endl;
        }
    }

    void run()
    {
        for (auto &iter : threads_)
        {
            iter->start();
        }
    }

    void PushTask(const T &task)
    {
        pthread_mutex_lock(&lock);
        task_queue_.push(task);
        pthread_mutex_unlock(&lock);
        pthread_cond_signal(&cond);
    }

    ~ThreadPool()
    {
        for (auto &iter : threads_)
        {
            iter->join();
            delete iter;
        }
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&cond);
    }

private:
    vector<Thread *> threads_;
    int num_;
    queue<T> task_queue_;
    pthread_mutex_t lock;
    pthread_cond_t cond;
};

任务:task.hpp:

#pragma once

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

class task
{
public:
    task()
    {
    }
    task(int x, int y)
        : x_(x), y_(y)
    {
    }

    int operator()()
    {
        return x_ + y_;
    }

private:
    int x_;
    int y_;
};

 测试代码:test.cc:

#include "threadPool.hpp"
#include "task.hpp"
#include <iostream>
#include <ctime>

int main()
{
    srand((unsigned int)time(nullptr) ^ getpid() ^ 12232);
    ThreadPool<task> *tp = new ThreadPool<task>();
    tp->run();
    while (true)
    {
        int x = rand() % 100 + 1;
        sleep(1);
        int y = rand() % 100 + 1;
        task t(x, y);
        tp->PushTask(t);
        cout << x << "+" << y << "=?" << endl;
    }

    return 0;
}

运行结果: 

3、线程池的应用场景

1、需要大量的线程来完成任务,且完成任务的时间比较短。 
2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

二、单例模式的线程安全问题

1、线程池的单例模式

首先,我们要做的第一件事就是把构造函数私有,再把拷贝构造和赋值运算符重载函数delete:

private:
    ThreadPool(int num = THREAD_NUM) : num_(num)
    {
        for (int i = 0; i < num_; i++)
        {
            threads_.push_back(new Thread(i, routine, this));
        }
        pthread_mutex_init(&lock, nullptr);
        pthread_cond_init(&cond, nullptr);
    }

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

接下来就要在类中定义一个成员变量:静态指针,方便获取单例对象,并在类外初始化:

//线程池中的成员变量
private:
    vector<Thread *> threads_;
    int num_;
    queue<T> task_queue_;
    pthread_mutex_t lock;
    pthread_cond_t cond;

    static ThreadPool<T> *tp;

//在类外初始化
​template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;

最后我们写一个函数可以获取单例对象,在设置获取单例对象的函数的时候,注意要设置成静态成员函数,因为在获取对象前根本没有对象,无法调用非静态成员函数(无this指针): 

static ThreadPool<T> *getThreadPool()
{
    if (tp == nullptr)
    {
        tp = new ThreadPool<T>();
    }
    return tp;
}

2、线程安全问题

上面的线程池的单例模式,看起来没有什么问题。可是当我们有多个线程去调用 getThreadPool函数,去创建线程池的时候,可能会有多个线程同时进入判断,判断出线程池指针为空,然后创建线程池对象。这样就会创建出多个线程池对象,这就不符合我们单例模式的要求了,所以我们必须让在同一时刻只有一个线程能够进入判断,我们就要用到锁了。

定义一个静态锁,并初始化:

private:
    vector<Thread *> threads_;
    int num_;
    queue<T> task_queue_;
    pthread_mutex_t lock;
    pthread_cond_t cond;
    static ThreadPool<T> *tp;
    static pthread_mutex_t lock;

// 类外初始化
​template <class T>
pthread_mutex_t ThreadPool<T>::lock = PTHREAD_MUTEX_INITIALIZER;

对 getThreadPool函数进行加锁:

    static ThreadPool<T> *getThreadPool()
    {
        if (tp == nullptr)
        {
            pthread_mutex_lock(&lock);
            if (tp == nullptr)
            {
                tp = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock);
        }
        return tp;
    }

对于上面的代码:我们为什么要在获取锁之前还要再加一个判断指针为空的条件呢?

当已经有一个线程创建出来了线程池的单例模式后,在这之后的所有其他线程即使申请到锁,紧着着下一步就是去释放锁,它不会进入第二个 if 条件里面。其实这样是效率低下的,因为线程会频繁申请锁,然后就释放锁。所以我们在最外层再加一个if判断,就可以阻止后来的线程不用去申请锁创建线程池了,直接返回已经创建出来的线程池。

三、其他锁

1、悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。

2、乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
~ CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

3、自旋锁:说到自旋锁,我们不得不说一说我们之前所用到的锁,我们之前所用的锁都是互斥锁,当线程没有竞争到互斥锁时,它会阻塞等待,只有等锁被释放了后,才能去重新申请锁。而对于自旋锁,当线程没有竞争到自旋锁的时候,线程会不断地循环检测去申请自旋锁,直到拿到锁。

一般来说,如果临界区的代码执行时间比较长的话,我们是使用互斥锁而不是自旋锁的,这样线程不会因为频繁地检测去申请锁而占用CPU资源。如果临界区的代码执行时间较短的话,我们一般就最好使用自旋锁,而不是互斥锁,因为互斥锁申请失败,是要阻塞等待,是需要发生上下文切换的,如果临界区执行的时间比较短,那可能上下文切换的时间会比临界区代码执行的时间还要长。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文教会女朋友学会日常Git使用!Git知识总结

文章目录 一文教会女朋友学会日常Git使用&#xff01;Git知识总结一、git基本知识了解1.git简介2.git区域了解3.git常用命令 二、常用工作场景1.克隆远程仓库&#xff0c;把仓库代码拉到本地2.推送代码到远程仓库&#xff08;1&#xff09;本地代码和远程仓库版本相同&#xff…

细谈SolidWorks教育版的一些基础知识

SolidWorks教育版是一款广泛应用于工程设计和教育领域的三维建模软件。它具备直观易用的操作界面和强大的设计功能&#xff0c;为学生提供了一个学习和实践的平台。在本文中&#xff0c;我们将详细探讨SolidWorks教育版的一些基础知识&#xff0c;帮助初学者更好地了解和掌握这…

鸿蒙实战开发-如何使用三方库

使用三方库 在使用三方库之前&#xff0c;需要安装 ohpm&#xff0c;并在环境变量中配置。 在项目目录的Terminal窗口执行ohpm命令下载依赖 ohpm install yunkss/eftool 命令运行成功后&#xff0c;在项目的oh-package.json5文件中会自动添加上依赖&#xff0c;如下所示&am…

【氮化镓】GaN器件中关态应力诱导的损伤定位

概括总结&#xff1a; 这项研究通过低频1/f噪声测量方法&#xff0c;探究了在关态&#xff08;OFF-state&#xff09;应力作用下&#xff0c;AlGaN/GaN高电子迁移率晶体管&#xff08;HEMTs&#xff09;中由应力引起的损伤的定位。研究中结合了电致发光&#xff08;EL&#xf…

【Java面试题系列】基础篇

目录 基本常识标识符的命名规则八种基本数据类型的大小&#xff0c;以及他们的封装类3*0.10.3返回值是什么short s1 1; s1 s1 1;有什么错? short s1 1; s1 1;有什么错?简述&&与&的区别&#xff1f;简述break与continue、return的区别&#xff1f;Arrays类的…

(负载点电源)PCD3203高转换率同步降压40V/3A内置高低侧MOSFET只需极少外围元件

1. 产品特性 ➢ 输入电压范围&#xff1a; 4.5V~40V ➢ 最大负载&#xff1a; 3A ➢ 上下管导通电阻&#xff1a; 110mΩ/70mΩ ➢ 软启保护时间 tss&#xff1a; 1ms ➢ 工作频率范围&#xff1a; 500kHz~2.5MHz ➢ 逐周期峰值电流限制 ➢ 内部补偿 ➢ 可调的输入欠压…

这个AI 应用万人使用:真人视频转动漫、手绘风,丝滑感前所未有

视频的次元壁就这么被打破了。 在 AI 的加持下&#xff0c;真人视频变身二次元就这么简单 只需要导入原始视频&#xff0c;它就可以帮你把视频改成你想要的风格&#xff0c;比如动漫风、手绘风或者 3D 卡通风格。 这一应用一经推出立刻引起了很多人的关注 因其操作简单&#x…

蓝桥杯-穿越雷区

题目要求 需求&#xff1a;从一个方格中A点&#xff0c;按要求移动到B点。 要求&#xff1a;每次只能走上下左右&#xff0c;每次只能走一次&#xff0c;每次是轮换穿越’‘,’-两个&#xff0c;否则就会能量失衡&#xff0c;发生爆炸。 使用的算法&#xff1a;这题典型的就是使…

nginx的安装教程

文章目录 简介nginx安装windows下安装linux下安装 简介 nginx是一个开源的web服务器和反向代理服务器&#xff0c;可以用作负载均衡和HTTP缓存。它处理并发能力是十分强大的&#xff0c;能够经受高负载的考验。 正向代理 Nginx不仅可以做反向代理&#xff0c;实现负载均衡&am…

AI工作站设计方案:903-多路PCIe3.0的单CPU 学习型AI工作站

多路PCIe3.0的单CPU 学习型AI工作站 一、机箱功能和技术指标&#xff1a; 系统 系统型号 ORI-SR500 主板支持 EEB(12*13)/CEB(12*10.5)/ATX(12*9.6)/Mi cro ATX 前置硬盘 最大支持2个3.5寸1个2.5寸SATA 硬盘2个2.5寸SATA 硬盘 &#xff08;背部&#xff09; 电源类型 C…

【Django开发】前后端分离美多商城项目第4篇:用户部分,1. 判断用户名是否存在【附代码文档】

美多商城项目4.0文档完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;美多商城&#xff0c;项目准备1.B2B--企业对企业,2.C2C--个人对个人,3.B2C--企业对个人,4.C2B--个人对企业,5.O2O--线上到线下,6.F2C--工厂到个人。项目准备&#xff0c;配置1. 修改set…

阿里云服务器ECS经济型e实例怎么样?

阿里云服务器ECS经济型e系列是阿里云面向个人开发者、学生、小微企业&#xff0c;在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器&#xff0c;CPU处理器采用Intel Xeon Platinum架构处理器&#xff0c;支持1:1、1:2、1:4多种处理器内存配比&#xff0c…

vue3组合式函数

vue3的组合式函数的作用是封装和复用响应式状态的函数。只能在setup 标签的script标签汇总或者setup函数中使用。 普通的函数只能调用一次&#xff0c;但是组合式函数接受到响应式参数&#xff0c;当该值发生变化时&#xff0c;也会触发相关函数的重新加载。 如下 定义了一个…

算法(滑动窗口四)

1.串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff…

《QDebug 2024年3月》

一、Qt Widgets 问题交流 1. 二、Qt Quick 问题交流 1.Qt5 ApplicationWindow 不能使用父组件 Window 的 transientParent 属性 ApplicationWindow 使用 transientParent 报错&#xff1a; "ApplicationWindow.transientParent" is not available due to compone…

Android车载设备开发相关名词介绍

一、通讯相关 1、ECALL 如遭遇紧急情况&#xff0c;用户可按下该键以最高优先级接通呼叫中心&#xff0c;人工坐席将同时获取客户车辆的重要数据并协助驾驶员脱离危险。 实现原理 E-Call 的核心思想是利用车载卫星定位系统获取车辆的位置信息&#xff0c;在事故发生后&#x…

性能测试入门 —— 什么是性能测试PTS?

性能测试PTS&#xff08;Performance Testing Service&#xff09;是一款简单易用&#xff0c;具备强大的分布式压测能力的SaaS压测平台。 PTS可以模拟复杂的业务场景&#xff0c;并快速精准地调度不同规模的流量&#xff0c;同时提供压测过程中多维度的监控指标和日志记录。您…

输出100~200之间的素数(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现素数判断函数&#xff1b; int Prime(int number) {//初始化变量值&#xff1b;int divided 2;int JudgementCondition 0;//循环判断素数&#xff1b;wh…

Git的使用【入门级】--下载github项目

1.下载Git Git for Windows Windows版本进入如上网址下载&#xff1a; 点击Download即可&#xff0c;建议下载到内存多的盘&#xff0c;我是下载到移动固态优盘中&#xff0c;以免C盘太挤。 验证Git是否安装成功&#xff1a; 双击git-bash.exe&#xff0c;命令行输入git versi…

【Django开发】0到1美多商城项目md教程第4篇:图形验证码,1. 图形验证码接口设计【附代码文档】

美多商城完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;欢迎来到美多商城&#xff01;&#xff0c;项目准备。展示用户注册页面&#xff0c;创建用户模块子应用。用户注册业务实现&#xff0c;用户注册前端逻辑。图形验证码&#xff0c;图形验证码接口设…