Encapsulamento de Threads e Mutexes em C++ para Multithreading no Linux

Encapsulamento de Threads

Esta seção aborda o encapsulamento da biblioteca pthread do Linux em classes C++ para simplificar o uso de threads. O objetivo é criar uma classe que gerencie o ciclo de vida das threads, suporte à vinculação de funções e controle entre modos join e detach.

Pontos-chave técnicos:

  • Uso de std::function para encapsular funções de thread, permitindo flexibilidade na execução de lambdas, funções regulares ou objetos vinculados.
  • Implementação de uma função estática como ponto de entrada para pthread, resolvendo a incompatibilidade de formato entre funções membro C++ (que possuem um ponteior this implícito) e a assinatura void*(*)(void*) exigida pela biblioteca.
  • Passagem do ponteiro this para a função estática via conversão segura static_cast, permitindo o acesso a membros da classe dentro da thread.
  • Gerenciamento de modos de execução: modo padrão join (bloqueia a thread principal até o término) ou modo detach (thread executada em segundo plano), controlado por uma flag booleana.
#pragma once
#include <functional>
#include <pthread.h>
#include <string>

namespace GerenciamentoThreads {
    std::uint32_t contador_ids = 0;

    using TipoFuncaoThread = std::function<void()>;

    enum class EstadoThread {
        NOVA,
        EXECUTANDO,
        FINALIZADA
    };

    class MinhaThread {
    private:
        std::string nome;
        pthread_t identificador;
        EstadoThread estado;
        bool aguardavel;
        TipoFuncaoThread tarefa;

        static void* rotina(void* argumento) {
            MinhaThread* self = static_cast<MinhaThread*>(argumento);
            pthread_setname_np(pthread_self(), self->nome.c_str());
            self->estado = EstadoThread::EXECUTANDO;
            if (!self->aguardavel) {
                pthread_detach(pthread_self());
            }
            self->tarefa();
            return nullptr;
        }

        void configurarNome() {
            nome = "Thread-" + std::to_string(contador_ids++);
        }

    public:
        MinhaThread(TipoFuncaoThread func)
            : estado(EstadoThread::NOVA), aguardavel(true), tarefa(func) {
            configurarNome();
        }

        bool iniciar() {
            if (estado == EstadoThread::EXECUTANDO) return true;
            return pthread_create(&identificador, nullptr, rotina, this) == 0;
        }

        bool juntar() {
            if (aguardavel) {
                pthread_join(identificador, nullptr);
                return true;
            }
            return false;
        }

        void tornarDetacado() {
            if (estado == EstadoThread::NOVA) aguardavel = false;
        }
    };
}

Encapsulamento de Mutexes com RAII

Esta parte foca na proteção de recursos compartilhados em ambientes multithreading usando mutexes. A abordagem utiliza o padrão RAII (Resource Acquisition Is Initialization) para garantir que locks sejam automaticamente liberados, evitando deadlocks e condições de corrida.

Aspectos técnicos essenciais:

  • Encapsulamento de pthread_mutex_t em uma classe Mutex, com métodos para travar, destravar e destruir o recurso.
  • Dseabilitação explícita de cópia e atribuição do mutex para assegurar exclusividade, já que cópias levariam a sincronização inválida.
  • Criação de uma classe GuardaLock que, no construtor, adquire o lock e, no destrutor, o libera automaticamente, promovendo segurança em blocos de código.
  • Uso de referências no GuardaLock para operar no mesmo mutex original, evitando cópias que causariam erros de compilação.
#pragma once
#include <pthread.h>

namespace Sincronizacao {
    class Mutex {
    private:
        pthread_mutex_t recurso_mutex;

    public:
        Mutex(const Mutex&) = delete;
        Mutex& operator=(const Mutex&) = delete;

        Mutex() {
            pthread_mutex_init(&recurso_mutex, nullptr);
        }

        void travar() {
            pthread_mutex_lock(&recurso_mutex);
        }

        void destravar() {
            pthread_mutex_unlock(&recurso_mutex);
        }

        ~Mutex() {
            pthread_mutex_destroy(&recurso_mutex);
        }
    };

    class GuardaLock {
    private:
        Mutex& mutex_ref;

    public:
        explicit GuardaLock(Mutex& mutex) : mutex_ref(mutex) {
            mutex_ref.travar();
        }

        ~GuardaLock() {
            mutex_ref.destravar();
        }
    };
}

Exemplo Prático: Venda de Ingressos com Multithreading

Este exemplo demonstra a aplicação das classes encapsuladas para resolver problemas de thread safety em um cenário de venda de ingressos. Quatro threads competem por um recurso compartilhado (quantidade de ingressos), garantindo atomicidade nas operações e evitando starvation.

Resolução de problemas comuns:

  • Sobrevenda e valores negativos: Acessos concorrentes à variável de ingressos são protegidos por um lock adquirido via GuardaLock, assegurando que decrementos ocorram de forma atômica.
  • Inanição de threads: Após liberar o lock, introduz-se uma chamada usleep para ceder a CPU, permitindo que outras threads tenham oportunidade de adquirir o recurso.
  • Compilação: É necessário vincular a biblioteca pthread usando a flag -lpthread durante a compilação.
#include <unistd.h>
#include <iostream>
#include "Sincronizacao.hpp"

int ingressos_restantes = 1000;
Sincronizacao::Mutex meu_mutex;

void* rotina_venda(void* arg) {
    char* identificador = static_cast<char*>(arg);
    while (true) {
        Sincronizacao::GuardaLock guarda(meu_mutex);
        if (ingressos_restantes > 0) {
            usleep(1000);
            std::cout << identificador << " adquiriu um ingresso. Restam: " << ingressos_restantes << std::endl;
            --ingressos_restantes;
        } else {
            break;
        }
    }
    return nullptr;
}

int main() {
    pthread_t threads[4];
    const char* ids[] = {"Thread-A", "Thread-B", "Thread-C", "Thread-D"};

    for (int i = 0; i < 4; ++i) {
        pthread_create(&threads[i], nullptr, rotina_venda, (void*)ids[i]);
    }

    for (int i = 0; i < 4; ++i) {
        pthread_join(threads[i], nullptr);
    }

    return 0;
}

Para compilar o programa completo, utilize um comando como:

g++ -o aplicacao main.cpp -lpthread

A execução resultará na distribuição equitativa de ingressos entre as threads, sem sobrevenda ou deadlocks.

Tags: Linux C++ pthreads Multithreading Mutex

Publicado em 6-23 16:45