Blog literacki, portal erotyczny - seks i humor nie z tej ziemi
LEKCJA 21: KILKA PROCESÓW JEDNOCZEŚNIE.
________________________________________________________________
W trakcie tej lekcji dowiesz się, jak to zrobić, by Twój PC mógł
wykonywać kilka rzeczy jednocześnie.
________________________________________________________________
Procesy współbieżne.
Sprzęt, czyli PC ma możliwości zdecydowanie pozwalające na
techniczną realizację pracy wielozadaniowej. Nie ma też żadnych
przeciwskazań, by zamiast koprocesora umożliwić w PC instalację
drugiego (trzeciego) równoległego procesora i uprawiać na PC
poważne programowanie współbieżne. Po co? To proste. Wyobraź
sobie Czytelniku, że masz procesor pracujący z częstotliwością
25 MHz (to 25 MILIONÓW elementarnych operacji na sekundę!).
Nawet, jeśli wziąć pod uwagę, że niektóre operacje (dodawanie,
mnożenie, itp.) wymagają wielu cykli - i tak można w
uproszczeniu przyjąć, że Twój procesor mógłby wykonać od
kilkuset tysięcy do kilku milionów operacji w ciągu sekundy.
Jeśli pracujesz np. z edytorem tekstu i piszesz jakiś tekst -
znacznie ponad 99% czasu Twój procesor czeka KOMPLETNIE
BEZCZYNNIE (!) na naciśnięcie klawisza. Przecież Twój komputer
mogłby w tym samym czasie np. i formatować dyskietkę (dyskietka
też jest powolna), i przeprowadzać kompilację programu, i
drukować dokumenty, i przeprowadzić defragmentację drugiego
dysku logicznego, itp. itd..
Nawet taka pseudowspółbieżność realizowana przez DOS, Windows,
czy sieć jest ofertą dostatecznie atrakcyjną, by warto było
przyjrzeć się mechanizmom PSEUDO-współbieżności w C i C++.
Współbieżność procesów, może być realizowana na poziomie
* sprzętowym (architektura wieloprocesorowa),
* systemowym (np. Unix, OS/2),
* nakładki (np. sieciowej - time sharing, token passing)
* aplikacji (podział czasu procesora pomiędzy różne
funkcje/moduły tego samego pojedynczego programu).
My zajmiemy się tu współbieżnością widzianą z poziomu aplikacji.
Funkcje setjmp() (ang. SET JuMP buffer - ustaw bufor
umożliwiający skok do innego procesu) i longjmp() (ang. LONG
JuMP - długi skok - poza moduł) wchodzą w skład standardu C i w
związku z tym zostały "przeniesine" do wszystkich kompilatorów
C++ (nie tylko Borlanada).
Porozmawiajmy o narzędziach.
Zaczniemy od klasycznego zestawu narzędzi oferowanego przez
Borlanda. Aby zapamiętać stan przerwanego procesu stosowana jest
w C/C++ struktura PSS (ang. Program Status Structure) o nazwie
jmp_buf (JuMP BUFfer - bufor skoku). W przypadku współbieżności
wielu procesów (więcej niż dwa) stosuje się tablicę złożoną ze
struktur typu
struct jmp_buf TablicaBuforow[n];
Struktura służy do przechowywania informacji o stanie procesu
(rejestrach procesora w danym momencie) i jest predefiniowana w
pliku SETJMP.H:
typedef struct
{
unsigned j_sp, j_ss, j_flag, j_cs;
unsigned j_ip, j_bp, j_di, j_es;
unsigned j_si, j_ds;
} jmb_buf[1];
Prototypy funkcji:
int setjmp(jmp_buf bufor);
void longjmp(jmp_buf bufor, int liczba);
W obu przypadkach jmp_buf bufor oznacza ten sam typ bufora
(niekoniecznie ten sam bufor - może ich być wiele), natomiast
int liczba oznacza tzw. return value - wartość zwracaną po
powrocie z danego procesu. Liczba ta może zawierać informację, z
którego procesu nastąpił powrót (lub inną przydatną w
programie), ale nie może być ZEREM. Jeśli funkcja longjmp()
otrzyma argument int liczba == 0 - zwróci do programu wartość 1.
Wartość całkowita zwracana przez funkcję setjmp() przy pierwszym
wywołaniu jest zawsze ZERO a przy następnych wywołaniach (po
powrocie z procesu) jest równa parametrowi "int liczba"
przekazanemu do ostatnio wywołanej funkcji longjmp().
Przyjrzyjmy się temu mechanizmowi w praktyce. Wyobraźmy sobie,
że chcemy realizować współbieżnie dwa procesy - proces1 i
proces2. Proces pierwszy będzie naśladował w uproszczeniu
wymieniony wyżej edytor tekstu - pozwoli na wprowadzanie tekstu,
który będzie powtarzany na ekranie. Proces drugi będzie
przesuwał w dolnej części ekranu swój numerek - cyferkę 2 (tylko
po to, by było widać, że działa). Program główny wywołujący oba
procesy powinien wyglądać tak:
...
void proces1(void);
void proces2(void);
int main(void)
{
clrscr();
proces1();
proces2();
return 0;
}
Ależ tu nie ma żadnej współbieżności! Oczywiście. Aby
zrealizować współbieżność musimy zadeklarować bufor na bieżący
stan rejestrów i zastosować funkcje setjmp():
#include
void proces1(void);
void proces2(void);
jmp_buf bufor1;
int main(void)
{
clrscr();
if(setjmp(bufor1) != 0) proces1(); //Powrót z procesu2 był?
proces2();
return 0;
}
Po wywołaniu funkcji setjmp() zostanie utworzony bufor1, w
którym zostanie zapamiętany stan programu. Funkcja, jak zawsze
przy pierwszym wywołaniu zwróci wartość ZERO, więc warunek
if(setjmp(bufor1) != 0) ...
nie będzie spełniony i proces1() nie zostanie wywołany. Program
pójdzie sobie dalej i uruchomi proces2():
void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".2\b");
delay(5); //UWAGA: delay() tylko dla DOS!
}
longjmp(bufor1, 1); <--- wróć
} ____________ tę jedynkę zwróci setjmp()
}
Proces 2 będzie drukował "biegającą dwójkę" (zwolnioną przez
opóźnienie delay(5); o pięć milisekund), poczym funkcja
longjmp() każe wrócić z procesu do programu głównego w to
miejsce:
int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1(); <--- tu powrót
proces2();
return 0;
}
Zmieni się tylko tyle, że powtórnie wywołana funkcja setjmp()
zwróci tym razem wartość 1, zatem warunek będzie spełniony i
rozpocznie się proces1():
void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}
Proces 1 sprawdzi przy pomocy funkcji kbhit() czy w buforze
klawiatury oczekuje znak (czy coś napisałeś). Jeśli tak -
wydrukuje znak, jeśli nie - zakończy się i program przejdzie do
procesu drugiego. A oto program w całości:
[P075.CPP]
#include
#include
#include
#include
#include
void proces1(void);
void proces2(void);
jmp_buf bufor1, bufor2;
char znak;
int pozycja = 1;
int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1();
proces2();
return 0;
}
void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}
void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".1\b");
delay(5);
}
longjmp(bufor1,1);
}
}
[!!!] UWAGA
________________________________________________________________
Funkcja delay() użyta dla opóżnienia i zwolnienia procesów
będzie funkcjonować tylko w środowisku DOS. Przy uruchamianiu
prykładowego programu pod Windows przy pomocy BCW należy tę
funkcję poprzedzić znakiem komentzrza // .
________________________________________________________________
Wyobrażmy sobie, że mamy trzy procesy. Przykład współbieżności
trzech procesów oparty na tej samej zasadzie zawiera program
poniżej
[P076.CPP]
#include
#include
#include
#include
#include
void proces1(void);
void proces2(void);
void proces3(void);
jmp_buf bufor1, bufor2;
char znak;
int pozycja = 1;
int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1();
if(setjmp(bufor2)) proces2();
proces3();
return 0;
}
void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}
void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".2\b");
delay(5);
}
longjmp(bufor1, 1);
}
}
void proces3(void)
{
for(;;)
{
gotoxy(10,23);
printf("PROCES 3: ");
for(int i = 1; i<40; i++)
{
printf(".3\b");
delay(2);
}
longjmp(bufor2,2);
}
}
Procesy odbywają się z różną prędkością. Kolejność uruchamiania
procesów będzie:
- proces3()
- proces2()
- proces1()
Po uruchomieniu programu zauważysz, że proces pierwszy (pisania)
został spowolniony. Można jednak temu zaradzić przez ustawienie
flag i priorytetów. Jeśli dla przykładu uważamy, że pisanie jest
ważniejsze, możemy wykrywać zdarzenie - naciśnięcie klawisza w
każdym z mniej ważnych procesów i przerywać wtedy procesy mniej
ważne. Wprowadzanie tekstu w przykładzie poniżej nie będzie
spowolnione przez pozostałe procesy.
[P077.CPP]
#include
#include
#include
#include
#include
void proces1(void);
void proces2(void);
void proces3(void);
jmp_buf BuforStanu_1, BuforStanu_2;
char znak;
int pozycja = 1;
int main(void)
{
clrscr();
if(setjmp(BuforStanu_1)) proces1();
if(setjmp(BuforStanu_2)) proces2();
proces3();
return 0;
}
void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}
void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
if(kbhit()) break;
printf(".2\b");
delay(5);
}
longjmp(BuforStanu_1, 1);
}
}
void proces3(void)
{
for(;;)
{
gotoxy(10,23);
printf("PROCES 3: ");
for(int i = 1; i<40; i++)
{
if(kbhit()) break;
printf(".3\b");
delay(2);
}
longjmp(BuforStanu_2,2);
}
}
[!!!]UWAGA
________________________________________________________________
W pierwszych dwu przykładach trzymanie stale wciśniętego
klawisza spowoduje tylko automatyczną repetycję wprowadzanego
znaku. W przykładzie trzecim spowoduje to przerwanie procesów 2
i 3, co będzie wyraźnie widoczne na monitorze (DOS).
Zwróć uwagę, że kbhit() nie zmienia stanu bufora klawiatury.
________________________________________________________________
W bardziej rozbudowanych programach można w oparciu o drugi
parametr funkcji longjmp() zwracany przez funkcję setjmp(buf) po
powrocie z procesu identyfikować - z którego procesu nastąpił
powrót i podejmować stosowną decyzję np. przy pomocy instrukcji
switch:
switch(setjmp(bufor))
{
case 1 : proces2();
case 2 : proces3();
.....
default : proces0();
}
[!!!]UWAGA
________________________________________________________________
* Zmienne sterujące przełączaniem procesów powinny być zmiennymi
globalnymi, bądź statycznymi. Także dane, które nie mogą ulec
nadpisaniu bezpieczniej potraktować jako globalne.
________________________________________________________________
W przypadku wielu procesów celowe jest utworzenie listy, bądź
kolejki procesów. Przydatny do tego celu bywa mechanizm tzw.
"łańcuchowej referencji". W obiektach klasy PozycjaListy należy
umieścić pole danych - strukturę i pointer do następnego
procesu, któremu (zgodnie z ustalonym priorytetem) należy
przekazać sterowanie:
static jmp_buf Bufor[m]; <-- m - ilość procesów
...
class PozycjaListy
{
public:
jmp_buf Bufor[n]; <-- n - Nr procesu
PozycjaListy *nastepna;
}
Wyobrażmy sobie sytuację odrobinę różną od powyższych przykładów
(w której zresztą para setjmp() - longjmp() równie często
występuje.
#include
jmp_buf BuforStanu;
int Nr_Bledu;
int main(void)
{
Nr_Bledu = setjmp(BuforStanu) <-- tu nastąpi powrót
if(Nr_Bledu == 0) <-- za pierwszym razem ZERO
{
/* PRZED powrotem z procesu (ów) */
....
Proces(); <-- Wywołanie procesu
}
else
{
/* PO powrocie z procesu (ów) */
ErrorHandler(); <-- obsługa błędów
}
....
return 0;
}
Taka struktura zapewnia działanie następujące:
- Był powrót z procesu?
NIE: Wywołujemy proces!
TAK: Obsługa błędów, które wystąpiły w trakcie procesu.
Jeśli teraz proces zaprojektujemy tak:
void Proces()
{
int Flaga_Error = 0;
...
/* Jeśli nastąpiły błędy, flaga w trakcie pracy procesu jest
ustawiana na wartość różną do zera */
if(Error) Flaga_Error++;
...
if(Fllaga_Error != 0) longjmp(BuforStanu, Flaga_Error);
...
}
proces przekaże sterowanie do programu w przypadku wystąpienia
błędów (jednocześnie z informacją o ilości/rodzaju błędów).
[Z]
________________________________________________________________
1. Napisz samodzielnie program realizujący 2, 3, 4 procesy
współbieżne. Jeśli chcesz, by jednym z procesów stał się
całkowivie odrębny program - skorzystaj z funkcji grupy
spawn...() umożliwiających w C++ uruchamianie procesów
potomnych.
________________________________________________________________