Quais são as vantagens e desvantagens do uso de threads em relação ao uso de processos?

O que são?

No início da computação, os primeiros sistemas operacionais conseguiam executar apenas uma tarefa por processo. Isso passou a se tornar inviável, pois as aplicações precisavam cada vez mais realizar várias tarefas ao mesmo tempo, ou seja, veio à tona a importância de fazer com que várias tarefa pudessem ser executadas dentro de um mesmo processo.
Threads estão relacionadas na maneira que o computador gerencia as tarefas em demanda através de processos para tratá-las. Mais especificamente, “cada fluxo de execução do sistema, seja associado a um processo ou no interior do núcleo, é denominado thread” (Maziero, 2017, p. 42).
As threads podem ser divididas em threads de usuário, as quais são executadas dentro de um processo e correspondem às tarefas executadas, e threads de núcleo, que correspondem à fluxos de execução reconhecidos e gerenciados pelo núcleo do sistema, ou também chamadas de kernel threads.

Modelos de thread

N:1

Os primeiros sistemas operacionais não tinham suporte a threads, fazendo com que os desenvolvedores elaborassem uma biblioteca, na qual eram definidas as threads para a aplicação desejada. Assim, um processo executaria em várias threads, definidas pela biblioteca, mas chegaria ao núcleo na forma de apenas uma thread.
As vantagens deste modelo estão em sua leveza e fácil implementação. No entanto, suas desvantagens estão nas operações de entrada/saída e na divisão de recursos. Se um processo exigir uma operação de entrada/saída, a thread de núcleo responsável por esse processo terá que aguardar essa informação, interrompendo as threads de usuário desse processo.

Quais são as vantagens e desvantagens do uso de threads em relação ao uso de processos?

Exemplo de modelo de threads N:1 (Maziero, Sistemas Operacionais: conceitos e mecanismos, p. 43).

1:1

Nesse modelo, cada thread de usuário possui uma thread correspondente no núcleo. Essa inovação fez com que não fosse mais necessária a implementação de bibliotecas para as threads de usuário.
Além disso, outro benefício desse modelo foi que, se um processo faz com que sua thread de núcleo seja suspensa, as demais threads de núcleo não são afetadas. Nesse modelo, era possível, ainda, fazer com que mais threads da mesma aplicação fossem executados ao mesmo tempo, caso o hardware do sistema possuísse mais de um processador.

Quais são as vantagens e desvantagens do uso de threads em relação ao uso de processos?

Exemplo de modelo de threads 1:1 (Maziero, Sistemas Operacionais: conceitos e mecanismos, p. 44).

N:M

O modelo N:M pode ser visto como uma forma híbrida, pois possui características dos dois modelos anteriores. Nele as threads de usuário são gerenciadas por uma biblioteca, passando-os em uma ou mais threads de núcleo. Tornando possível, assim, ajustes dependo da aplicação.

Quais são as vantagens e desvantagens do uso de threads em relação ao uso de processos?

Exemplo de modelo de threads N:M (Maziero, Sistemas Operacionais: conceitos e mecanismos, p. 44).

No quadro apresentado a seguir é possível comparar de maneira resumida as diferenças e semelhanças entre esses modelos de trheads.

Quais são as vantagens e desvantagens do uso de threads em relação ao uso de processos?

Comparação entre os modelos N:1, 1:1, N:M (Maziero, Sistemas Operacionais: conceitos e mecanismos, p. 45).

No Linux as threads de kernel são tão otimizadas que são consideradas melhores que as threads de usuário e são as mais usadas em todos os cenários, exceto onde o requisito principal é o de multitarefa cooperativa.

Como criar threads no Linux

Identificação de thread

Assim como um processo é identificado através do ID do processo, uma thread é identificada pela sua ID de thread.

  • Um process ID é único em todo o sistema e uma thread ID é único somente no contexto do processo o qual pertence.
  • O process ID é um valor inteiro, mas o thread ID pode não seguir a mesma regra. Pode muito bem ser um structure.
  • Um process ID pode ser facilmente mostrado, um thread ID não.

O thread ID é representado pelo tipo ‘pthread_t’. E como muitas vezes esse tipo é um structure, então deve haver uma função que compara dois IDs.

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2); 

Essa função compara dois threads ID, retorna zero se forem diferentes e valor diferente de zero se forem iguais.

Se uma thread precisa saber seu próprio ID a seguinte função o retorna:

#include <pthread.h>
pthread_t pthread_self(void);

Criação de thread

Geralmente quando um programa inicia e se torna um processo, ele começa com uma thread padrão, então é verdadeira a afirmação de que todo processo tem pelo menos uma thread de controle. Um processo pode criar mais threads usando a seguinte função:

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr, void *(*start_rtn)(void), 
void *restrict arg);

Essa função requer quatro argumentos:

  • O primeiro é um endereço do tipo pthread_t. Uma vez que a função foi executada com sucesso, a variável cujo endereço é passado aqui vai conter o ID da thread recém criada.
  • O segundo contém certos atributos que queremos que a thread contenha, como prioridade.
  • O terceiro é um ponteiro de função. Cada thread começa com uma função que o endereço é passado aqui.
  • Como a função passada no terceiro argumento também pode aceitar argumentos, tais argumentos podem ser passados aqui na forma de um ponteiro para um tipo void. Isso devido se a função aceitar mais de um argumento então esse ponteiro pode ser um ponteiro para uma structure que contém tais argumentos.

Um exemplo prático

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
 
pthread_t tid[2];
 
