Initonce - блог Реймънд Чен (в превод)

Както на писане код без брави може да започне главоболие. ви, то вероятно има смисъл да се премине тази отговорност на някои други хора да имат главоболие. И тези хора са момчетата от rabotki на отбора Windows Kernel, който е написал много готови софтуерни компоненти, които не използват за заключване, които сега не е нужно да ти се развива. Сред тях, например, има набор от функции за работа със списъци без блокиране на конци. Но днес ние ще разгледаме функция инициализация еднократно.

В действителност, в най-простите функции на еднократна инициализация блок се използва, но те се прилагат на шаблон уверили заключване, която ви позволява да не се налага да се притеснявате за тези подробности. Функцията за ползване алгоритъм InitOnceExecuteOnce е съвсем проста. Тук са най-примитивен вариант на неговата употреба:

BOOL обратно повикване ThisRunsAtMostOnce (
PINIT_ONCE initOnce,
PVOID параметър,
PVOID * Context)
calculate_an_integer (SomeGlobalInteger);
върне TRUE;
>

анулира InitializeThatGlobalInteger ()
статичен INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
InitOnceExecuteOnce (initOnce,
ThisRunsAtMostOnce,
nullptr, nullptr);
>

В този много прост начин, вие ще премине функция InitOnceExecuteOnce структура INIT_ONCE (които функционират пише си състояние) и позоваване на функция за обратно повикване. Ако за дадена функция InitOnceExecuteOnce INIT_ONCE структура за първи път, когато той извика функцията обаждане. Функцията за обратно повикване може да направи всичко, което е угодно, но най-вероятно, това ще доведе до някои инициализация да се извършва веднъж. Ако InitOnceExecuteOnce функция за същия INIT_ONCE структура ще се нарича по друг конец, изпълнението на този поток ще бъде спрян, докато първата нишка е приключил своето изпълнение инициализация код.

Ние можем да направим това малко по-интересен пример, като се предполага, че работата на изчисление на цяло число може да се провали.

BOOL обратно повикване ThisSucceedsAtMostOnce (
PINIT_ONCE initOnce,
PVOID параметър,
PVOID * Context)
върне УСПЯВАМЕ (calculate_an_integer (SomeGlobalInteger));
>

BOOL TryToInitializeThatGlobalInteger ()
статичен INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
върнете InitOnceExecuteOnce (initOnce,
ThisSucceedsAtMostOnce,
nullptr, nullptr);
>

Ако вашият инициализация функция връща FALSE, инициализация ще се счита за успешно, а на следващия път, когато някой ще се обади функция InitOnceExecuteOnce, тя ще се опита да се инициализира отново.
Дори и малко по-интересно приложение опция InitOnceExecuteOnce функции отчита контекста на настройка. Момчетата от екипа по разработването на ядрото на Windows забелязали, че INIT_ONCE структура "инициализира" състояние съдържа много неизползвани бита, и те ви се предлагат, за да ги използва за собствените си нужди. Това е доста удобно в случаите, когато това, което се инициализира, указател към обект C ++, защото това означава, че сега трябва да се грижи само за едно нещо, а не две.

BOOL обратно повикване AllocateAndInitializeTheThing (
PINIT_ONCE initOnce,
PVOID параметър,
PVOID * Context)
* Контекст = нов (nothrow) Нещо ();
върне * контекст = nullptr !;
>

Нещо * GetSingletonThing (междинно ARG1, Int ARG2)
статичен INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
нищожен * Резултат;
ако (InitOnceExecuteOnce (initOnce,
AllocateAndInitializeTheThing,
nullptr, Резултат))
върнете static_cast(Резултати);
>
върнете nullptr;
>

Последната функция на параметъра InitOnceExecuteOnce отнема "магия" Размер на данни е почти идентичен с този индекс, чиято функция ще запомни за вас. След това си функция за обратно извикване преминава на "магически" миналите данни чрез параметъра контекст, InitOnceExecuteOnce и ги връща към вас като параметър резултат.
Както и в предишния случай, ако две нишки причиняват InitOnceExecuteOnce функция едновременно използване неинициализирана INIT_ONCE структура, един от тях vyzyvet инициализация функция, и друга нишка се суспендира.

