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

本文主要是介绍【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、线程池
  • 二、线程安全的单例模式
    • 1.单例模式的特点
    • 2.饿汉实现方式和懒汉实现方式
    • 3.懒汉方式实现单例模式(线程安全版本)
  • 三、STL,智能指针和线程安全
  • 四、常见的各种锁
  • 五、读者写者问题
    • 1.读写锁
    • 2.读写锁接口

一、线程池

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络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;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error" << std::endl;return -1;}elseresult = 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);

对于读者写者问题,我们了解一下即可,做实现的现象并不明显,理解其原理即可。

这篇关于【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

MySQL启动报错:InnoDB表空间丢失问题及解决方法

《MySQL启动报错:InnoDB表空间丢失问题及解决方法》在启动MySQL时,遇到了InnoDB:Tablespace5975wasnotfound,该错误表明MySQL在启动过程中无法找到指定的s... 目录mysql 启动报错:InnoDB 表空间丢失问题及解决方法错误分析解决方案1. 启用 inno

Linux CPU飙升排查五步法解读

《LinuxCPU飙升排查五步法解读》:本文主要介绍LinuxCPU飙升排查五步法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录排查思路-五步法1. top命令定位应用进程pid2.php top-Hp[pid]定位应用进程对应的线程tid3. printf"%

Linux下安装Anaconda3全过程

《Linux下安装Anaconda3全过程》:本文主要介绍Linux下安装Anaconda3全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录简介环境下载安装一、找到下载好的文件名为Anaconda3-2018.12-linux-x86_64的安装包二、或者通

Linux系统之stress-ng测压工具的使用

《Linux系统之stress-ng测压工具的使用》:本文主要介绍Linux系统之stress-ng测压工具的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、理论1.stress工具简介与安装2.语法及参数3.具体安装二、实验1.运行8 cpu, 4 fo

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Linux lvm实例之如何创建一个专用于MySQL数据存储的LVM卷组

《Linuxlvm实例之如何创建一个专用于MySQL数据存储的LVM卷组》:本文主要介绍使用Linux创建一个专用于MySQL数据存储的LVM卷组的实例,具有很好的参考价值,希望对大家有所帮助,... 目录在Centos 7上创建卷China编程组并配置mysql数据目录1. 检查现有磁盘2. 创建物理卷3. 创

Linux查看系统盘和SSD盘的容量、型号及挂载信息的方法

《Linux查看系统盘和SSD盘的容量、型号及挂载信息的方法》在Linux系统中,管理磁盘设备和分区是日常运维工作的重要部分,而lsblk命令是一个强大的工具,它用于列出系统中的块设备(blockde... 目录1. 查看所有磁盘的物理信息方法 1:使用 lsblk(推荐)方法 2:使用 fdisk -l(

Linux中的more 和 less区别对比分析

《Linux中的more和less区别对比分析》在Linux/Unix系统中,more和less都是用于分页查看文本文件的命令,但less是more的增强版,功能更强大,:本文主要介绍Linu... 目录1. 基础功能对比2. 常用操作对比less 的操作3. 实际使用示例4. 为什么推荐 less?5.

基于Python实现智能天气提醒助手

《基于Python实现智能天气提醒助手》这篇文章主要来和大家分享一个实用的Python天气提醒助手开发方案,这个工具可以方便地集成到青龙面板或其他调度框架中使用,有需要的小伙伴可以参考一下... 目录项目概述核心功能技术实现1. 天气API集成2. AI建议生成3. 消息推送环境配置使用方法完整代码项目特点

电脑蓝牙连不上怎么办? 5 招教你轻松修复Mac蓝牙连接问题的技巧

《电脑蓝牙连不上怎么办?5招教你轻松修复Mac蓝牙连接问题的技巧》蓝牙连接问题是一些Mac用户经常遇到的常见问题之一,在本文章中,我们将提供一些有用的提示和技巧,帮助您解决可能出现的蓝牙连接问... 蓝牙作为一种流行的无线技术,已经成为我们连接各种设备的重要工具。在 MAC 上,你可以根据自己的需求,轻松地