Skip to content

Commit

Permalink
aula18
Browse files Browse the repository at this point in the history
  • Loading branch information
flubacheski committed Apr 28, 2024
1 parent 2c84fa0 commit 7501b57
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 1 deletion.
21 changes: 21 additions & 0 deletions material/aulas/18-threads-I/exemplo1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

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

void *minha_thread(void *arg) {
printf("Hello thread!\n");
return NULL;
}


int main() {
pthread_t tid;

int error = pthread_create(&tid, NULL, minha_thread, NULL);

printf("Hello main\n");

pthread_join(tid, NULL);

return 0;
}
203 changes: 203 additions & 0 deletions material/aulas/18-threads-I/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# 18 - Concorrência e Threads

Nossa aula de hoje envolverá aprender a API `pthreads` para criação de threads e sincronização simples.

## Criando tarefas e esperando elas acabarem

O exemplo abaixo cria uma thread que roda a função `primeira_thread`, espera por seu fim e mostra a mensagem *Fim do programa*.

```c
// Funções rodadas em thread sempre tem essa assinatura
void *minha_thread(void *arg) {
printf("Hello thread!\n");
return NULL;
}

....
pthread_t tid;
int error = pthread_create(&tid, NULL, minha_thread, NULL);
pthread_join(tid, NULL); // espera tid acabar.
```
!!! example
Compile o arquivo *exemplo1.c* com a flag especial `-pthread` e execute-o.
<div class="termy">
```console
$ gcc exemplo1.c -o exemplo1 -pthread
$ ./exemplo1
```
</div>
Vamos dissecar a chamada da função `pthread_create`:
```c
int error = pthread_create(
&tid, // variável para guardar ID da nova thread
NULL, // opções de criação. NULL = opções padrão
minha_thread, // função a ser executada
NULL // parâmetro passado para a função acima
);
```

Toda thread que rodarmos terá a seguinte assinatura (mudando, é claro, o nome da função).

```c
void *minha_thread(void *arg);
```
Uma variável do tipo `void *` representa um endereço de memória cujo conteúdo é desconhecido. Ou seja, ele diz somente onde encontrar os dados, mas não diz o que está guardado na memória naquele lugar. Este tipo de variável é usada quando queremos passar blocos de memória entre funções mas não queremos fixar um tipo de dados. Veremos com mais detalhes como isto funciona na parte 2.
!!! example
O manual contém entradas muito bem escritas de todas as chamadas de POSIX threads que usaremos. Abra as seguintes e se familiarize com seu conteúdo.
<div class="termy">
```console
$ man 7 pthreads
$ man 3 pthread_create
$ man 3 pthread_join
```
</div>
Assim como processos, threads são escalonadas pelo kernel. Isto significa que **não controlamos a ordem em que elas rodam** no nosso programa. Ou seja, ao executar `pthread_create` não sabemos se a thread principal (aquela que roda o `main`) continuará rodando ou se o controle passará instantaneamente para a nova thread. A primitiva de **sincronização** mais simples que dispomos é `pthread_join`, que garante que uma thread só prossegue quando outra acabar.
!!! exercise text short
Retire o `pthread_join` do programa exemplo e o execute. Repita a execução várias vezes. Todas as vezes o resultado é o mesmo? O quê acontece?
!!! answer "Resposta"
Não. Como a `main` pode chegar no `return 0`, então o processo pode acabar sem que a thread tenha sido devidamente executada.
!!! exercise text short
É possível que duas threads chamem `pthread_join` na mesma thread destino? Consulte o manual para saber esta resposta.
!!! answer "Resposta"
A resposta está disponível na seção **DESCRIPTION** ao executar `man 3 pthread_join`: *If multiple threads simultaneously try to join with the same thread...*
A resposta acima indica que precisaremos de outras primitivas de **sincronização** mais sofisticadas no futuro. Veremos isso nas próximas aulas.
!!! example
Em um novo arquivo `.c`, crie quatro threads, cada uma executando uma função que faz um print diferente. Compile e execute seu programa várias vezes. A saída será sempre a mesma, com os printfs sempre na mesma ordem? O que está acontecendo?!
## Passando argumentos para threads
Nossas threads ainda são muito limitadas: elas não recebem nenhum argumento nem devolvem resultados. Vamos consertar isso nesta seção.
Vimos na parte 1 que o último argumento de `pthread_create` é um ponteiro para os dados que nossa função deverá receber. Neste sequência de exercícios iremos aprender a usar este argumento para passar dados para nossas threads.
Nosso primeiro exercício será feito passo a passo. Siga cada um dos passos a risca e depois responda as questões. Vamos trabalhar a partir de um arquivo vazio.
!!! example
Crie um programa simples com uma função `main` que aloca (usando malloc) um vetor `vi` com 4 `int`s e um vetor `tids` com 4 `pthread_t`s.
!!! example
Adicione ao seu programa um `for` que cria 4 threads (colocando seus ids no vetor `tids`). Passe como último argumento o endereço do elemento correspondente de `vi`.
!!! example
Espere pelo fim desta thread.
!!! example
Crie uma função `void *tarefa_print_i(void *arg)` que declara uma variável `int *i` e dá print em seu conteúdo. Inicialize a variável `i` como mostrado abaixo:
> `int *i = (int *) arg;`
!!! exercise text short
Explique a utilização da variável `i` na tarefa acima.
!!! answer "Resposta"
Apontadores `void *` contém somente o endereço do dado, mas sem indicar seu tipo. Ao declarar `i` acima dizemos que queremos interpretar aquele endereço como o endereço de um `int`. Assim, quando fazemos `*i` conseguimos acessar o inteiro presente no endereço passado para a thread.
Se seu programa estiver correto você deverá ver no terminal 4 prints com números de 0 a 3, cada um vindo de um thread.
!!! warning
Se tiver problemas, valide seu código com algum colega que já tenha sido validado pelo professor. Se não tiver ninguém por perto já validado me chame ;)
!!! exercise text short
Explique como é feita a passagem do argumento para a thread.
!!! answer "Resposta"
A thread recebe o endereço da respectiva posição do array alocado dinâmicamente.
!!! exercise text short
Passamos para a thread um valor alocado dinamicamente. Por que isso é necessário?
!!! answer "Resposta"
Vamos discutir depois!
Vamos explorar a resposta da pergunta acima nos próximos exercícios. Para cada exercício, encontre seu problema, descreva-o usando suas próprias palavras e mostre um exemplo de saída possível. Somente depois de escrever sua resposta rode o programa.
!!! warning
Cada exercício foca em um problema diferente. A resposta não é a mesma para ambas.
!!! exercise text medium
Identifique um problema de escopo de dados no código abaixo (arquivo *parte2-1.c*)
Dica: compile, execute e leia o código para tentar entender o problema!
```c
void *minha_thread(void *arg) {
int *i = (int *) arg;
printf("Hello thread! %d\n", *i);
}
// dentro do main
for (int i = 0; i < 4; i++) {
pthread_create(&tid[i], NULL, minha_thread, &i);
}
```
!!! answer "Resposta"
Com threads, não tenho garantir da ordem de escalonamento (não qual thread o sistema operacional vai escolher para execução, nem em qual ordem). Assim, a thread da `main` altera o valor da variável `i` e quando cada thread executa, o valor recuperado é diferente do esperado.
!!! exercise text medium
Identifique um problema de escopo de dados no código abaixo (arquivo *parte2-2.c*)
Dica: compile, execute e leia o código para tentar entender o problema!
```c
void *minha_thread(void *arg) {
int *i = (int *) arg;
printf("Hello thread! %d\n", *i);
}
pthread_t *criar_threads(int n) {
pthread_t *tids = malloc(sizeof(pthread_t) * n);
for (int i = 0; i < n; i++) {
pthread_create(&tids[i], NULL, minha_thread, &i);
}
return tids;
}
// dentro do main
pthread_t *tids = criar_threads(4);
```
!!! answer "Resposta"
Lembra da pilha ou `stack`? Veja nos slides, cada thread tem o seu próprio espaço para guardar suas variáveis locais. Quando uma função é finalizada, o espaço alocado a ela na `stack` pode ser reutilizado. Neste exemplo, cada thread da `minha_thread` tenta ler uma variável local criada na função `criar_threads`, que pode não "existir" mais.
!!! warning
Valide sua solução com o professor para garantir que realmente entendeu!
Agora que já entendemos como passar um argumento e que devemos sempre colocá-lo no *heap*, passar vários é muito simples: alocamos um `struct` com todos os dados que queremos enviar e passamos seu endereço no último argumento. Ao recebê-lo, a função faz um *cast* de `void *` para um ponteiro para o `struct`.
!!! example
Modifique seu exercício do começo desta parte para receber dois argumentos do tipo inteiro e imprimir ambos valores.
## Retornando valores
Na prática, ao passar `struct`s para threads como argumentos já sabemos como retornar valores: basta adicionar um campo que própria thread deve preencher com o resultado de sua execução. Isso é equivalente a criar uma função que retorna valores em variáveis passadas por referência (ou seja, escrevendo em variáveis passadas como ponteiros).
!!! example
Modifique seu exercício da parte anterior para que as threads retornem a multiplicação dos dois inteiros passados. Faça o print deste valor no `main`.
Binary file added material/aulas/18-threads-I/main
Binary file not shown.
25 changes: 25 additions & 0 deletions material/aulas/18-threads-I/parte2-1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <pthread.h>
#include <stdio.h>

