Blogaro!

C++ - Threads

Created by Thiago Guedes on 2008-12-22 02:00:31

Tags: #c++   #programação  

Continuando a série “O que fazer e o que não fazer em C++”, vamos hoje falar de threads. Threads, famosas threads.

As threads tem um estigma forte de “só use se realmente for necessário, e eu concordo com isso, mas o caso é que geralmente é preciso usar threads para várias coisas.

Podemos separá-las em dois grupos básicos:

  1. Threads seriais

  2. Threads concorrentes

Neste caso, as problemáticas são as concorrentes. Vamos entender o porquê.
Threads seriais não precisam de sincronização.  Podemos citar como exemplo uma thread que recebe mensagens via socket e coloca em uma fila, e outra thread vai remover pacotes dessa fila. O único cuidado que temos é de colocar corretamente os locks na fila usada para não ocorrer uma leitura de memória inválida.

Já as threads concorrentes são threads que executam um processamento qualquer e uma depende da finalização da outra para executar algo, ou de uma sinalização de outra.
Ex: Um thread recebe um comando e sinaliza outra thread que faz algum tipo de processamento no comando. Depois da outra thread responder, ela pode dar continuidade no processamento. Isso é muito utilizado em processamento paralelo.

Isso de qualquer forma, precisa-se de um livro para ser discutido, então vamos falar de algo que é cabível em um artigo, que é um outro problema comum (e só do que eu deveria estar falando), e é a forma como uma thread é implementada. Para termos um exemplo, uma clássica implementação de thread é:

m_error = pthread_create(&m_thread, NULL, _exec, (void *)this);

static void *_exec(void *instance)
{
  while( ... ) {
    //...
  }
  pthread_exit(ret);
}

O grande problema da implementação acima é que é muito fácil de se esquecer de sinalizar o fim do programa, pois precisa-se passar um contexto para ele e dentro dos loops da thread SEMPRE verificar essa variável/evento, e acontece a famosa thread fantasma que não faz nada, nem responde e as vezes fica segurando o programa no ar.

Seguindo a implementação abaixo, isso já não é necessário, pois temos uma classe com um stop e que se declarada dentro de um contexto, não temos loops e ao sair-se do contexto a thread se fecha automaticamente. Vamos ver:

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

class WorkerThread {
public:
	WorkerThread(std::string threadName) {
		m_threadName = threadName;
		m_error = 0;
		m_running = false;
	}

	~WorkerThread() {
		stop();
	}

	bool start() {
		m_running = true;
		m_error = pthread_create(&m_thread, NULL, _exec, (void *)this);
		if( m_error != 0 ) return false;
	}

	void stop() {
		m_running = false;
	}

	static void *_exec(void *instance) {

		WorkerThread *pThis = reinterpret_cast<WorkerThread *>(instance);
		bool exitRet=true;
		while( (exitRet = pThis->run()) && pThis->isRunning() ) {
			;
		}

		void *ret;
		ret = (void *)(exitRet?0:-1);
		pthread_exit(ret);

	}

	virtual bool run()=0;

	__inline useconds_t isRunning() { return m_running; }

private:

	std::string m_threadName;
	pthread_t m_thread;
	int m_error;
	bool m_running;

};

class ThreadHello : public WorkerThread
{
public:

ThreadHello(int num) : WorkerThread("ThreadHello")
{
	m_num = num;
}

bool run() {

	//Repare que aqui não temos while!
	printf("Thread num: %d\n", m_num);
	if( m_num == 0 ) return false;

	sleep(m_num);
	return true;
}

private:
	int m_num;
};

int main (int argc, char *argv[])
{

	ThreadHello t1(0);
	ThreadHello t2(1);
	ThreadHello t3(2);
	ThreadHello t4(3);
	ThreadHello t5(4);
	ThreadHello t6(5);

	printf("pressione qquer tecla para sair\n");

	t1.start();
	t2.start();
	t3.start();
	t4.start();
	t5.start();
	t6.start();
	getchar();

	t1.stop();
	t2.stop();

	return 0;  
}

Como vemos, nesta implementação ficarmos com a thread sempre associada a uma classe, fica muito mais fácil de gerenciar uma thread.

Sugestões e comentários são sempre bem vindos!

Abraços e até a próxima!


Coloco aqui também os comentários originais: