Documente Academic
Documente Profesional
Documente Cultură
4) Tratarea erorilor
Scopul tratării erorilor este de a evita blocările infinite ale proceselor, care se pot produce în diverse circumstanţe:
Emiterea unui mesaj cu o destinaţie (proces sau poartă) inexistentă. În acest caz primitiva nu este blocantă;
eroarea este tratată prin emiterea unui cod de eroare sau printr-o deviere.
Distrugerea unui proces de la care alte procese aşteaptă un mesaj sau un răspuns: procesele în aşteptare sunt
blocate şi recepţionează un cod de eroare.
Ultima situaţie nu este totdeauna detectabilă. O tehnică uzuală constă în stabilirea unui interval de timp maxim de
aşteptare a unui mesaj şi deblocarea procesului care aşteaptă la expirarea acestui interval (v. 3.4.5).
Vom ilustra prin câteva exemple reprezentative utilizarea acestor mecanisme de comunicare.
--Daca o sa trebuiasca de dat exemple.
Exemplul 3.12. Sistemul de operare Thoth [8]. În acest sistem comunicarea foloseşte desemnarea directă şi sincronizarea prin rendez-vous. Mesajele
sunt de lungime constantă. Sunt utilizate patru primitive:
id:=send(message, id_dest)
emite procesului id_dest un mesaj; blochează emiţătorul până la primirea unui răspuns, transmis în message. Această primitivă
indică identitatea procesului care a transmis răspunsul (sau nil, dacă destinatarul nu există).
id:=receive(message, id_orig)
recepţionează un mesaj; procesul origine poate să nu fie specificat. Valoarea transmisă este identitatea emiţătorului.
reply(message, id_orig, id_dest)
trimite un răspuns destinatarului specificat (care trebuie să-l aştepte); nu este blocantă; fără consecinţe, dacă răspunsul nu era
aşteptat.
forward(message, id_orig, id_dest)
această operaţie non blocantă este utilizată de un proces după recepţionarea unui mesaj trimis de către id_orig, pentru ca să impună
mesajul să ajungă la id_dest, care are acum obligaţia de a răspunde lui id_orig. ◄
Exemplul 3.13. Sistemul de operare Unix [9]. În sistemul Unix comunicarea între procese utilizează tampoane, numite pipes (tuburi), administrate
conform schemei producător-consumator. Mesajele transmise sunt caractere. Un pipe (tub) leagă un emiţător şi un receptor,
conexiunea fiind stabilită dinamic. ◄
Exemplul 3.14. Rendez-vous în limbajul de programare Ada [10, 11]. Limbajul Ada permite definirea proceselor. Forma sintactică a comunicărilor
între procese este apelarea procedurii, însă transmiterea parametrilor şi a rezultatelor are loc conform principiului de transmitere a
mesajelor cu rendez-vous. Recepţionarea poate fi condiţionată (un apel este acceptat doar dacă o condiţie specificată este
satisfăcută) şi există posibilitatea de aşteptare multiplă. ◄
Procedura lansare_transfer_i pregăteşte programul pentru schimbul cerut (construirea programului canalului sau al ADM -
Acces Direct la Memorie, ţinând cont de parametrii schimbului) şi lansează execuţia sa (instrucţiunea SIO – Start Input
Output). Procesele apelante aşteaptă sfârşitul transferului datorită condiţiei sfr_schimb_i. Sosirea unei întreruperi, care
marchează sfârşitul schimbului de tip i provoacă în mod automat executarea următoarei secvenţe:
Programul de intrare-ieşire este realizat prin cooperarea a patru procese programul cărora este prezentat schematic
mai jos (trei procese ale sistemului de operare şi procesul utilizator). Pentru a simplifica expunerea au fost omise
secvenţele de tratare a erorilor şi am admis, că sistemul funcţionează în regim permanent fără limitarea numărului de
linii la imprimare. Programele folosesc trei monitoare de gestionare a perifericelor (tampon1, tampon2 şi tampon_disc)
şi două monitoare de gestionare a perifericelor (impr şi disc), construite în baza modelului perif, prezentat în 3.4.4.1
proces imprimare linie proces scriere_disc proces citire_disc
ciclu ciclu ciclu
tampon2.preluare(l); tampon1.preluare(l); tampon_disc.citire(l);
impr.scriere(l); tampon_disc.scriere(l); tampon2.depozitare(l);
endciclu endciclu endciclu
Imprimarea unei linii este cerută de procedura:
procedura
scriere_linie(l:linie);
tampon1.depozitare(l)
Putem constata, că programele de mai sus sunt mult mai simple decât cele care folosesc direct întreruperile.
Structura modulară, introdusă de monitoare permite separarea totală a gestionării tampoanelor de cea a perifericelor.
Schimbarea capacităţii unui tampon modifică doar monitorul care comandă acest tampon; înlocuirea unui periferic cu
un altul implică rescrierea doar a monitorului, care comandă acest periferic.
57. Sincronizare temporală
Sincronizarea temporală face ca timpul să intervină nu numai ca mijloc de ordonare a evenimentelor, dar şi ca
măsură de durată absolută. Acest mod de sincronizare este utilizat în aplicaţiile de timp real, care conţin interacţiuni cu
organe externe (comanda proceselor industriale, de exemplu). Sincronizarea temporală solicită folosirea unui ceas,
realizat prin intermediul unui oscilator cu quartz, care emite impulsuri la intervale regulate. Aceste impulsuri pot fi
utilizate pentru a declanşa o întrerupere la fiecare impuls sau pentru a decrementa în mod automat conţinutul unui
registru contor, o întrerupere este declanşată atunci când conţinutul acestui registru atinge valoare 0.
Vom utiliza a doua metodă de funcţionare. Unitatea de timp folosită este perioada ceasului.
Pentru rezolvarea problemelor principale de sincronizare temporală poate fi folosită primitiva suspendare(t), care
are ca efect blocarea procesului apelant pentru o durată (fizică) egală cu t unităţi de timp. Prezentăm principiul de
realizare a acestei primitive cu ajutorul unui ceas.*
Pentru rezolvarea acestei probleme vom fi nevoiţi să întreţinem:
valoarea absolută a timpului (ora absolută), care măsoară la orice moment timpul trecut de la o instanţă
iniţială,
un registru, adică o listă a proceselor care aşteaptă deblocarea, ordonat conform timpului absolut de deblocare.
Toate procesele, care apelează primitiva suspendare(t) sunt inserate în registru în poziţia, care corespunde orei sale
absolute de deblocare.
Numim ora_de_bază ora absolută a ultimei înnoiri a ceasului, adică a ultimei iniţializări a contorului; fie t_aşt
ultima valoare încărcată. Ora absolută exactă este dată la fiecare moment de timp de relaţia
ora_exactă = ora_de_bază + t_aşt - contor
(t_aşt - contor este timpul care s-a scurs după ultima înnoire a contorului). De la o întrerupere de ceas (la trecerea
contorului prin 0), după ultima înnoire s-a scurs un timp egal cu t_aşt; ora_de_bază poate, deci, fi iniţializată conform
relaţiei
ora_de_bază := ora_de_bază + t_aşt
Variabila ora_de_bază, odată iniţializată, va fi corect întreţinută cu condiţia ca registrul să nu fie nicicând vid; în
caz general această condiţie va fi asigurată introducând un proces, numit paznic:
procesul paznic
ciclu
suspendare(tmax
) endciclu
în care tmax este un interval foarte mare de timp, paznicul rămânând pentru toată perioada de lucru în coada registrului.
Mecanismele descrise sunt realizate într-un monitor numit ceas, care are două intrări: procedura suspendare (apelată
prin ceas.suspendare(t)) şi procedura tratare_întrerupere, pentru tratarea întreruperii de ceas (trecerea contorului prin
zero). Registrul este realizat cu ajutorul unui fir de aşteptare, care conţine descriptorii proceselor. Un descriptor este
format din numele procesului şi timpul absolut de deblocare; firul de aşteptare este ordonat în ordinea creşterii timpului
deblocării.
Presupunem, că întreruperea de ceas activează un proces, unica activitate a căruia constă în apelarea procedurii
ceas.tratare_întrerupere.
ceas: monitor;
type descriptor: struct
i :
proces; ora_debl :
integer end;
var contor, t_aşt;
ora_de_bază, ora_exactă: integer
deblocare : array[proces] of condiţie;
*
Problema ar fi uşor de rezolvat, dacă fiecare proces ar dispune de un ceas propriu: ar fi suficient să se introducă în contor valoarea t şi să se aştepte
întreruperea la trecerea prin zero. Realizarea primitivei suspendare cu ajutorul unui ceas unic comun presupune ataşarea fiecărui proces a unui ceas
virtual.
<declaraţiile firului de aşteptare şi procedurilor sale de acces>
procedura suspendare(durata:integer);
var proc: descriptor;
begin
ora_exactă:=ora_de_bază+t_aşt-contor;
proc.ora_deblocării:=ora_exactă+durata -- ora absolută a deblocării
proc.i := p; -- p este procesul apelant
intrare(proc,fir) -- ordonare de ora_deblocării
if proc=primul(fir) then
t_aşt:=contor:=durata;
ora_de_bază:=ora_exactă;
endif;
deblocare[p].aşteptare
end;
procedura tratare_întrerupere; -- contor în zero
var proc:
descriptor;
delta_t:integer;
begin
ieşire(proc,fir); -- primul din firul de aşteptare
ora_de_bază := ora_exactă+t_aşt;
if vid(fir) then
delta_t := tmax
else
delta_t := primul(fir).ora_deblocării – ora_de_bază;
endif;
t_aşt := contor := delta_t; -- deblocarea viitoare
deblocare[proc.i].semnalizare
end;
begin
ora_de_bază :=
<ora_iniţială>; contor :=
t_aşt := tmax;
intrare(paznic,fir)
end
end
ceas
Metoda de mai sus (întreţinerea unui registru şi a orei absolute) este utilizată pentru realizarea programelor de
simulare discretă. Un atare program realizează un model în care diferite procese, executate în regim pseudo-paralel,
reprezintă activităţi care se derulează în paralelism real. Timpul utilizat este un timp simulat, adică o variabilă, care
reprezintă trecerea timpului fizic, respectând proporţionalitatea intervalelor timpului simulat cu intervalele
corespunzătoare ale timpului fizic.
procesul 1 procesul 2
copie
date date
stivă stivă
procesul fiu
procesul
părinte Fig.4.4. Crearea proceselor cu ajutorul instrucţiunii fork
if (fork() == 0) if (fork() == 0)
codul procesului fiu codul procesului fiu
else else
codul procesului părinte codul procesului părinte
66. Semafoarele
Un obiect-semafor este în ultimă instanţă un mutex cu contor. Acest obiect permite să fie „ocupat” de un număr anume
de fire, după care „ocuparea” va fi posibilă numai dacă unul din fire va „elibera” semaforul. Semafoarele sunt utilizate pentru
a limita numărul de fire, care lucrează simultan cu resursa. La iniţializare se specifică numărul maxim de fire, la fiecare
„ocupare” valoarea contorului semaforului scade.
#include <windows.h>
#include <iostream.h>
void main()
{
DWORD res;
// eliberăm obiectul
cout<<"Acum eliberăm obiectul\n"; cout.flush();
ReleaseMutex(mutex);
}
// închidem descriptorul
CloseHandle(mutex);
}
Pentru a controla modul de funcţionare a mecanismului de excludere mutuală se vor lansa două instanţe ale acestei
aplicaţii. Prima instanţă va ocupa obiectul imediat şi-l va elibera doar peste 10 secunde. Numai după aceasta instanţa
a doua va reuşi să ocupe obiectul. În acest exemplu obiectul de sincronizare este folosit pentru sincronizarea
proceselor, din care cauză în mod obligatoriu trebuie să aibă nume.
// Firul #2
void Proc2(IObject *pNewObject)
{
if (m_pObject)
delete m_pObject;
m_pObject = pNewObject;
}
În acest exemplu există pericolul potenţial de apelare m_pObject->SomeMethod() după ce obiectul a fost distrus cu
ajutorul delete m_pObject, deoarece în sistemele de operare cu multitasking controlat execuţia oricărui fir poate fi
întreruptă în cel mai neconvenabil (pentru firul dat) moment şi să înceapă execuţia unui alt fir. Pentru exemplul nostru
momentul nedorit este atunci când firul #1 a testat deja m_pObject, dar nu a reuşit să apeleze SomeMethod().
Execuţia firului #1 a fost întreruptă şi a început execuţia firului #2. Iar firul #2 reuşise deja să apeleze destructorul
obiectului. Ce se va întâmpla atunci când firului #1 i se va acorda din nou timp de procesor şi va fi apelat
SomeMethod() al unui obiect deja inexistent? Greu de presupus.
Aici ne vin în ajutor secţiunile critice. Să modificăm exemplul de mai sus.
// Firul #1
void Proc1()
{
::EnterCriticalSection(&m_lockObject);
if (m_pObject)
m_pObject->SomeMethod();
::LeaveCriticalSection(&m_lockObject);
}
// Firul #2
void Proc2(IObject *pNewObject)
{
::EnterCriticalSection(&m_lockObject);
if (m_pObject)
delete m_pObject;
m_pObject = pNewObject;
::LeaveCriticalSection(&m_lockObject);
}
Câmpul LockCount este incrementat cu o unitate la fiecare apelare ::EnterCriticalSection() şi decrementat cu unu
la fiecare apel ::LeaveCriticalSection(). Acesta este primul (adesea şi unicul) control pentru testarea secţiunii critice. Dacă
după incrementare în acest câmp avem 0, aceasta înseamnă că până la acest moment nu au avut loc apeluri impare
::EnterCriticalSection() din alte fire.
În acest caz datele “păzite” de această secţiune critică pot fi utilizate în regim monopol. Adică, dacă secţiunea critică
este folosită intensiv de un singur fir, atunci ::EnterCriticalSection() se transformă practic în ++LockCount, iar
::LeaveCriticalSection() în--LockCount.
Aceasta înseamnă, că folosirea a mai multor mii de secţiuni critice într-un singur proces nu va conduce la creşterea
substanţială a utilizării nici a resurselor de sistem, nici a timpului de procesor. Ca şi concluzie: nu se va face economie pe
contul secţiunilor critice. Mult totuna nu vom putea economisi.
În câmpul RecursionCount este păstrat numărul de apeluri repetate ::EnterCriticalSection() din unul şi acelaşi fir.
Dacă se va apela ::EnterCriticalSection() din unul şi acelaşi fir de mai multe ori, toate apelurile vor avea succes. Adică
următorul cod nu se va opri pentru totdeauna în cel de-al doilea apel ::EnterCriticalSection(), ci va merge până la capăt.
// Firul #1
void Proc1()
{
::EnterCriticalSection(&m_lock);
// ...
Proc2()
// ...
::LeaveCriticalSection(&m_lock);
}
// Încă Firul #1
void Proc2()
{
::EnterCriticalSection(&m_lock);
// ...
::LeaveCriticalSection(&m_lock);
}
Într-adevăr, secţiunile critice sunt destinate să protejeze datele la accesarea din câteva fire. Utilizarea multiplă a uneia
şi aceeaşi secţiuni critice de un singur fir nu va genera eroare, ceea ce este normal. Trebuie doar să avem grijă ca numărul de
apeluri ::EnterCriticalSection() şi ::LeaveCriticalSection() să coincidă şi totul va fi în regulă.
Câmpul OwningThread conţine 0 în cazul secţiunilor critice libere sau identificatorul unic al firului-posesor. Acest
câmp este testat, dacă la apelul ::EnterCriticalSection() câmpul LockCount, după incrementarea cu o unitate, a devenit mai
mare ca 0. Dacă OwningThread coiincide cu identificatorul unic al firului curent, atunci valoarea lui RecursionCount
creşte cu o unitate şi ::EnterCriticalSection() este returnat imediat.
În caz contrar ::EnterCriticalSection() va aştepta până firul, care posedă secţiunea critică, va apela
::LeaveCriticalSection() de un număr necesar de ori. Câmpul LockSemaphore este folosit, dacă este necesar să se aştepte
până secţiunea critică este eliberată.
Dacă LockCount este mai mare ca 0 şi OwningThread nu coiincide cu identificatorul unic al firului curent, atunci
firul blocat crează un obiect al nucleului – eveniment – şi apelează ::WaitForSingleObject(LockSemaphore). Firul
posesor, după decrementarea câmpului RecursionCount, testează acest câmp şi dacă valoarea lui este 0, iar LockCount este
mai mare ca 0, constată că există minimum un fir în aşteptarea momentului când LockSemaphore se va afla în starea
“sosit”. Pentru aceasta firul-posesor apelează ::SetEvent() şi un fir oarecare (doar unul) dintre cele blocate este deblocat şi
are acces la datele critice.
Windows NT/2000 generează o excepţie, dacă încercarea de a crea un eveniment a eşuat. Aceasta este just atât pentru
funcţiile ::Enter/LeaveCriticalSection(), cât şi pentru ::InitializeCriticalSectionAndSpinCount() cu bitul cel mai
semnificativ al parametrului SpinCount setat. În cazul sistemului de operare WindowsXP creatorii nucleului au procedat un
pic altfel.
Aici, funcţiile ::Enter/LeaveCriticalSection() în loc să genereze o excepţie, dacă nu pot crea un eveniment propriu,
vor folosi un obiect global, unic pentru toţi, creat în avans. În aşa mod, în caz de deficienţă catastrofală a resurselor de sistem,
programul sub comanda lui WindowsXP “şchiopătează” un timp oarecare mai departe. Într-adevăr, este foarte complicat să
scrii programe, care ar fi în stare să continue lucrul după ce
::EnterCriticalSection() a generat o excepţie. De obicei, chiar dacă programatorul a prezis o astfel de situaţie, nu
se ajunge mai departe de generarea unui mesaj de eroare şi stoparea forţată a execuţiei programului. Drept rezultat,
WindowsXP ignorează bitul cel mai semnificativ al câmpului LockCount.
În sfârşit, câmpul LockCount. Acest câmp este folosit doar în sistemele multiprocesorale. În sistemele
monoprocesorale, dacă secţiunea critică este ocupată de un fir oarecare, putem doar comuta comanda la această secţiune şi
trece în aşteptarea evenimentului nostru.
În sistemele multiprocesorale există o alternativă: să executăm de câteva ori un ciclu vid, testând de fiecare dată dacă
secţiunea critică nu a fost eliberată (aşteptare activă). Dacă după un număr de SpinCount ori secţiunea critică nu a fost
eliberată, trecem în starea blocat. Această modalitate este mult mai eficientă decât comutarea la planificatorul nucleului şi
înapoi.
În WindowsNT/2000 bitul cel mai semnificativ al acestui câmp indică dacă obiectul nucleului, variabila handle a căruia
se află în câmpul LockSemaphore, trebuie creat anticipat.
Dacă pentru aceasta nu dispunem de resurse de sistem suficiente, sistemul generează o excepţie şi programul îşi poate
„micşora” posibilităţile funcţionale sau chiar termina execuţia.
73. Funcţii API pentru secţiunile critice
Descriem mai jos câteva funcţii din API pentru lucrul cu secţiunile critice.
Mai întâi funcţiile BOOL InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) şi BOOL
InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount).
Completează câmpurile structurii lpCriticalSection adresate. După apelare secţiunea critică este gata de lucru. Iată
pseudocodul funcţiei RtlInitializeCriticalSection din ntdll.dll
RtlpWaitForCriticalSection(pcs);
}
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
}
return TRUE;
}
class CScopeLock
{
LPCRITICAL_SECTION m_pCS;
public:
CScopeLock(LPCRITICAL_SECTION pCS) : m_pCS(pCS) { Lock(); }
CScopeLock(CLock& lock) : m_pCS(&lock.m_CS) { Lock(); }
~CScopeLock() { Unlock(); }
void Lock() { ::EnterCriticalSection(m_pCS); }
void Unlock() { ::LeaveCriticalSection(m_pCS);
}
};
Clasele CLock şi CAutoLock sunt utilizate, de obicei, pentru sincronizarea accesării variabilelor clasei, iar
CScopeLock este destinat, în special, pentru a fi utilizată în proceduri. Compilatorul singur va avea grijă să apeleze
::LeaveCriticalSection() prin intermediul destructorului. Urmează un exemplu de folosire a CScopeLock.
CAutoLock m_lockObject;
CObject *m_pObject;
void Proc1()
{
CScopeLock lock(m_ lockObject); // apelarea lock.Lock();
if (!m_pObject)
return; // apelarea lock.Unlock();
m_pObject->SomeMethod();
// apelarea lock.Unlock();
}
75. Depanarea secţiunilor critice
Depanarea secţiunilor critice este o ocupaţie foarte interesantă, dar şi dificilă. Poţi căuta ore şi chiar zile în şir cauza
apariţiei unei probleme.
Erorile, legate de secţiunile critice sunt de două tipuri: de realizare şi de arhitectură. Erorile de realizare pot fi depistate
relativ uşor şi, de regulă, sunt generate de utilizarea incorectă (lipsa perechii) a apelurilor
::EnterCriticalSection() şi ::LeaveCriticalSection(). Urmează un fragment de cod în care este omis apelul
::EnterCriticalSection().
// În procedură se presupune, că m_lockObject.Lock(); a fost deja apelat
void Pool()
{
for (int i = 0; i < m_vectSinks.size(); i++)
{
m_lockObject.Unlock();
m_vectSinks[i]->DoSomething();
m_lockObject.Lock();
}
}
Apelul ::LeaveCriticalSection() fără ::EnterCriticalSection() va conduce la faptul că chiar primul apel
::EnterCriticalSection() va stopa execuţia firului pentru totdeauna.
În fragmentul de cod de mai jos lipseşte apelul ::LeaveCriticalSection():
void Proc()
{
m_lockObject.Lock();
if (!m_pObject)
return;
// ...
m_lockObject.Unlock();
}
În acest exemplu are sens să fie utilizată o clasă de tipul CSopeLock. Se mai poate întâmpla ca
::EnterCriticalSection() să fie apelată fără iniţializarea secţiunii critice cu ajutorul ::InitializeCriticalSection() (de
exemplu, în proiectele scrise cu ajutorul lui ATL). În versiunea debug totul poate lucra foarte bine, iar în versiunea
release „moare”.
Aceasta are loc din cauza aşa-zisului CRT (_ATL_MIN_CRT) „minimal”, care nu apelează constructorii obiectelor
statice (Q166480, Q165076). În versiunea ATL 7.0 această problemă a fost rezolvată. Pot să apară probleme, dacă atunci
când este folosită o clasă de tipul CScopeLock a fost omis identificatorul variabilei, de exemplu, CScopeLock (m_lock).
Compilatorul apelează constructorul CScopeLock şi imediat distruge acest obiect fără nume (în conformitate cu
standardul!). Adică, imediat după apelarea metodei Lock() are loc apelarea metodei Unlock() şi sincronizarea nu se
produce.
Dintre erorile de arhitectură cea mai frecventă este îmbrăţişarea fatală (deadlock, v.5.1.3.3), când două fire
încearcă să acceseze două şi mai multe secţiuni critice. Prezentăm un exemplu pentru două fire.
void Proc1()
// Firul #1
{
::EnterCriticalSection(&m_lock1);
// ...
::EnterCriticalSection(&m_lock2);
// ...
::LeaveCriticalSection(&m_lock2);
// ...
::LeaveCriticalSection(&m_lock1);
}
// Firul #2
void Proc2()
{
::EnterCriticalSection(&m_lock2);
// ...
::EnterCriticalSection(&m_lock1);
// ...
::LeaveCriticalSection(&m_lock1);
// ...
::LeaveCriticalSection(&m_lock2);
}
Pot să apară probleme şi în cazul copierii unor secţiuni critice. Este greu de presupus că codul de mai jos a fost scris
de un programator sănătos:
CRITICAL_SECTION sec1;
CRITICAL_SECTION sec2;
// ...
sec1 = sec2;
Din atribuirea de mai sus este dificil să obţii foloase. Dar fragmentul următor poate fi adesea întâlnit:
struct SData
{
CLock m_lock;
DWORD m_dwSmth;
} m_data;
// Firul #2
void IObject2::SomeMethod()
{
// Apelăm metoda obiectului #1 din firul obiectului #2
m_pObject1->Proc2();
}
// Firul #2
void IObject1::Proc2()
{
// Încercăm să intrăm în secţiunea critică a obiectului #1
m_lockObject.Lock();
// Aici nu vom ajunge niciodată
m_lockObject.Unlock();
}
Dacă în acest exemplu nu ar fi avut loc comutarea firelor, toate apelurile ar fi avut loc în firul obiectului #1 şi nu am
fi avut probleme. Exemple de acest gen stau la baza tehnologiei compartimentului COM (apartments).
Nu sunt recomandate apelurile obiectelor, dacă au avut loc intrări în secţiunile critice. Primul exemplu din acest
subparagraf va fi rescris astfel:
// Firul #1
void Proc1()
{
m_lockObject.Lock();
CComPtr<IObject> pObject(m_pObject); // apelarea pObject->AddRef();
m_lockObject.Unlock();
if (pObject)
pObject->SomeMethod();
}
// Firul #2
void Proc2(IObject *pNewObject)
{
m_lockObject.Lock();
m_pObject = pNewobject;
m_lockObject.Unlock();
}
Accesul la obiect a rămas ca şi mai înainte sincronizat, dar apelul SomeMethod() are loc în afara secţiunii critice.
Situaţia a fost aproape rezolvată. Mai există o problemă mică. Să cercetăm mai atent Proc2():
Concluzii:
• secţiunile critice sunt executate relativ repede şi nu cer multe resurse de sistem;
• pentru sincronizarea accesării a mai multor variabile independente este mai bine să fie utilizate
câteva secţiuni critice (nu una pentru toate variabilele);
• codul unei secţiuni critice va fi redus la minimum;
• nu este recomandat să fie apelate metode ale unor obiecte “străine” dintr-o secţiune critică.