void *minha_thread(void *arg) {
int *i = (int *) arg;
printf("Hello thread! %d\n", *i);

return NULL;
}


int main() {
pthread_t tid[4];

for (int i = 0; i < 4; i++) {
pthread_create(&tid[i], NULL, minha_thread, &i);
}


for (int i = 0; i < 4; i++) {
pthread_join(tid[i], NULL);
}

return 0;
}
30 changes: 30 additions & 0 deletions material/aulas/18-threads-I/parte2-2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>

void *minha_thread(void *arg) {
int *i = (int *) arg;
printf("Hello thread! %d\n", *i);

return NULL;
}

pthread_t *criar_threads(int n) {
pthread_t *tids = malloc(sizeof(pthread_t) * n);

for (int i = 0; i < n; i++) {
pthread_create(&tids[i], NULL, minha_thread, &i);
}

return tids;
}

int main() {
pthread_t *tids = criar_threads(4);

for (int i = 0; i < 4; i++) {
pthread_join(tids[i], NULL);
}

return 0;
}
57 changes: 57 additions & 0 deletions material/aulas/18-threads-I/sinais-concorrentes_aula_passada.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int status = 0;

void operacao_lenta() {
sleep(10);
}


void sigint_handler(int num) {
status += 1;
printf("Chamou Ctrl+C; status=%d\n", status);
operacao_lenta();
printf("SIGINT: Vou usar status agora! status=%d\n", status);

}

void sigterm_handler(int num) {
status += 1;
printf("Recebi SIGTERM; status=%d\n", status);
operacao_lenta();
printf("SIGTERM: Vou usar status agora! status=%d\n", status);
}

int main() {
/* TODO: registar SIGINT aqui. */
struct sigaction handler_sigint;

handler_sigint.sa_handler = sigint_handler;
handler_sigint.sa_flags = 0;
sigemptyset(&handler_sigint.sa_mask);
// bloqueia o SIGTERM
sigaddset(&handler_sigint.sa_mask, SIGTERM);

sigaction(SIGINT, &handler_sigint, NULL);

/* TODO: registar SIGTERM aqui. */

struct sigaction handler_sigterm;

handler_sigterm.sa_handler = sigterm_handler;
handler_sigterm.sa_flags = 0;
sigemptyset(&handler_sigterm.sa_mask);
// bloqueia o SIGINT
sigaddset(&handler_sigterm.sa_mask, SIGINT);

sigaction(SIGTERM, &handler_sigterm, NULL);

printf("Meu pid: %d\n", getpid());

while(1) {
sleep(1);
}
return 0;
}
Binary file added material/aulas/18-threads-I/slides.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ nav:
- aulas/15-entrada-saida/index.md
- aulas/16-sinais-I/index.md
- aulas/17-sinais-II/index.md
#- aulas/18-threads-I/index.md
- aulas/18-threads-I/index.md
#- aulas/19-sincronizacao/index.md
#- aulas/20-semaforos/index.md
#- aulas/21-semaforos-II/index.md
Expand Down

0 comments on commit 7501b57

Please sign in to comment.