【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题

这篇具有很好参考价值的文章主要介绍了【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、线程池

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

线程池的应用场景:

1.需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

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

3.接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

线程池的种类:

线程池示例:

1.创建固定数量线程池,循环从任务队列中获取任务对象,

2.获取到任务对象后,执行任务对象中的任务接口

Thread.hpp

以下是自己封装实现的线程

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <cassert>
#include <pthread.h>

namespace ThreadNs
{
    typedef std::function<void *(void *)> func_t;
    const int num = 1024;
    class Thread
    {
    private:
        static void *start_routine(void *args)
        {
            Thread *td = static_cast<Thread *>(args);
            return td->callback();
        }

    public:
        Thread()
        {
            char buffer[num];
            snprintf(buffer, sizeof buffer, "thread-%d", threadnum++);
            _name = buffer;
        }

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

        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;
        void *_args;
        func_t _func;
        pthread_t _tid;

        static int threadnum;
    };

    int Thread::threadnum = 1;
}

LockGuard.hpp

以下是自己封装实现的RAII风格的锁

#pragma once

#include <cassert>
#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t *lock_p = nullptr)
        : _lock_p(lock_p)
    {
    }

    void lock()
    {
        if (_lock_p)
        {
            int n = pthread_mutex_lock(_lock_p);
            assert(n == 0);
            (void)n;
        }
    }

    void unlock()
    {
        if (_lock_p)
        {
            int n = pthread_mutex_unlock(_lock_p);
            assert(n == 0);
            (void)n;
        }
    }

    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock_p;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        _mutex.lock();
    }

    ~LockGuard()
    {
        _mutex.unlock();
    }

private:
    Mutex _mutex;
};

Task.hpp

以下代码是用于任务的处理

#pragma once

#include <iostream>
#include <string>
#include <functional>

class Task
{
public:
    typedef std::function<int(int, int, char)> func_t;
    // using func_t = std::function<int(int, int, char)>;

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

    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 calculate(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;
            return -1;
        }
        else
            result = x / y;
    }
    break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error" << std::endl;
            return -1;
        }
        else
            result = x % y;
    }
    break;
    default:
        std::cerr << "请输入正确的操作符" << std::endl;
        break;
    }

    return result;
}

ThreadPool.hpp

#pragma once

#include "Thread.hpp"
#include "LockGuard.hpp"

using namespace ThreadNs;

#include <vector>
#include <queue>
#include <iostream>

const int gnum = 3;

template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
public:
    ThreadData(ThreadPool<T> *tp, const std::string &threadname)
        : _threadpool(tp), _threadname(threadname)
    {}

    ~ThreadData()
    {}

public:
    ThreadPool<T> *_threadpool;
    std::string _threadname;
};

template <class T>
class ThreadPool
{
private:
    static void* handleTask(void* args)
    {
        ThreadData<T>* td = static_cast<ThreadData<T>*>(args);
        while(true)
        {
            T t;
            {
                LockGuard lockguard(td->_threadpool->mutex());
                while(td->_threadpool->isQueueEmpty())
                {
                    td->_threadpool->threadWait();
                }
                t = td->_threadpool->pop();
            }
            std::cout << td->_threadname << " 获取了一个任务: " << t.toTaskString() << " 并处理完成,结果是:" << t() << std::endl;
        }

        delete td;
        return nullptr;
    }
public:
    bool isQueueEmpty() {return _task_queue.empty(); }
    void threadWait() { pthread_cond_wait(&_cond,&_mutex); }
    void lockQueue() {pthread_mutex_lock(&_mutex); }
    void unlockQueue() {pthread_mutex_unlock(&_mutex); }

    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    pthread_mutex_t* mutex() { return &_mutex; }
public:
    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());
        }
    }

