mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 13:43:05 +02:00
310 lines
13 KiB
Markdown
310 lines
13 KiB
Markdown
# Pytanie 32: Komunikacja synchroniczna/asynchroniczna, blokująca/nieblokująca
|
|
|
|
## Pytanie
|
|
**"Podać definicję komunikacji synchronicznej i asynchronicznej oraz blokującej i nieblokującej. Jak uniknąć zakleszczenia, gdy dwa symetryczne procesy (np. realizujące algorytm iteracyjny Jacobiego) mają w kodzie następujące po sobie wywołania funkcji wysyłającej komunikat do partnera i odbierającej komunikat wysłany przez niego?"**
|
|
|
|
Przedmiot: PORR (Programowanie Równoległe i Rozproszone)
|
|
|
|
---
|
|
|
|
## 📚 Odpowiedź główna
|
|
|
|
### 1. Definicje podstawowe
|
|
|
|
#### Synchroniczna vs Asynchroniczna
|
|
|
|
```
|
|
KOMUNIKACJA SYNCHRONICZNA:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Nadawca i odbiorca synchronizują się w momencie przekazania │
|
|
│ │
|
|
│ Proces A Proces B │
|
|
│ │ │ │
|
|
│ │ send() ──────────────────> recv() │
|
|
│ │ [czeka aż B odbierze] [odbiera] │
|
|
│ │ [kontynuuje] │ │
|
|
│ │
|
|
│ Gwarancja: Po powrocie z send() wiadomość została odebrana │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
KOMUNIKACJA ASYNCHRONICZNA:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Nadawca nie czeka na odbiorcę │
|
|
│ │
|
|
│ Proces A Proces B │
|
|
│ │ │ │
|
|
│ │ send() ─────────> [bufor] ───> recv() │
|
|
│ │ [kontynuuje │ (później) │
|
|
│ │ natychmiast] │ │
|
|
│ │
|
|
│ Gwarancja: Wiadomość trafiła do bufora (nie do odbiorcy!) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
#### Blokująca vs Nieblokująca
|
|
|
|
```
|
|
OPERACJA BLOKUJĄCA:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Wywołanie nie zwraca kontroli do wołającego dopóki operacja │
|
|
│ nie zostanie zakończona (lub warunek spełniony) │
|
|
│ │
|
|
│ Proces: send() │
|
|
│ ├─────────────────────┤ │
|
|
│ │ BLOKADA │ │
|
|
│ │ (czeka na coś) │ │
|
|
│ └─────────────────────┘ │
|
|
│ return │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
OPERACJA NIEBLOKUJĄCA:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Wywołanie zwraca natychmiast, operacja wykonuje się w tle │
|
|
│ │
|
|
│ Proces: isend() → return immediately │
|
|
│ │ │
|
|
│ │ (inne obliczenia) │
|
|
│ │ │
|
|
│ wait() lub test() ← sprawdź czy zakończone │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Kombinacje w MPI
|
|
|
|
| Funkcja MPI | Blokująca? | Synchroniczna? | Opis |
|
|
|-------------|------------|----------------|------|
|
|
| `MPI_Send` | Blokująca | Zależne od impl. | Standard send |
|
|
| `MPI_Ssend` | Blokująca | Synchroniczna | Czeka na recv |
|
|
| `MPI_Bsend` | Blokująca | Asynchroniczna | Buforowana |
|
|
| `MPI_Rsend` | Blokująca | - | Ready (recv musi czekać) |
|
|
| `MPI_Isend` | Nieblokująca | Asynchroniczna | Immediate |
|
|
| `MPI_Recv` | Blokująca | - | Standard recv |
|
|
| `MPI_Irecv` | Nieblokująca | - | Immediate recv |
|
|
|
|
---
|
|
|
|
### 3. Problem zakleszczenia (Deadlock)
|
|
|
|
#### Scenariusz: Algorytm Jacobiego
|
|
|
|
```c
|
|
// DEADLOCK! - oba procesy czekają na siebie nawzajem
|
|
|
|
// Proces 0: // Proces 1:
|
|
MPI_Send(to=1, data); MPI_Send(to=0, data);
|
|
MPI_Recv(from=1, data); MPI_Recv(from=0, data);
|
|
|
|
Przebieg:
|
|
┌──────────────────┬──────────────────┐
|
|
│ PROCES 0 │ PROCES 1 │
|
|
├──────────────────┼──────────────────┤
|
|
│ Send(to=1) │ Send(to=0) │
|
|
│ [BLOKUJE - czeka │ [BLOKUJE - czeka │
|
|
│ aż 1 odbierze] │ aż 0 odbierze] │
|
|
│ ↓ │ ↓ │
|
|
│ DEADLOCK! │ DEADLOCK! │
|
|
│ (nikt nie robi │ (nikt nie robi │
|
|
│ Recv) │ Recv) │
|
|
└──────────────────┴──────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Rozwiązania problemu zakleszczenia
|
|
|
|
#### 4.1 Zmiana kolejności operacji
|
|
|
|
```c
|
|
// Proces 0: // Proces 1:
|
|
MPI_Send(to=1, data); MPI_Recv(from=0, data); // ← zmiana!
|
|
MPI_Recv(from=1, data); MPI_Send(to=0, data);
|
|
|
|
Przebieg:
|
|
┌──────────────────┬──────────────────┐
|
|
│ PROCES 0 │ PROCES 1 │
|
|
├──────────────────┼──────────────────┤
|
|
│ Send(to=1) ──────│──→ Recv(from=0) │
|
|
│ [zakończone] │ [zakończone] │
|
|
│ Recv(from=1) ←───│─── Send(to=0) │
|
|
│ [zakończone] │ [zakończone] │
|
|
└──────────────────┴──────────────────┘
|
|
✓ Brak deadlocka!
|
|
```
|
|
|
|
#### 4.2 Nieblokujące operacje
|
|
|
|
```c
|
|
// Oba procesy:
|
|
MPI_Request req_send, req_recv;
|
|
|
|
MPI_Irecv(from=partner, data_in, &req_recv); // Nieblokujące recv
|
|
MPI_Isend(to=partner, data_out, &req_send); // Nieblokujące send
|
|
MPI_Wait(&req_recv, &status); // Czekaj na recv
|
|
MPI_Wait(&req_send, &status); // Czekaj na send
|
|
|
|
Przebieg:
|
|
┌──────────────────┬──────────────────┐
|
|
│ PROCES 0 │ PROCES 1 │
|
|
├──────────────────┼──────────────────┤
|
|
│ Irecv (posted) │ Irecv (posted) │
|
|
│ Isend (posted) │ Isend (posted) │
|
|
│ ↓ ↓ │ ↓ ↓ │
|
|
│ [operacje w tle] │ [operacje w tle] │
|
|
│ Wait (recv done) │ Wait (recv done) │
|
|
│ Wait (send done) │ Wait (send done) │
|
|
└──────────────────┴──────────────────┘
|
|
✓ Brak deadlocka!
|
|
```
|
|
|
|
#### 4.3 MPI_Sendrecv
|
|
|
|
```c
|
|
// Oba procesy (najczystsze rozwiązanie):
|
|
MPI_Sendrecv(
|
|
send_buf, send_count, type, dest, send_tag,
|
|
recv_buf, recv_count, type, source, recv_tag,
|
|
comm, &status
|
|
);
|
|
|
|
// Wewnętrznie implementuje bezpieczną wymianę
|
|
// Unika deadlocka automatycznie
|
|
```
|
|
|
|
#### 4.4 Buforowane wysyłanie
|
|
|
|
```c
|
|
// Attach buffer
|
|
char buffer[BUFFER_SIZE];
|
|
MPI_Buffer_attach(buffer, BUFFER_SIZE);
|
|
|
|
// Oba procesy:
|
|
MPI_Bsend(to=partner, data); // Kopiuje do bufora i wraca
|
|
MPI_Recv(from=partner, data); // Teraz może odebrać
|
|
|
|
MPI_Buffer_detach(&buffer, &size);
|
|
|
|
// Działa gdy bufor wystarczająco duży
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Porównanie rozwiązań
|
|
|
|
| Rozwiązanie | Zalety | Wady |
|
|
|-------------|--------|------|
|
|
| **Zmiana kolejności** | Proste, brak overhead | Wymaga asymetrii kodu |
|
|
| **Isend/Irecv** | Elastyczne, overlap | Złożoność kodu |
|
|
| **Sendrecv** | Proste, bezpieczne | Mniej elastyczne |
|
|
| **Bsend** | Podobne do standardowego | Wymaga bufora, memory |
|
|
|
|
---
|
|
|
|
### 6. Algorytm Jacobiego - pełny przykład
|
|
|
|
```c
|
|
// Iteracyjne rozwiązanie równania Laplace'a
|
|
// Grid podzielony między procesy
|
|
|
|
for (int iter = 0; iter < MAX_ITER; iter++) {
|
|
// Wymiana granic z sąsiadami
|
|
|
|
// Bezpieczna wymiana z lewym sąsiadem
|
|
if (rank > 0) {
|
|
MPI_Sendrecv(
|
|
&u[1], 1, MPI_DOUBLE, rank-1, 0, // wyślij lewą granicę
|
|
&u[0], 1, MPI_DOUBLE, rank-1, 0, // odbierz od lewego
|
|
MPI_COMM_WORLD, &status
|
|
);
|
|
}
|
|
|
|
// Bezpieczna wymiana z prawym sąsiadem
|
|
if (rank < size-1) {
|
|
MPI_Sendrecv(
|
|
&u[n-2], 1, MPI_DOUBLE, rank+1, 0, // wyślij prawą granicę
|
|
&u[n-1], 1, MPI_DOUBLE, rank+1, 0, // odbierz od prawego
|
|
MPI_COMM_WORLD, &status
|
|
);
|
|
}
|
|
|
|
// Obliczenia Jacobiego
|
|
for (int i = 1; i < n-1; i++) {
|
|
u_new[i] = 0.5 * (u[i-1] + u[i+1]);
|
|
}
|
|
|
|
swap(&u, &u_new);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 7. Wzorce komunikacji
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ RING (pierścień) - każdy z sąsiadami: │
|
|
│ │
|
|
│ ┌───→ P0 ───→ P1 ───→ P2 ───→ P3 ───┐ │
|
|
│ └──────────────────────────────────────┘ │
|
|
│ │
|
|
│ Bezpieczne: Sendrecv w jednym kierunku │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ ALL-TO-ALL - każdy z każdym: │
|
|
│ │
|
|
│ P0 ←→ P1 │
|
|
│ P0 ←→ P2 │
|
|
│ P1 ←→ P2 │
|
|
│ ... │
|
|
│ │
|
|
│ Rozwiązanie: MPI_Alltoall lub ordered pairwise exchange │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 🧠 Mnemoniki
|
|
|
|
### "S-B Matrix":
|
|
| | Sync | Async |
|
|
|---|---|---|
|
|
| **Block** | Ssend | Send/Bsend |
|
|
| **Non-block** | - | Isend |
|
|
|
|
### "I = Immediate = Non-blocking":
|
|
MPI_Isend, MPI_Irecv - nieblokujące (I na początku)
|
|
|
|
### "Sendrecv = Safe exchange":
|
|
Jedna funkcja, zero deadlocków
|
|
|
|
---
|
|
|
|
## ❓ Pytania dodatkowe
|
|
|
|
### Q1: "Czy MPI_Send jest synchroniczne?"
|
|
**Odpowiedź:** Zależy od implementacji i rozmiaru! Małe wiadomości często buforowane (async), duże mogą być sync. MPI_Ssend zawsze sync, MPI_Bsend zawsze async (buforowane).
|
|
|
|
### Q2: "Jak wykryć potencjalny deadlock?"
|
|
**Odpowiedź:** Analiza statyczna grafu zależności send/recv. Narzędzia: MUST, Marmot. Runtime: timeouty, watchdog. Zasada: unikaj cykli w grafie oczekiwań.
|
|
|
|
### Q3: "Co to jest eager vs rendezvous protocol?"
|
|
**Odpowiedź:** Eager: małe msg kopiowane do bufora natychmiast (async). Rendezvous: duże msg - handshake send/recv przed transferem (sync). Threshold zależy od implementacji MPI.
|
|
|
|
---
|
|
|
|
## 🎯 Kluczowe punkty
|
|
|
|
1. **Synchroniczna:** Nadawca czeka na odbiorcę
|
|
2. **Blokująca:** Funkcja nie wraca do zakończenia
|
|
3. **Deadlock:** Cykliczne oczekiwanie (Send-Send)
|
|
4. **Rozwiązania:** Sendrecv, Isend/Irecv, zmiana kolejności
|
|
5. **MPI_I*** = nieblokujące (Immediate)
|
|
|
|
---
|
|
|
|
## 📖 Źródła
|
|
|
|
1. MPI Standard (mpi-forum.org)
|
|
2. Gropp et al. - "Using MPI"
|
|
3. Pacheco - "Parallel Programming with MPI"
|