Blogaro!

C++ – Mutex / Critical Section

Created by Thiago Guedes on 2008-12-01 04:07:07

Tags: #c++   #programação  

Saluton!
Este é o primeiro de uma série de artigos de C++ sobre o que fazer e o que nunca fazer em c++.

É claro que fazer ou não fazer não é uma lei, mas uma boa prática, pois temos sistemas que devido a certos requisitos ou situações não podemos fazer as coisas da forma que gostariamos.

Para começar, vamos falar de mutex ou critical sections.
O que são e para que servem ?

Critical Section é uma forma de garantir que um trecho de código será acessado por somente uma thread/processo por vez, garantindo assim que uma thread não apagará algo que outra está lendo, por exemplo.
Para ficar mais claro, imaginemos uma fila de mensagens, aonde uma thread recebe mensagens via socket e põe um uma fila, e outra consome e processa. Se não tivermos uma critical section garantindo que só uma delas acessa um trecho de código por vez pode ocorrer da thread de processamento retirar uma mensagem da fila de leitura e a thread de recebimento tentar inserir uma nova mensagem depois dela por não saber que a mensagem já foi pegada e assim perdendo a mensagem quando esta for liberada.

Antes de dar a minha opinião sobre como fazer, vamos mostrar o que nunca fazer:

pthread_mutex_t mutex;

void inicializa() {
	pthread_mutex_init( &mutex, NULL );
}

void finaliza() {
	pthread_mutex_destroy( &mutex );
}

bool imprimeMensagem() {

	pthread_mutex_lock( &mutex );

	//Vamos pegar uma mensagem de uma
	//fila, por exemplo, e imprimí-la
	string msg;
	myQueue->get(&msg);
	cout << msg << endl;

	pthread_mutex_unlock( &mutex );

	//de acordo com alguma regra
	return true; //ou false

}

int main(int argc, char **argv) {
	inicializa();

	while(imprimeMensagem());

	finaliza();
}

O grande problema deste tipo de implementação (muito comum em programas feitos em C) é que um vacilo na codificação, principalmente tendo funções grandes, colocando um “if( msg == NULL ) return false;” sem fazer um unlock da critical section faz com que a thread trave e esses tipos de erros não são fáceis de serem achados.

pthread_mutex_t mutex;

void inicializa() {
	pthread_mutex_init( &mutex, NULL );
}

void finaliza() {
	pthread_mutex_destroy( &mutex );
}

void doSomeAction() {
	pthread_mutex_lock( &mutex );
	//vamos executar o início de uma ação
...
}

void doOtherAction() {
//E aqui uma outra ação qualquer
	pthread_mutex_unlock( &mutex );
}

Este pior ainda pois quem chama os métodos, se não olhar o corpo deles não saberá da critical section e a chance de sair sem fazer o unlock é maior ainda.

Como implementar então ?
A forma mais segura de implementar é uma forma na qual a critical section nunca será esquecida lockada (exceto alguns loops infinitos, mas ninguém faz milagres também).
Isso pode ser feito usando-se uma classe dentro de um contexto, como mostrado abaixo

#include <stdio.h>
#include <pthread.h>

class ICriticalSection {
public:
	virtual void initialize()=0;
	virtual void destroy()=0;
	virtual void lock()=0;
	virtual void unlock()=0;
};

class CriticalSection : public ICriticalSection {
public:
	pthread_mutex_t mutex;
	CriticalSection()
	{
		initialize();
	}
	~CriticalSection() {
		destroy();
	}
	void initialize()
	{
		pthread_mutex_init( &mutex, NULL );
	}
	void destroy()
	{
	    pthread_mutex_destroy( &mutex );
	}
	void lock()
	{
	    pthread_mutex_lock( &mutex );
	}
	void unlock()
	{
	    pthread_mutex_unlock( &mutex );
	}
};

class Locker {
private:
	ICriticalSection *critical;
	bool locked;
public:
	Locker(ICriticalSection &critical) {
		this->critical = &critical;
		locked = false;
	}
	~Locker() {
		if( this->critical != NULL && locked ) {
			this->critical->unlock();
		}
	}
	void lock() {
		this->critical->lock();
		locked = true;
	}
	void unlock() {
		locked = false;
		this->critical->unlock();
	}

};

CriticalSection cs;

int someMethod(int argc, char **argv) {
        //Note que locker está com o contexto fechado.
        //Após sair de someMethod, a class locker irá
        //automáticamente fazer o unlock.
        //Veja também que a classe CriticalSection
        //não pode ser local.
        Locker locker(cs);
        locker.lock();

        printf("teste do Thiago\n");
        return 0;
}

Bom, é isso. Qualquer sugestões, críticas, etc, me contatem.
Até o próximo artigo!


Old comments

  1. Paulo Cesar says:

    2008-12-04 at 18:03

    Nossa, isso seria muito útil nas minhas aulas de SO, pois o professor era horrível… Tive que aprender do jeito mais difícil: sofrendo! hehe

    A propósito, este template do seu blog é FANTÁSTICO!

    Reply

  2. thiago says:

    2008-12-05 at 10:25

    Obrigado!
    Quando eu fiz faculdade também tive um professor ruim de SO, mas o que fazer?

    Reply

  3. Rafael says:

    2008-12-05 at 11:45

    Bem vindo ao clube dos alunos com professores toscos de SO na faculdade.
    Será que alguma faculdade no Brasil tem curso de SO decente?

    Reply

  4. Leonardo L. says:

    2008-12-07 at 16:26

    Legal o texto, realmente é uma boa referência em português ao conceito de mutexes por contexto.

    Há uma implementação disponível deste e de outros conceitos de mutexes na biblioteca Boost.Threads, que podem ser consultados em:

    http://www.boost.org/doc/libs/1_37_0/doc/html/thread/synchronization.html

    Abraços!

    Reply

  5. Miguel says:

    2008-12-16 at 12:48

    Há um livro em bom português brasileiro que trata EXCLUSIVAMENTE de programação paralela utilizando a Win32API:

    PROGRAMAÇÃO CONCORRENTE EM AMBIENTE WINDOWS
    De Constantino Seixas Filho & Marcelo Szuster.
    Editora UFMG
    ISBN 857041318-1

    Difícil de encontrar, mas referência obrigatória.