Grupo de subprocesos de Linux, modo singleton basado en grupo de subprocesos, problema de lector y escritor

1. ¿Qué es un grupo de subprocesos?

Grupo de subprocesos: un patrón de uso de subprocesos. Demasiados subprocesos introducen una sobrecarga de programación, lo que afecta la ubicación de la memoria caché y el rendimiento general. El grupo de subprocesos mantiene múltiples subprocesos, esperando que el supervisor asigne tareas que se pueden ejecutar simultáneamente. Esto evita el costo de crear y destruir subprocesos al procesar tareas de corta duración. El grupo de subprocesos no solo puede garantizar la utilización completa del núcleo, sino también evitar una programación excesiva. La cantidad de subprocesos disponibles debe depender de la cantidad de procesadores simultáneos, núcleos de procesador, memoria, sockets de red, etc. disponibles.

Escenarios de aplicación del grupo de subprocesos:

  1. Se requiere una gran cantidad de subprocesos para completar la tarea y el tiempo para completar la tarea es relativamente corto.
  2. Aplicaciones críticas para el rendimiento, como requerir que el servidor responda rápidamente a las solicitudes de los clientes.
  3. Una aplicación que acepta una gran cantidad de solicitudes repentinas, pero que no hace que el servidor genere una gran cantidad de subprocesos.
    inserte la descripción de la imagen aquí

2. Ideas de diseño

inserte la descripción de la imagen aquí

3. Implementación del código

Nota: por mi descuido personal, 将ThreadPool写成了ThredaPool!!!

//Thread.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <pthread.h>
#include <cassert>

namespace ThreadCat
{
    
    
typedef std::function<void *(void *)> func_t;
const int num = 1024;

    class Thread
    {
    
    
    private:
        // 使用静态方法
        static void *start_rountine(void *args)
        {
    
    

            Thread *this_ = static_cast<Thread *>(args);
            return this_->callback();
        }
    public:
        Thread() 
        {
    
    
            char namebuffer[num];
            snprintf(namebuffer, sizeof namebuffer, "thread-%d", thread_num++);
            _name=namebuffer;   
        }

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

        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;
        pthread_t _tid;
        func_t _func;
        void *_args;
        static int thread_num;
    };
    int Thread::thread_num=1;
    

}
//Task.hpp
#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <functional>
class Task//计算任务
{
    
    

public:
    using func_t =std::function<int(int,int,char)>;
    Task(){
    
    }
    Task(int x,int y,char op,func_t callback)
        :x_(x),y_(y),op_(op),callback_(callback)
    {
    
    

    }
    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 mymath(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;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
    
    
        if (y == 0)
        {
    
    
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        break;
    }
    return result;
}

//lockguard.hpp
#pragma once
#include <iostream>
#include <pthread.h>

class Mutex
{
    
    
public:
    Mutex(pthread_mutex_t * lock_p=nullptr)
        :lock_p_(lock_p)
    {
    
    }
    void lock()
    {
    
    
        if(lock_p_) pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
    
    
        if(lock_p_) pthread_mutex_unlock(lock_p_);
    }
    ~Mutex(){
    
    }
private:
    pthread_mutex_t* lock_p_;    
};


//RAII思想管理资源
class LockGuard
{
    
    
public:
    LockGuard(pthread_mutex_t* lock_p)//用传过来的锁去初始化Mutex
        :mutex(lock_p)
    {
    
    
        mutex.lock();
    }
    ~LockGuard()
    {
    
    
        mutex.unlock();
    }
private:
    Mutex mutex;
};
//ThreadPool.hpp
#pragma once
#include "Thread.hpp"
#include "lock_guard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace ThreadCat;

const int gnum = 5;

template <class T>
class ThredaPool;

template <class T>
class ThreadData
{
    
    
public:
    ThreadData(ThredaPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    
    
    }

public:
    ThredaPool<T> *threadpool;
    std::string name;
};

template <class T>
class ThredaPool
{
    
    
private:
    static void *handlerTask(void *args)
    {
    
    
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
    
    
            T t;
            {
    
    
                LockGuard lock(td->threadpool->mutex());
                //td->threadpool->lockQueue();
                while (td->threadpool->isQueueEmpty())
                {
    
    
                    td->threadpool->threadWait();
                }
                // pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
                t = td->threadpool->pop();
                //td->threadpool->unlockQueue();
            }
            std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
        }
        delete td;
        return nullptr;
    }

public:
    void lockQueue() {
    
     pthread_mutex_lock(&mutex_); }
    void unlockQueue() {
    
     pthread_mutex_unlock(&mutex_); }
    bool isQueueEmpty() {
    
     return task_queue_.empty(); }
    void threadWait() {
    
     pthread_cond_wait(&cond_, &mutex_); }
    T pop()
    {
    
    
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
    pthread_mutex_t* mutex()
    {
    
    
        return &mutex_;
    }
public:
    ThredaPool(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());
        }
    }

    void run()
    {
    
    
        for (const auto &t : threads_)
        {
    
    
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            std::cout << t->threadname() << std::endl;
        }
    }

    void push(const T &in)
    {
    
    
        LockGuard lock(&mutex_);
        //pthread_mutex_lock(&mutex_);
        task_queue_.push(in);
        pthread_cond_signal(&cond_);
        //pthread_mutex_unlock(&mutex_);
    }

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

