linux操作系统多线程(代码片段)

Ricky_0528 Ricky_0528     2023-01-12     141

关键词:

文章目录

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 优先级

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

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

linux操作系统多线程(代码片段)

文章目录1.线程概念2.线程控制2.1创建线程2.2线程ID2.3线程等待2.4线程终止2.5线程分离3.线程同互斥与同步3.1互斥量3.2死锁3.3同步-条件变量3.4生产者消费者模型3.5POSIX信号量3.6基于环形队列的生产消费者模型1.线程概念线程:是... 查看详情

linux操作系统多线程(代码片段)

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

linux---多线程线程池(代码片段)

...。Notes:进程是系统进行资源分配的基本单元,线程是操作系统进行调度运行的基本单元,在linux下pc 查看详情

[linux]linux多线程详解(代码片段)

目录1.线程概念1.1什么是线程1.2从操作系统看线程1.3线程的分类1.4线程的优缺点2.线程控制2.1线程创建2.2线程终止2.3线程等待2.4线程分离3.线程安全3.1线程不安全的现象3.1如何解决--互斥锁3.1.1互斥锁原理3.1.2互斥锁接口3.2死锁3.2.1... 查看详情

linux下多线程的操作(代码片段)

...参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解,进程是资源分配的最小单位& 查看详情

linux多线程编程与同步实例(基于条件变量)(代码片段)

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。C语言编... 查看详情

linux----多线程(下)(代码片段)

多线程(下)2)线程控制①...②线程池Routine的static属性实现一个线程池,多线程处理任务(接收两数和一个操作符打印结果)③单例模式(线程安全)更改上面实现的线程池为懒汉模式④STL智能指... 查看详情

多进程多线程在不同环境下的操作(代码片段)

...程、多线程在不同环境下的操作多进程:Linux创建进程是操作系统把父进程的东西拷贝到子进程    Windows创建进程类似于模块导入  Linux环境下开启多进程,可以用os里的fork1importos2importtime3pid=os.fork()45ifpid==0:#子进程永远返... 查看详情

多线程(代码片段)

...游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗? 查看详情

linux多线程(代码片段)

...得多2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多3.线程占用的资源要比进程少很多4.能充分利用多处理器的可并行数量5.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务6.计算密集... 查看详情

linux多线程(代码片段)

...得多2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多3.线程占用的资源要比进程少很多4.能充分利用多处理器的可并行数量5.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务6.计算密集... 查看详情

java多线程:linux多路复用,javanio与netty简述(代码片段)

JVM的多路复用器实现原理Linux2.5以前:select/pollLinux2.6以后:epollWindows:IOCPFreeBSD,OSX:kqueue下面仅讲解Linux的多路复用。Linux中的IOLinux的IO将所有外部设备都看作文件来操作,与外部设备的操作都可以看做文件操作,其读写都使用内核... 查看详情

linux是一个基于posix和unix的多用户多任务支持多线程和多cpu的性能稳定的操作系统,可免费使用并自由传播。(代码片段)

...Unix的多用户、多任务、支持多线程和多CPU的性能稳定的操作系统,可免费使用并自由传播。Linux是众多操作系统之一,目前流行的服务器和PC端操作系统有Linux、Windows、UNIX等Linux的创始人 LinusTorvalds林纳斯(同时也是git的开发者)... 查看详情

[linux]linux多线程详解(代码片段)

目录1.线程概念1.1什么是线程1.2从操作系统看线程1.3线程的分类1.4线程的优缺点2.线程控制2.1线程创建2.2线程终止2.3线程等待2.4线程分离3.线程安全3.1线程不安全的现象3.1如何解决--互斥锁3.1.1互斥锁原理3.1.2互斥锁接口3.2死锁3.2.1... 查看详情

linux多线程操作pthread_t(代码片段)

进程概念进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,... 查看详情

linux多线程操作pthread_t(代码片段)

进程概念进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,... 查看详情

linux操作系统(代码片段)

什么是Linux操作系统Linux操作系统(GNU/Linux)是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。特点基本思想:一切都是文件;每个文件都有确定... 查看详情

linux多线程的创建等待终止(代码片段)

...管理单元MMU将会终止该访问行为,MMU硬件报错,操作系统发现硬件报错, 查看详情