praca_magisterska/pytania/odpowiedzi/32-komunikacja-synchroniczna-asynchroniczna.md

310 lines
13 KiB
Markdown
Raw Normal View History

2025-12-21 19:58:11 +01:00
# 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"