Initonce - Blogul Raymond Chen (tradus)
Ca de scriere de cod, fără încuietori poate începe o durere de cap. tu, face, probabil, un sens să transfere această responsabilitate unor alte persoane să aibă o durere de cap. Și acești oameni sunt cei de la echipa rabotki pentru Windows Kernel, care a scris o mulțime de componente software gata de utilizare, nu folosiți funcția de blocare, care acum nu trebuie să vă dezvoltați. Printre ei, de exemplu, are un set de funcții pentru lucrul cu liste de fire non-blocare. Dar astăzi ne vom uita la funcția de inițializare o singură dată.
De fapt, în cele mai simple, funcțiile de bloc de initializare o singură dată este folosit, dar acestea sunt puse în aplicare pe o blocare reverificat șablon care vă permite să nu trebuie să vă faceți griji cu privire la aceste detalii. Funcția InitOnceExecuteOnce utilizare algoritm este destul de simplu. Aici sunt versiunea cea mai primitivă a utilizării sale:
BOOL CALLBACK ThisRunsAtMostOnce (
PINIT_ONCE initOnce,
PVOID Parametru,
PVOID * Context)
calculate_an_integer (SomeGlobalInteger);
return true;
>
anula InitializeThatGlobalInteger ()
static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
InitOnceExecuteOnce (initOnce,
ThisRunsAtMostOnce,
nullptr, nullptr);
>
În acest mod foarte simplu, va trece o structură INIT_ONCE funcție InitOnceExecuteOnce (care funcția scrie starea sa) și o referință la o funcție de apel invers. În cazul în care pentru o funcție anumită structură InitOnceExecuteOnce INIT_ONCE pentru prima dată, acesta solicită funcția de apel invers. Funcția de apel invers poate face orice dorește, dar cel mai probabil, se va produce o anumită inițializare care urmează să fie realizată o singură dată. Dacă funcția InitOnceExecuteOnce pentru aceeași structură INIT_ONCE va fi numit de un alt fir, executarea acestui flux va fi suspendat până la primul fir de executie a terminat executia de cod de inițializare.
Putem face acest exemplu un pic mai interesant, presupunând că funcționarea de calcul a unui număr întreg poate eșua.
BOOL CALLBACK ThisSucceedsAtMostOnce (
PINIT_ONCE initOnce,
PVOID Parametru,
PVOID * Context)
reveni Succeeded (calculate_an_integer (SomeGlobalInteger));
>
BOOL TryToInitializeThatGlobalInteger ()
static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
reveni InitOnceExecuteOnce (initOnce,
ThisSucceedsAtMostOnce,
nullptr, nullptr);
>
În cazul în care funcția de inițializare returnează FALSE, inițializarea va fi considerat fără succes, iar data viitoare cineva va apela funcția InitOnceExecuteOnce, va încerca să inițializa din nou.
Chiar și un pic mai interesant funcții opțiune InitOnceExecuteOnce utilizare ia în considerare contextul de setare. Băieții de la echipa de dezvoltare de nucleu Windows a observat că structura INIT_ONCE „inițializat“ de stat conține mai multe biți neutilizate, iar acestea sunt oferite pentru a le folosi pentru propriile lor nevoi. Este destul de convenabil în cazul în care ceea ce inițializa, un pointer la un obiect C ++, deoarece înseamnă că acum trebuie să aibă grijă doar despre un singur lucru în loc de doi.
BOOL CALLBACK AllocateAndInitializeTheThing (
PINIT_ONCE initOnce,
PVOID Parametru,
PVOID * Context)
* Context = new (nothrow) Lucru ();
întoarce * Context = nullptr !;
>
Lucru * GetSingletonThing (int arg1, int arg2)
static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
void * Rezultatul;
în cazul în care (InitOnceExecuteOnce (initOnce,
AllocateAndInitializeTheThing,
nullptr, Rezultat))
reveni static_cast
>
nullptr reveni;
>
Ultimul parametru Funcția InitOnceExecuteOnce ia „magic“ dimensiune a datelor este aproape identic cu indicele, care funcția va aminti de tine. Apoi, funcția de apel invers trece de „magice“ date înapoi prin parametrul context, InitOnceExecuteOnce și le întoarce la tine, ca un parametru Rezultat.
La fel ca în cazul precedent, în cazul în care două fire determina funcția InitOnceExecuteOnce simultan folosind structura INIT_ONCE neinițializate, una dintre ele funcții vyzyvet de inițializare, și un alt fir este suspendat.
Până în acest moment am considerat șabloanele de inițializare sincrone. Pentru munca lor, ei folosesc un sistem de blocare: dacă apelați funcția InitOnceExecuteOnce într-un moment când inițializarea este structura INIT_ONCE, apelul va aștepta finalizarea încercarea de inițializare curent (indiferent dacă acesta va avea succes sau va eșua).
Mult mai interesant pentru un model asincron. Aici este un exemplu de un astfel de șablon aplicat la problema noastră cu clasa SingletonManager:
SingletonManager (const SINGLETONINFO * RGSI, UINT csi)
. m_rgsi (RGSI), m_csi (csi),
m_rgio (nou INITONCE [csi]) pentru (UINT IIO = 0; IIO
>
.
// O matrice care descrie obiectele create
// obiectele din matrice sunt aranjate paralel cu elementele de matrice m_rgsi
INIT_ONCE * m_rgio;
>;
ITEMCONTROLLER * SingletonManager :: Căutare (DWORD dwId)
. toate în același mod ca și în versiunea anterioară, până la un punct,
în cazul în care începe punerea în aplicare «Singleton-constructor“ șablon
void * pv = NULL;
BOOL fPending;
if (! InitOnceBeginInitialize (m_rgio [i], INIT_ONCE_ASYNC,
fPending, pv)) întoarcere NULL;
if (fPending) * ITEMCONTROLLER pic = m_rgsi [i] .pfnCreateController ();
DWORD dwResult = pic. 0. INIT_ONCE_INIT_FAILED;
if (InitOnceComplete (m_rgio [i],
INIT_ONCE_ASYNC | dwResult, pic)) pv = pic;
> Else // a pierdut cursa - acum distruge inutile și a obține o copie a rezultatului câștigător
șterge pic;
InitOnceBeginInitialize (m_rgio [i], INIT_ONCE_CHECK_ONLY,
XfPending, pv);
>
>
reveni static_cast
>
Astfel, modelul de inițializare asincronă constă din următoarele etape:
- Apelați funcția InitOnceBeginInitialize asincronă.
- Dacă se returnează fPending == FALSE, atunci inițializarea a fost deja realizată și puteți utiliza rezultatul trecut ca ultimul parametru.
- În caz contrar, inițializarea nu este încă realizată (sau nu au fost încă finalizate). Urmează inițializarea, dar rețineți că, deoarece acest algoritm fără a utiliza încuietori, puteți găsi că această inițializare se execută în prezent o serie de alte fluxuri, prin urmare, trebuie să fie foarte atent cu operațiunile de pe obiectele stocate de stat la nivel mondial. Acest model funcționează cel mai bine atunci când inițializarea este implementat sub forma crearea unui obiect nou (deoarece, în acest caz, în cazul în care mai multe fire executa inițializarea, fiecare dintre ele va crea o entitate independentă separată).
- Apel InitOnceComplete cu rezultatele initializare dumneavoastră.
- În cazul în care InitOnceComplete funcția reușește, atunci ați câștigat cursa și această procedură de inițializare este finalizată.
- Dacă funcția InitOnceComplete de execuție eșuează, atunci va pierde cursa de inițializare și trebuie să anuleze rezultatele initializare dvs. de succes. De asemenea, în acest caz, trebuie să apelați InitOnceBeginInitialize ultima dată, în scopul de a obține rezultatul initializare efectuate într-un curent, care a câștigat cursa.
În ciuda faptului că descrierea algoritmului este destul de vastă, din punct de vedere conceptual, este destul de simplu. Cel puțin acum este scris sub formă de instrucțiuni pas cu pas.
Exercitarea. Ce s-ar întâmpla dacă în loc să conteste valoarea funcției InitOnceComplete INIT_ONCE_INIT_FAILED întoarce pur și simplu de control fără să se termine inițializarea o singură dată?
Exercitarea. Ce s-ar întâmpla dacă două fire să încerce să execute de asigurare a accesului și fir asincron că mai întâi ajunge în etapa finală a inițializarea eșuează?
Exercitarea. Combinând rezultatele celor două exerciții anterioare și imaginați-vă ce se va întâmpla în cele din urmă.