Skip to content

Commit c76cbe2

Browse files
committed
aula18
1 parent ae9e22c commit c76cbe2

8 files changed

Lines changed: 574 additions & 164 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
#include <pthread.h>
3+
#include <stdio.h>
4+
5+
void *minha_thread(void *arg) {
6+
printf("Hello thread!\n");
7+
return NULL;
8+
}
9+
10+
11+
int main() {
12+
pthread_t tid;
13+
14+
int error = pthread_create(&tid, NULL, minha_thread, NULL);
15+
16+
printf("Hello main\n");
17+
18+
pthread_join(tid, NULL);
19+
20+
return 0;
21+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# 18 - Concorrência e Threads
2+
3+
!!! pdf
4+
![](slides.pdf)
5+
6+
<br>
7+
8+
Nossa aula de hoje envolverá aprender a API `pthreads` para criação de threads e sincronização simples.
9+
10+
## Criando tarefas e esperando elas acabarem
11+
12+
O exemplo abaixo cria uma thread que roda a função `primeira_thread`, espera por seu fim e mostra a mensagem *Fim do programa*.
13+
14+
```c
15+
// Funções rodadas em thread sempre tem essa assinatura
16+
void *minha_thread(void *arg) {
17+
printf("Hello thread!\n");
18+
return NULL;
19+
}
20+
21+
....
22+
pthread_t tid;
23+
int error = pthread_create(&tid, NULL, minha_thread, NULL);
24+
pthread_join(tid, NULL); // espera tid acabar.
25+
```
26+
27+
!!! example
28+
Compile o arquivo *exemplo1.c* com a flag especial `-pthread` e execute-o.
29+
30+
<div class="termy">
31+
32+
```console
33+
$ gcc exemplo1.c -o exemplo1 -pthread
34+
$ ./exemplo1
35+
```
36+
37+
</div>
38+
39+
Vamos dissecar a chamada da função `pthread_create`:
40+
41+
```c
42+
int error = pthread_create(
43+
&tid, // variável para guardar ID da nova thread
44+
NULL, // opções de criação. NULL = opções padrão
45+
minha_thread, // função a ser executada
46+
NULL // parâmetro passado para a função acima
47+
);
48+
```
49+
50+
Toda thread que rodarmos terá a seguinte assinatura (mudando, é claro, o nome da função).
51+
52+
```c
53+
void *minha_thread(void *arg);
54+
```
55+
56+
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.
57+
58+
!!! example
59+
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.
60+
61+
<div class="termy">
62+
63+
```console
64+
$ man 7 pthreads
65+
$ man 3 pthread_create
66+
$ man 3 pthread_join
67+
```
68+
69+
</div>
70+
71+
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.
72+
73+
!!! exercise text short
74+
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?
75+
76+
!!! answer "Resposta"
77+
Não. Como a `main` pode chegar no `return 0`, então o processo pode acabar sem que a thread tenha sido devidamente executada.
78+
79+
!!! exercise text short
80+
É possível que duas threads chamem `pthread_join` na mesma thread destino? Consulte o manual para saber esta resposta.
81+
82+
!!! answer "Resposta"
83+
Quando uma thread termina, ela entra num estado chamado “*joinable*” (aguardando ser recolhida). A chamada da função `pthread_join()` faz duas coisas:
84+
85+
* Bloqueia a thread fez a chamada da função `pthread_join()` até outra thread terminar.
86+
* Desaloca os recursos da *thread* que está finalizando (stack, estruturas internas do sistema, etc.), evitando um *memory leak*.
87+
88+
Assim, só uma thread pode “recolher” (*join*) outra — se mais de uma tentar, não há uma regra definida sobre qual delas consegue, e isso gera comportamento indefinido.
89+
90+
Para saber mais acesse a seção **DESCRIPTION** em `man 3 pthread_join`: *If multiple threads simultaneously try to join with the same thread...*
91+
92+
A resposta acima indica que precisaremos de outras primitivas de **sincronização** mais sofisticadas no futuro. Veremos isso nas próximas aulas.
93+
94+
!!! example
95+
Em um novo arquivo `.c`, crie quatro threads, cada uma executando uma função diferente que faz um print distinto. 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?!
96+
97+
## Passando argumentos para threads
98+
99+
Nossas threads ainda são muito limitadas: elas não recebem nenhum argumento nem devolvem resultados. Vamos consertar isso nesta seção.
100+
101+
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.
102+
103+
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.
104+
105+
!!! example
106+
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.
107+
108+
!!! example
109+
Adicione ao seu programa um `for` que cria 4 threads, use o vetor `*vi` para armazenar, em cada posição do vetor, os valores do índice do `for`, e no vetor `*tids` na na chamada da função `pthread_create`. Passe como último argumento da função `pthread_create` o endereço do elemento correspondente de `vi`.
110+
111+
!!! example
112+
Espere pelo fim desta thread.
113+
114+
!!! example
115+
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:
116+
117+
> `int *i = (int *) arg;`
118+
119+
!!! exercise text short
120+
Explique a utilização da variável `i` na tarefa acima.
121+
122+
!!! answer "Resposta"
123+
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.
124+
125+
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.
126+
127+
!!! warning
128+
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 ;)
129+
130+
!!! exercise text short
131+
Explique como é feita a passagem do argumento para a thread.
132+
133+
!!! answer "Resposta"
134+
A thread recebe o endereço da respectiva posição do array alocado dinâmicamente.
135+
136+
137+
!!! exercise text short
138+
Passamos para a thread um valor alocado dinamicamente. Por que isso é necessário?
139+
140+
!!! answer "Resposta"
141+
Vamos discutir depois!
142+
143+
144+
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.
145+
146+
!!! warning
147+
Cada exercício foca em um problema diferente. A resposta não é a mesma para ambas.
148+
149+
!!! exercise text medium
150+
Identifique um problema de escopo de dados no código abaixo (arquivo *parte2-1.c*)
151+
152+
Dica: compile, execute e leia o código para tentar entender o problema!
153+
154+
```c
155+
void *minha_thread(void *arg) {
156+
int *i = (int *) arg;
157+
printf("Hello thread! %d\n", *i);
158+
}
159+
160+
// dentro do main
161+
162+
for (int i = 0; i < 4; i++) {
163+
pthread_create(&tid[i], NULL, minha_thread, &i);
164+
}
165+
```
166+
167+
!!! answer "Resposta"
168+
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.
169+
170+
!!! exercise text medium
171+
Identifique um problema de escopo de dados no código abaixo (arquivo *parte2-2.c*)
172+
173+
Dica: compile, execute e leia o código para tentar entender o problema!
174+
175+
```c
176+
void *minha_thread(void *arg) {
177+
int *i = (int *) arg;
178+
printf("Hello thread! %d\n", *i);
179+
}
180+
181+
pthread_t *criar_threads(int n) {
182+
pthread_t *tids = malloc(sizeof(pthread_t) * n);
183+
184+
for (int i = 0; i < n; i++) {
185+
pthread_create(&tids[i], NULL, minha_thread, &i);
186+
}
187+
188+
return tids;
189+
}
190+
191+
// dentro do main
192+
pthread_t *tids = criar_threads(4);
193+
194+
```
195+
196+
!!! answer "Resposta"
197+
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 `i` criada no `for` da função `criar_threads`, que pode não "existir" mais quando a função `minha_thread` é executada.
198+
199+
200+
!!! warning
201+
Valide sua solução com o professor para garantir que realmente entendeu!
202+
203+
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`.
204+
205+
!!! example
206+
Modifique seu exercício do começo desta parte para receber dois argumentos do tipo inteiro e imprimir ambos valores.
207+
208+
209+
## Retornando valores
210+
211+
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).
212+
213+
!!! example
214+
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`.
215+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <pthread.h>
2+
#include <stdio.h>
3+
4+
void *minha_thread(void *arg) {
5+
int *i = (int *) arg;
6+
printf("Hello thread! %d\n", *i);
7+
8+
return NULL;
9+
}
10+
11+
12+
int main() {
13+
pthread_t tid[4];
14+
15+
for (int i = 0; i < 4; i++) {
16+
pthread_create(&tid[i], NULL, minha_thread, &i);
17+
}
18+
19+
20+
for (int i = 0; i < 4; i++) {
21+
pthread_join(tid[i], NULL);
22+
}
23+
24+
return 0;
25+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
#include <stdio.h>
5+
6+
void *minha_thread(void *arg) {
7+
int *i = (int *) arg;
8+
printf("Hello thread! %d\n", *i);
9+
10+
return NULL;
11+
}
12+
13+
pthread_t *criar_threads(int n) {
14+
pthread_t *tids = malloc(sizeof(pthread_t) * n);
15+
16+
for (int i = 0; i < n; i++) {
17+
pthread_create(&tids[i], NULL, minha_thread, &i);
18+
}
19+
20+
return tids;
21+
}
22+
23+
int main() {
24+
pthread_t *tids = criar_threads(4);
25+
26+
for (int i = 0; i < 4; i++) {
27+
pthread_join(tids[i], NULL);
28+
}
29+
free(tids);
30+
return 0;
31+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Para compilar use:
3+
gcc -g sinais-concorrentes_aula_passada.c -o sinais
4+
5+
para ver o código dos sinais
6+
man 7 signal
7+
SIGINT - 2
8+
SIGTERM - 15
9+
*/
10+
11+
#include <stdio.h>
12+
#include <unistd.h>
13+
#include <signal.h>
14+
15+
int status = 0;
16+
17+
void operacao_lenta() {
18+
sleep(10);
19+
}
20+
21+
22+
void sigint_handler(int num) {
23+
status += 1;
24+
printf("Chamou Ctrl+C; status=%d\n", status);
25+
operacao_lenta();
26+
printf("SIGINT: Vou usar status agora! status=%d\n", status);
27+
28+
}
29+
30+
void sigterm_handler(int num) {
31+
status += 1;
32+
printf("Recebi SIGTERM; status=%d\n", status);
33+
operacao_lenta();
34+
printf("SIGTERM: Vou usar status agora! status=%d\n", status);
35+
}
36+
37+
int main() {
38+
/* TODO: registar SIGINT aqui. */
39+
struct sigaction handler_sigint;
40+
41+
handler_sigint.sa_handler = sigint_handler;
42+
handler_sigint.sa_flags = 0;
43+
sigemptyset(&handler_sigint.sa_mask);
44+
//bloqueia o SIGTERM
45+
sigaddset(&handler_sigint.sa_mask, SIGTERM);
46+
47+
sigaction(SIGINT, &handler_sigint, NULL);
48+
49+
/* TODO: registar SIGTERM aqui. */
50+
51+
struct sigaction handler_sigterm;
52+
53+
handler_sigterm.sa_handler = sigterm_handler;
54+
handler_sigterm.sa_flags = 0;
55+
sigemptyset(&handler_sigterm.sa_mask);
56+
// bloqueia o SIGINT
57+
sigaddset(&handler_sigterm.sa_mask, SIGINT);
58+
59+
sigaction(SIGTERM, &handler_sigterm, NULL);
60+
61+
printf("Meu pid: %d\n", getpid());
62+
63+
while(1) {
64+
sleep(1);
65+
}
66+
return 0;
67+
}
2.33 MB
Binary file not shown.

0 commit comments

Comments
 (0)