До този момент ние разгледахме синхронни инициализация шаблоните. За работата си те използват за заключване: ако ти се обадя функция InitOnceExecuteOnce в момент, когато инициализация е INIT_ONCE структура, обаждането ще чака приключването на опита за текущата инициализация (независимо от това дали тя ще бъде успешна или ще се провали).

Много по-интересно да асинхронен модел. Ето един пример за такъв шаблон прилага към нашия проблем с SingletonManager клас:

SingletonManager (конст * SINGLETONINFO rgsi, UINT CSI)
. m_rgsi (rgsi), m_csi (CSI),
m_rgio (нов INITONCE [CSI]) за (UINT Iio = 0; Iio >
>
.
// масив, който описва създадени обекти
// обекти в масива са разположени успоредно на елементи масив m_rgsi
INIT_ONCE * m_rgio;
>;

ITEMCONTROLLER * SingletonManager :: Търсене (DWORD dwId)
. всичко по същия начин, както в предишната версия, до известна степен,
когато изпълнението започва «Сингълтън-конструктор" Шаблон

невалидни * PV = NULL;
BOOL fPending;
ако (! InitOnceBeginInitialize (m_rgio [Ь] INIT_ONCE_ASYNC,
fPending, PV)) връщане NULL;

ако (fPending) ITEMCONTROLLER * снимка = m_rgsi [Ь] .pfnCreateController ();
DWORD dwResult = снимка. 0. INIT_ONCE_INIT_FAILED;
ако (InitOnceComplete (m_rgio [Ь]
INIT_ONCE_ASYNC | dwResult, снимка)) PV = снимка;
> Иначе // загубил състезанието - сега унищожи ненужни и да получите копие от резултата на победителя
изтриване на снимка;
InitOnceBeginInitialize (m_rgio [Ь] INIT_ONCE_CHECK_ONLY,
XfPending, PV);
>
>
върнете static_cast(Pv);
>

По този начин, моделът за асинхронно инициализация се състои от следните етапи:

  • Обадете функция InitOnceBeginInitialize асинхронно.
  • Ако тя се връща fPending == FALSE, тогава инициализация вече е извършена и можете да използвате резултата премина като последен параметър.
  • В противен случай, инициализация все още не се извършва (или все още не е завършена). Следвайте своя инициализация, но имайте предвид, че тъй като този алгоритъм, без да използвате ключалки, може да откриете, че тази инициализация се изпълнява в момента няколко други потоци, следователно, трябва да сте много внимателни с операциите по предмети, съхранявани глобална държава. Този модел работи най-добре, когато инициализация се осъществява под формата на създаване на нов обект (тъй като в този случай, ако множество нишки изпълняват инициализация, като всеки от тях ще се създаде отделен независим орган).
  • Обадете InitOnceComplete с резултатите от вашата инициализация.
  • Ако InitOnceComplete функцията успее, тогава сте спечелили състезанието и тази процедура инициализация е завършена.
  • Ако функцията за изпълнение InitOnceComplete не успее, тогава ще загубите състезанието инициализация и трябва да отмени резултатите от вашата неуспешен инициализация. И в този случай трябва да се обадите InitOnceBeginInitialize за последен път, за да се получи в резултат на инициализация извършва в поток, който спечели състезанието.

Въпреки факта, че описанието на алгоритъма е доста обширна, от концептуална гледна точка, това е съвсем проста. Сега поне е писано под формата на инструкции стъпка по стъпка.

Упражнение. Какво би станало, ако вместо да се обадите на стойност функция InitOnceComplete INIT_ONCE_INIT_FAILED просто връща контрола без попълване на еднократна инициализация?

Упражнение. Какво би станало, ако две теми се опитват да изпълни обезпечаване и асинхронни нишка, която за пръв път достига до крайния етап от подготовката за работа се провали?

Упражнение. Комбинирането на резултатите от предишните две упражнения и си представете какво ще се случи в края на краищата.