public:
    void run()
    {
        for (const auto &iter : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, iter->threadname());
            iter->start(handleTask, td);
            std::cout << iter->threadname() << " start..." << std::endl;
        }
    }

    void push(T& in)
    {
        LockGuard lockguard(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
    }

    ~ThreadPool()
    {
        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 "Task.hpp"
#include "ThreadPool.hpp"

#include <memory>
#include <unistd.h>

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

    int x, y;
    char op;
    while (1)
    {
        std::cout << "请输入数据1# ";
        std::cin >> x;
        std::cout << "请输入数据2# ";
        std::cin >> y;
        std::cout << "请输入你要进行的运算#";
        std::cin >> op;
        Task t(x, y, op, calculate);

        tp->push(t);

        sleep(1);
    }

    return 0;
}

二、线程安全的单例模式

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

1.单例模式的特点

某些类, 只应该具有一个对象(实例), 就称之为单例.例如一个男人只能有一个媳妇.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据

2.饿汉实现方式和懒汉实现方式

我们以洗碗的例子来说明懒汉模式和饿汉模式:

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。

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

饿汉方式实现单例模式

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

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例

懒汉方式实现单例模式

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

存在一个严重的问题, 线程不安全。第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例,但是后续再次调用, 就没有问题了

3.懒汉方式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全
template <class T>
class Singleton
{
	volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
	static std::mutex lock;
public:
	static T* GetInstance()
    {
		if (inst == NULL)
        { 
            // 双重判定空指针, 降低锁冲突的概率, 提高性能.
			lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
			if (inst == NULL)
            {
				inst = new T();
			}
			lock.unlock();
		}
		return inst;
	}
};

注意事项:

1.加锁解锁的位置

2.双重 if 判定, 避免不必要的锁竞争

3.volatile关键字防止过度优化

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

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

不是。原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.

而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。

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

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

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

四、常见的各种锁

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

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁,公平锁,非公平锁。

五、读者写者问题

1.读写锁

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

2.读写锁接口

设置读写优先

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

初始化

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);

对于读者写者问题,我们了解一下即可,做实现的现象并不明显,理解其原理即可。文章来源地址https://www.toymoban.com/news/detail-770432.html

到了这里,关于【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java中23种设计模式-单例模式2--懒汉式2线程安全】

    加油,新时代打工人! 简单粗暴,学习Java设计模式。 23种设计模式定义介绍 Java中23种设计模式-单例模式 Java中23种设计模式-单例模式2–懒汉式线程不安全 通过运行结果看,两个线程的地址值是相同的,说明内存空间里,创建了一个对象。

    2024年02月20日
    浏览(37)
  • C++面试之线程池、智能指针、设计模式

    一、线程池 1、线程池实现步骤 这里就讲讲正常的一个线程池的实现步骤。 1.1 定义任务类:首先需要定义一个任务类,用于封装需要在线程池中执行的任务。任务类至少应该包含一个执行任务的方法,可以是一个函数指针或者是一个函数对象。 1.2 定义线程池类:接下来定义

    2024年01月16日
    浏览(37)
  • 【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

    🍎 个人博客: 个人主页 🏆 个人专栏: JAVA ⛳️   功不唐捐,玉汝于成 目录 前言 正文 懒汉式(Lazy Initialization): 双重检查锁定(Double-Checked Locking): 结语 我的其他博客 在软件设计中,单例设计模式是一种重要的设计思想,它确保了一个类只有一个实例,并提供了一

    2024年01月15日
    浏览(41)
  • 【Linux】简单线程池的设计与实现 -- 单例模式

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

    2024年02月12日
    浏览(31)
  • 线程安全版本的单例设计模式 与 生产者消费者模型简介

    目录 单例设计模式 单例设计模式——饿汉式 单例设计模式——懒汉式 单例设计模式——懒汉式(优化步骤) 生产者消费者模型 介绍 优点 补充:关于阻塞队列 单例设计模式能够保证 某个类的实例在程序运行过程中始终都只会存在一份 。这一点在很多场景上都有需要,比

    2023年04月24日
    浏览(45)
  • 【多线程】线程安全的单例模式

    单例模式能保证某个类在程序中只存在 唯一 一份实例, 而不会创建出多个实例,从而节约了资源并实现数据共享。 比如 JDBC 中的 DataSource 实例就只需要一个. 单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种. 类加载的同时, 创建实例. 注意: 使用 static 修饰 instanc

    2024年02月09日
    浏览(36)
  • 单例模式及其线程安全问题

    目录 ​ 1.设计模式 2.饿汉模式 3.懒汉模式 4.线程安全与单例模式 设计模式是什么? 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的 单例模式的作用就是保证某个类在程序

    2024年02月03日
    浏览(29)
  • 线程安全之单例模式

    这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把

    2024年02月06日
    浏览(49)
  • 单例模式的线程安全形式

    目录 1.单例设计模式的概念 2.实现方法: 1.饿汉式 2.懒汉式 3.区分饿汉式和懒汉式: 3.单例模式的双重校验线程安全形式 1.线程安全问题的解决方法 1.1 synchronized: 1.2 volatile:         保证变量可见性(不保证原子性)         禁止指令的重排序 2.线程安全

    2024年02月15日
    浏览(45)
  • 设计模式(单例模式,工厂模式),线程池

    目录 什么是设计模式? 单例模式 饿汉模式 懒汉模式 工厂模式 线程池 线程池种类 ThreadPoolExcutor的构造方法: 手动实现一个线程池  计算机行业程序员水平层次不齐,为了 让所有人都能够写出规范的代码, 于是就有了设计模式, 针对一些典型的场景,给出一些典型的解决方案 单例

    2024年02月11日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包