Junção de threads

Unir um fio significa esperar que ele finalize, o que pode ser visto como um uso específico de variáveis de condição.

Waiting for a thread

Usando o pthread_join subroutine alows um fio a esperar por outro fio a finalizar. Condições mais complexas, como a espera de várias threads para finalizar, podem ser implementadas usando variáveis de condição.

Chamando a subroutine pthread_join

A subroutina pthread_join bloqueia a thread de chamada até que o encadeamento especificado seja finalizado. O fio de destino (o fio cuja finalização é aguardada) não deve ser descolado. Se o encadeamento de destino já estiver finalizado, mas não descolado, a subroutine pthread_join retorna imediatamente. Depois que um encadeamento de destino foi unido, ele é automaticamente descolado, e seu armazenamento pode ser reafirmado.

A tabela a seguir indica os casos possíveis quando uma thread chama a subroutine pthread_join , dependendo do estado e do atributo detachstate do thread de destino.

Estado de Destino Alvo desdetetado Alvo descolado
O destino ainda está em execução O caldo está bloqueado até que o alvo seja finalizado. A chamada retorna imediatamente, indicando um erro.
O destino está finalizado A chamada retorna imediatamente, indicando uma conclusão bem-sucedida.  

Várias joias

Várias threads podem aderir ao mesmo encadeamento de destino, se o destino não for descolado. O sucesso desta operação depende da ordem das chamadas para a subroutine pthread_join e o momento em que o encadeamento de destino finaliza.

  • Qualquer chamada para a subroutine pthread_join ocorrendo antes da finalização da thread de destino bloqueia o encadeamento de chamadas.
  • Quando o encadeamento de destino finaliza, todas as threads bloqueadas são awoken, e o encadeamento de destino é automaticamente descolado.
  • Qualquer chamada para a subroutine pthread_join ocorrendo após a finalização da thread de destino falhará, pois o encadeamento é descolado pela junção anterior.
  • Se nenhum fio chamado de subroutine pthread_join antes da finalização da thread de destino, a primeira chamada para a subroutine pthread_join retornará imediatamente, indicando uma conclusão bem-sucedida, e qualquer nova chamada falhará.

Aderir exemplo

No exemplo a seguir, o programa termina depois de exatamente cinco mensagens serem exibidas em cada idioma. Isso é feito por meio do bloqueio da thread inicial até a saída de threads "writer".
#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */

void *Thread(void *string)
{
        int i;
 
        /* writes five messages and exits */
        for (i=0; i<5; i++)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}

int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_attr_t attr;
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;

        /* creates the right attribute */
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr,
                PTHREAD_CREATE_UNDETACHED);

        /* creates both threads */
        rc = pthread_create(&e_th, &attr, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, &attr, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_attr_destroy(&attr);

        /* joins the threads */
        pthread_join(e_th, NULL);
        pthread_join(f_th, NULL);
 
        pthread_exit(NULL);
}

Um thread não pode se associar a si mesmo porque um deadlock ocorreria e ele é detectado pela biblioteca. No entanto, duas threads podem tentar juntar-se umas às outras. Eles vão deadlock, mas essa situação não é detectada pela biblioteca.

Retornando informações de um encadeamento

A subroutine pthread_join também permite que uma thread retorne informações para outro encadeamento. Quando uma thread chama o subroutine pthread_exit ou quando ele retorna de sua rotina de ponto de entrada, ele retorna um pointer (veja Saindo um Thread). Este ponteiro é armazenado enquanto a thread não for descolada, e a subroutine pthread_join pode retorná-lo.

Por exemplo, um comando multithreaded grep pode escolher a implementação no exemplo a seguir. Neste exemplo, a thread inicial cria uma thread por arquivo para digitalizar, cada thread tendo a mesma rotina de ponto de entrada. O thread inicial então espera que todas as threads sejam finalizadas. Cada thread "scanning" armazena as linhas encontradas em um buffer alocado dinamicamente e retorna um ponteiro para este buffer. O fio inicial imprime cada buffer e libera-o.
/* "scanning" thread */
...
buffer = malloc(...);
        /* finds the search pattern in the file 
           and stores the lines in the buffer   */
return (buffer);

/* initial thread */
...
for (/* each created thread */) {
        void *buf;
        pthread_join(thread, &buf);
        if (buf != NULL) {
                /* print all the lines in the buffer,
                        preceded by the filename of the thread */
                free(buf);
        }
}
...

Se a thread de destino for cancelada, a sub-rotina pthread_join retornará um valor de -1 convertido em um ponteiro (consulte Cancelamento de uma thread). Como -1 não pode ser um valor de ponteiro, obter -1 como ponteiro retornado de um thread significa que o thread foi cancelado.

O ponteiro retornado pode apontar para qualquer tipo de dado. O ponteiro ainda deve ser válido depois que o fio foi finalizado e seu armazenamento reafirmado. Portanto, evite retornar um valor, pois a rotina do destruidor é chamada quando o armazenamento da thread é reafirmado.

O retorno de um ponteiro para armazenamento alocado dinamicamente para vários threads precisa de consideração especial. Considere o fragmento de código a seguir:
void *returned_data;
...
pthread_join(target_thread, &returned_data);
/* retrieves information from returned_data */
free(returned_data);
O ponteiro returned_data é liberado quando ele é executado por apenas um fio. Se várias threads executam o fragmento de código acima conatualmente, o ponteiro returned_data é liberado várias vezes; uma situação que deve ser evitada. Para evitar isso, use um sinalizador de mutex protegido para sinalizar que o ponteiro returned_data foi liberado. A linha a seguir a partir do exemplo anterior:
free(returned_data);
seria substituído pelas linhas a seguir, em que um mutex pode ser usado para bloqueio do acesso à região crítica (assumindo que a variável flag é inicialmente 0):
/* lock - entering a critical region, no other thread should
                run this portion of code concurrently */
if (!flag) {
        free(returned_data);
        flag = 1;
}
/* unlock - exiting the critical region */

O acesso ao bloqueio à região crítica garante que o ponteiro returned_data seja liberado apenas uma vez.

Ao retornar um ponteiro para alocar dinamicamente o armazenamento para várias threads todas executando código diferente, você deve garantir que exatamente um fio libera o ponteiro.