void* doSomeThing(void *arg)
{
    unsigned long i = 0;
    pthread_t id = pthread_self();
 
    if(pthread_equal(id,tid[0]))
    {
        printf("\n First thread processing\n");
    }
    else
    {
        printf("\n Second thread processing\n");
    }
 
    for(i=0; i<(0xFFFFFFFF);i++);
 
    return NULL;
}
 
int main(void)
{
    int i = 0;
    int err;
 
    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        else
            printf("\n Thread created successfully\n");
 
        i++;
    }
 
    sleep(5);
    return 0;
}

O código acima usa a função pthread_create() para criar duas threads com a mesma função inicial. Dentro da thread as funções pthread_self() e pthread_equal() são usadas para identificar se a thread executando é a primeira ou segunda, tem também um loop para simular um tempo consumido.

O código possui a seguinte saída:

$ ./threads
Thread created successfully
First thread processing
Thread created successfully
Second thread processing

Conforme visto, a primeira thread é crida e então processada daí a segunda é criada e então processada. Vale notar que a ordem de execução não é fixa, depende do algoritmo de escalonamento do SO.

Encerramento de threads

Conforme dito anteriormente cada programa contém pelo menos uma thread na qual é executada a função main(). Se a thread principal (que executa a  main()) termina, todas as outras threads do processo são encerradas também. Portanto o tempo de vida máximo de uma thread executando no programa é o tempo de vida da thread principal.

Se queremos que a thread principal espere que as outras terminem deve ser usado a função pthread_join().

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

Essa função faz com que a thread espere até que a thread  cujo ID foi passado no primeiro argumento da função termine antes de encerrar. O segundo argumento é o valor de retorno da thread que foi esperada, se não há interesse no valor o argumento pode ser NULL.

Threads podem terminar de três maneiras:

  1. Retornando da rotina que estava fazendo;
  2. Sendo cancelada por outra thread. (Função pthread_cancel());
  3. Chamando a função pthread_exit() no próprio código.

Olhando a função pthread_exit()

#include <pthread.h>
void pthread_exit(void *rval_ptr);

Possui só um argumento, que é o valor de retorno da thread que chamou a função. É o valor acessado pela thread que está esperando com a função pthread_join() explicada antes.

Exemplo em C de encerramento de thread

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
 
pthread_t tid[2];
int ret1,ret2;
 
void* doSomeThing(void *arg)
{
    unsigned long i = 0;
    pthread_t id = pthread_self();
 
    for(i=0; i<(0xFFFFFFFF);i++);
 
    if(pthread_equal(id,tid[0]))
    {
        printf("\n First thread processing done\n");
        ret1  = 100;
        pthread_exit(&ret1);
    }
    else
    {
        printf("\n Second thread processing done\n");
        ret2  = 200;
        pthread_exit(&ret2);
    }
 
    return NULL;
}
 
int main(void)
{
    int i = 0;  
    int err;
    int *ptr[2];
 
    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        else
            printf("\n Thread created successfully\n");
 
        i++;
    }
 
    pthread_join(tid[0], (void**)&(ptr[0]));
    pthread_join(tid[1], (void**)&(ptr[1]));
 
    printf("\n return value from first thread is [%d]\n", *ptr[0]);
    printf("\n return value from second thread is [%d]\n", *ptr[1]);
 
    return 0;
}

Esse código cria duas thread com a mesma função, então sai dela através da função pthread_exit() passando um valor de retorno. Na main() as threads são esperadas com a função pthread_join() e depois que ambas as threads terminam seus valores de retorno são acessados através do segundo argumento da função pthread_join().

A saída é a seguinte:

$ ./threads
Thread created successfully
Thread created successfully
First thread processing done
Second thread processing done
return value from first thread is [100]
return value from second thread is [200]

REFERÊNCIAS
MAZIERO, Dr. Prof. Carlos A. Sistemas Operacionais: Conceitos e Mecanismos. DINF – UFPR. 4 de agosto 2017.

ARORA, Himanshu. Introduction To Linux Threads – Part I, II e III. The Geek Stuff. 30/03/2012 Disponível em: <http://www.thegeekstuff.com/2012/03/linux-threads-intro/?utm_source=tuicool>. Acesso em: 07 de set. de 2017

Imagem de fundo: https://i.ytimg.com/vi/X_b3wbXuI0c/hqdefault.jpg

Quais são as vantagens e desvantagens do uso de thread em relação ao uso de processos?

A comunicação entre threads é mais rápida do que a comunicação entre processos - porque as threads compartilham tudo: espaço de endereçamento, variáveis globais etc; Multi-programação usando o modelo de threads é mais simples e mais portável do que multi-programação usando múltiplos processos.

Quais as vantagens e aplicações de threads?

Os threads possuem vantagens e desvantagens ao dividir um programa em vários processos. Uma das vantagens é que isso facilita o desenvolvimento, visto que torna possível elaborar e criar o programa em módulos, experimentando-os isoladamente no lugar de escrever em um único bloco de código.

Quais são as principais diferenças entre processos e threads?

Resumindo: Um processo é um programa em execução, somado ao seu contexto. Threads: É um processo com múltiplos fluxos de controle.

Qual a importância da utilização de threads nos processos programas para o funcionamento do computador?

Threads permitem que múltiplas execuções ocorram no mesmo ambiente do aplicativo com um grande grau de independência uma da outra, portanto, se temos muitas threads executando em paralelo no sistema é análogo a múltiplos aplicativos executando em paralelo em um computador.