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:
Threads seriais
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:
Fábio says:
Acho um pecado não comentar…:)
Artigo simples e de fácil entendimento, ideal para quem não conhece threads e está começando.
Parabéns pelo artigo, assim como pelo restante do blog! Foi para os meus favoritos!
Abraço
Alex says:
realmente, bem explicado e simples, parabéns.
Wagner Reck says:
Muito bom mesmo e em hora oportuna, dentro de poucos dias vou precisar de threads em C++….
Se fosse youtube levava 5 estrelas :p
Eduardo Gurgel says:
Acho que o correto seria vc dizer C Threads. Não existe nada de C++ aqui. Apenas C.
thiago says:
Me desculpe Eduardo, mas não me lembro de C ter classes, herança, etc.
Eduardo says:
Mas a parte de threads não possui Orientação a objetos. Deu pra entender?
thiago says:
Entendido, mas thread faz parte do kernel do sistema operacional. Não existe thread com OO, mas classes worker threads, por exemplo, que são orientadas à objetos.
Alias, se for assim, seguindo essa linha, não existe nada de orientação à objetos pois o kernel de SOs são escritos em C e assembler, e suas interfaces todas em C.
PotHix says:
Æ!!
Bem legal seu artigo cara!
Estou aprendendo C++ de verdade agora ( já sabia o básico mas muito tosco ), e estou precisando mesmo de algum lugar legal para ler um bom conteúdo!
Já assinei os feeds e aguardo novos conteúdos legais!
Parabens!
Há braços
Leonardo L. says:
Acho que o que o Eduardo quis dizer é que o artigo referencia uma API de C, não de C++, para uso de threads. Mas realmente ele não foi muito feliz na afirmação…
O artigo é interessante do ponto de vista didático (“como fazer sua classe de threads”), mas poderia ser mais útil para os programadores de C++ se mostrasse como utilizar uma biblioteca de threads na própria linguagem, como o caso da Boost.Thread.
Aliás, esta biblioteca já encapsula vários dos conceitos abordados aqui, e provém ainda outros, evitando que o programador tenha que reescrever tudo do zero.
Outra questão diz respeito ao conceito de threads: por definição, estas são sempre concorrentes, não existindo essa classificação entre seriais/concorrentes. Aliás, em ambas explicações há concorrência de threads, assim como o problema a resolver é o mesmo – sincronização.
É interessante ficar atento com a parte teórica, já que – mesmo que dois problemas pareçam muito diferentes – muitas vezes a base para resolução destes é exatamente a mesma.
E é isso.
thiago says:
Opa Leonardo!
Obrigado pelo comentário.
Eu coloquei o exemplo de threads /locks pois além da parte didática (que é importante) tem muitos lugares que usam MFC (win32) ou só stl pura e não deixam usar boost e outras libs por razões que só deus sabe, hehe.
Quanto ao conceito de serial/concorrente, eu não encontrei palavra melhor para descrevê-las e explicar o que são pois o estigma de “não use threads” existe muito por aí.
De qualquer forma, aceito sugestões para melhoria do artigo, adições, etc. Se você tiver alguma, por favor, mande, que recebo com muito prazer.
Abração!
Thiago.
GarykPatton says:
Hi! I like your srticle and I would like very much to read some more information on this issue. Will you post some more?
thiago says:
Give me a suggestion 🙂
Regards,
Thiago.
William says:
O código de exemplo que você postou é para Linux, para fazer um código portátil você teria algum exemplo? Desconsiderando o uso da STL.
[]’s