#include <unistd.h>
#include <memory>
using std::cout;
using std::endl;

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

    srand((size_t)time(0) ^ 0x22116652);
    int x, y;
    char op;
    while (true)
    {
    
    
        x = rand() % 10 + 1;
        y = rand() % 5 + 1;
        op = oper[rand() % oper.size()];
        Task t(x, y, op, mymath);
        tp->push(t);
        sleep(1);
    }
    return 0;
}

En el código anterior, primero encapsulamos la biblioteca de subprocesos Thread, la clase de tarea Task y los bloqueos de estilo RAII, y luego escribimos el archivo ThreadPool. Cuando comenzamos en main.cc, primero creamos un objeto de grupo de subprocesos y luego llamamos a su método de ejecución. El método de ejecución llamará al método de inicio en la biblioteca de subprocesos, pasando la función que ejecutará el subproceso (handlerTask) y los parámetros alimentado a la función ( ThreadData *); en la biblioteca Thread, el método de inicio usará pthread_create() para crear un hilo y regresar a la función (de hecho, un círculo aquí es para resolver el problema de este puntero al llamar el método en la clase, necesitamos poner el método Set en estático, pero los métodos estáticos no pueden usar miembros no estáticos y requieren una estructura grande (ThreadData) que contenga un objeto ThreadPool). A continuación, el subproceso principal empuja continuamente las tareas a la cola de tareas, y los subprocesos en el grupo de subprocesos ejecutan la función handlerTask para determinar si hay tareas (sin espera de bloqueo de tareas). Si hay tareas, obtendrán su propia pila independiente de la cola pública para ejecutar.

4. Modo Singleton basado en grupo de subprocesos

El patrón singleton es un patrón de diseño creacional que garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global para acceder a esta instancia. Este modo se usa a menudo para controlar el acceso a los recursos, como las conexiones de la base de datos, los grupos de subprocesos, etc. La idea central del patrón singleton es limitar el proceso de creación de instancias de una clase a un solo objeto y proporcionar un punto de acceso global para obtener el objeto. Este punto de acceso suele ser un método estático al que se puede llamar desde cualquier parte del programa.

Las ventajas del patrón singleton incluyen:

  1. Asegurarse de que una clase tenga solo una instancia ahorra recursos del sistema.

  2. Se proporciona un punto de acceso global para facilitar la invocación y gestión del programa.

  3. La consistencia del objeto está garantizada y se evita el problema de la inconsistencia de datos causada por múltiples instancias.

  4. El proceso de creación de instancias se puede controlar para garantizar la seguridad y corrección del objeto.

4.1 Ideas de diseño del modo Lazy Man

  1. Privatizar constructores, prohibir la construcción de copias y la sobrecarga de operadores de asignación
  2. Agregue variables miembro en función del grupo de subprocesos: mutex estático, puntero de grupo de subprocesos estáticos ()
  3. Agregue métodos estáticos sobre la base del grupo de subprocesos: responsable de obtener objetos únicos
#include <mutex>
const int gnum = 5;

template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
    
    
public:
    ThreadData(ThreadPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    
    
    }

public:
    ThreadPool<T> *threadpool;
    std::string name;
};

template <class T>
class ThreadPool
{
    
    
private:
    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());
        }
    }

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

    static void *handlerTask(void *args)
    {
    
    
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
    
    
            T t;
            {
    
    
                LockGuard lock(td->threadpool->mutex());
                // td->threadpool->lockQueue();
                while (td->threadpool->isQueueEmpty())
                {
    
    
                    td->threadpool->threadWait();
                }
                // pop本质,就是把任务从公共队列中拿到当前线程独立的栈中
                t = td->threadpool->pop();
                // td->threadpool->unlockQueue();
            }
            std::cout << td->name << "获取了一个" << t.toTaskString() << "并处理完成,结果是" << t() << std::endl;
        }
        delete td;
        return nullptr;
    }

public:
    void lockQueue() {
    
     pthread_mutex_lock(&mutex_); }
    void unlockQueue() {
    
     pthread_mutex_unlock(&mutex_); }
    bool isQueueEmpty() {
    
     return task_queue_.empty(); }
    void threadWait() {
    
     pthread_cond_wait(&cond_, &mutex_); }
    T pop()
    {
    
    
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
    pthread_mutex_t *mutex()
    {
    
    
        return &mutex_;
    }

public:
    void run()
    {
    
    
        for (const auto &t : threads_)
        {
    
    
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            std::cout << t->threadname() << std::endl;
        }
    }

    void push(const T &in)
    {
    
    
        LockGuard lock(&mutex_);
        // pthread_mutex_lock(&mutex_);
        task_queue_.push(in);
        pthread_cond_signal(&cond_);
        // pthread_mutex_unlock(&mutex_);
    }

    ~ThreadPool()
    {
    
    
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);

        for (const auto &t : threads_)
        {
    
    
            delete t;
        }
    }

    static ThreadPool<T> *GetInstance()
    {
    
    
        if (tp == nullptr)
        {
    
    
            //std::lock_guard<mutex> lock(siglock);
            siglock.lock();
            if (nullptr == tp)
            {
    
    
                tp = new ThreadPool<T>();
            }
            siglock.unlock();
        }
        return tp;
    }

private:
    int num_;
    std::vector<Thread *> threads_;
    std::queue<T> task_queue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    volatile static ThreadPool<T> *tp;
    static std::mutex siglock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;

template <class T>
std::mutex  ThreadPool<T>::siglock;
//main.cc
ThreadPool<Task>::GetInstance()->run();

5. Problema de lectura y escritura y bloqueo de lectura y escritura

Al escribir multiproceso, hay una situación que es muy común. Es decir, algunos datos públicos tienen menos posibilidades de modificación. En comparación con la reescritura, sus posibilidades de lectura son mucho mayores. En términos generales, en el proceso de lectura, suele ir acompañado de la operación de búsqueda, que lleva mucho tiempo en el medio. Bloquear este tipo de segmento de código reducirá en gran medida la eficiencia de nuestro programa.

El principio 321 también se aplica al problema del lector-escritor
3 relaciones: el lector y el escritor se excluyen mutuamente y están sincronizados, el lector y el lector no tienen relación, el escritor y el escritor se excluyen mutuamente
2 roles: el lector y el escritor
1 lugar de intercambio

La diferencia esencial entre el problema lector-escritor y el modelo productor-consumidor es que el consumidor tomará los datos, pero el lector no tomará los datos.

Comportamiento de bloqueo de lectura y escritura

estado de bloqueo actual solicitud de bloqueo de lectura solicitud de bloqueo de escritura
sin candado Poder Poder
leer bloqueo Poder bloquear
bloqueo de escritura bloquear bloquear
//初始化读写锁
pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
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);

inserte la descripción de la imagen aquí
Gracias por leer, este es el final de la programación del sistema Linux, y luego actualizaré lentamente los blogs sobre programación de redes y C++.

Supongo que te gusta

Origin blog.csdn.net/m0_54469145/article/details/130473392
Recomendado
Clasificación