<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Блог Рэймонда Чена (перевод)</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/ruoldnewthing/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/feed.xml</link>
    <description />
    <lastBuildDate>Wed, 17 Apr 2019 03:25:32 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Алгоритмы без использования блокировок: обнови, если сможешь, а я выключаюсь</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/751</link>
      <pubDate>Sun, 01 Jul 2012 00:54:52 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2012/07/01/751/</guid>
      <description><![CDATA[Клиент попросил помощи в решении проблемы синхронизации процессов:
У нас есть небольшой объем...]]></description>
      <content:encoded><![CDATA[<p>Клиент попросил помощи в решении проблемы синхронизации процессов:</p>
<blockquote><p>У нас есть небольшой объем данных, который разделяется между несколькими процессами. Одним из способов защиты данных является использование примитива взаимоисключающей блокировки SpinLock. Однако этот подход может привести к взаимоблокировке, если процесс, владеющий примитивом SpinLock, не имеет возможности освободить его. Например, он может быть приостановлен отладчиком или кто-то может вызвать функцию TerminateProcess для того, чтобы уничтожить этот процесс.</p>
<p>Есть ли какие-нибудь идеи относительно того, как можно разделять эти данные между процессами, не опасаясь возникновения проблем в подобных случаях? Может, стоит реализовать что-нибудь вроде следующего механизма: считыватель устанавливает блокировку, извлекает данные, а затем, по окончанию операции, проверяет некоторый статус, указывающий, верны ли считанные данные или нет. Тем временем, процесс, записывающий данные, пытается получить блокировку с некоторым таймаутом, и если таймаут истек, он все равно обновляет данные и каким-то образом устанавливает статус считывателя так, что считывателю становится известно, что полученные данные устарели и нужно перечитать их заново. В сущности, я не хочу, чтобы процесс, который считывает или записывает данные, завис только из-за того, что разработчик, скажем, просто установил точку останова в отладчике в самое неподходящее время.</p></blockquote>
<p>Эта задача может быть разрешена при помощи шаблона «Издатель – подписчик». Когда издателю нужно обновить значения, он уведомляет подписчиков об этом, просто сообщая им местоположение обновленных данных.</p>
<p>Давайте предположим, что разделяемые данные являются коллекцией четырех целых чисел типа int.</p>
<p><code>struct SHAREDDATA {<br />
&nbsp;&nbsp;int a, b, c, d;<br />
};</code></p>
<p>Также предположим, что существует некоторый лимит на частоту изменения этих данных. Это предположение, обычно, является допустимым, потому что в подобных ситуациях присутствует какое-либо внешнее ограничение, вроде: «Это значение изменяется в результате некоторого действия пользователя». (Даже если такой лимит отсутствует, большинство реализаций просто устанавливают какое-либо собственное значение в качестве лимита. К примеру, <a href="https://msdn.microsoft.com/ru-ru/library/ms684121.aspx">функции SLIST</a> предполагают, что процессор не будет заблокирован более чем 65535 раз подряд). Давайте предположим, что в нашем случае значение не будет изменяться более чем 64 раза в расчете на одну операцию чтения.</p>
<p><code>#define SHAREDDATA_MAXCONCURRENT 64<br /><br />
SHAREDDATA g_rgsd[SHAREDDATA_MAXCONCURRENT];<br />
UINT g_isd; // текущее местоположение корректного значения<br /><br />
void GetSharedData(__out SHAREDDATA *psd)<br />
{<br />
&nbsp;&nbsp;*psd = g_rgsd[g_isd];<br />
}</code></p>
<p>Считывателю данных нужно просто получить последнее опубликованное значение. Самой сложной частью является публикация значения.</p>
<p>Хотя, на самом деле, не так уж это и сложно.</p>
<p><code>LONG g_isdNext = 1;<br /><br />
void UpdateSharedData(__in const SHAREDDATA *psd)<br />
{<br />
&nbsp;&nbsp;UINT isd = (UINT)InterlockedIncrementAcquire(&g_isdNext);<br />
&nbsp;&nbsp;isd %= SHAREDDATA_MAXCONCURRENT;<br />
&nbsp;&nbsp;g_rgsd[isd] = *psd;<br />
&nbsp;&nbsp;InterlockedExchange(&g_isd, isd);<br />
}</code></p>
<p>Публикация данных заключается в простом получении слота для данных при помощи функции InterlockedIncrement, которая вернет нам уникальное местоположение для сохраняемых данных, или, по крайней мере, уникальное в течение SHAREDDATA_MAXCONCURRENT – 1 промежуточных публикаций. Мы сохраняем наши результаты в полученный нами слот памяти, а затем публикуем новый индекс. Публикация должна быть осуществлена с применением семантики освобождения (Release semantics), но поскольку другие вызовы функции InterlockedExchangeRelease отсутствуют, то мы просто выполним обновление значения с использованием полного барьера памяти.</p>
<p>Обратите внимание, что обновление данных не является атомарной операцией с точки зрения их считывания. Процессор может выполнить функцию GetSharedData, обработать полученные значения, а затем опубликовать их, перезаписывая тем самым публикацию, производимую в это время другим процессором. Если новые значения зависят от старых (например, если они являются нарастающим итогом), то в таком случае вы только что потеряли одно обновление данных.</p>
<p>Также обратите внимание, что если два потока попытаются обновить данные одновременно, то нельзя сказать с полной уверенностью, какой набор данных будет прочитан, потому что здесь действует правило «<i>выигрывает последний издатель</i>».</p>
<p>Так получилось, что в данном случае новые значения абсолютно не зависят от старых, следовательно, проблема с потерянными обновлениями попросту отсутствует. Кроме того, в системе клиента обновлением данных в каждый момент времени занимается только один процесс. (В системе присутствует главный контроллер, который обновляет эти значения, а все остальные процессы лишь считывают их). Таким образом, этот простой механизм удовлетворяет заданным требованиям.</p>
<p><strong>Упражнение</strong>: как бы вы изменили это решение, если бы новые значения зависели от старых?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: обнови, если сможешь, а я выключаюсь</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/23</link>
      <pubDate>Sat, 30 Jun 2012 13:54:52 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2012/06/30/23/</guid>
      <description><![CDATA[Клиент попросил помощи в решении проблемы синхронизации процессов:
У нас есть небольшой объем...]]></description>
      <content:encoded><![CDATA[<p>Клиент попросил помощи в решении проблемы синхронизации процессов:</p>
<blockquote><p>У нас есть небольшой объем данных, который разделяется между несколькими процессами. Одним из способов защиты данных является использование примитива взаимоисключающей блокировки SpinLock. Однако этот подход может привести к взаимоблокировке, если процесс, владеющий примитивом SpinLock, не имеет возможности освободить его. Например, он может быть приостановлен отладчиком или кто-то может вызвать функцию TerminateProcess для того, чтобы уничтожить этот процесс.</p>
<p>Есть ли какие-нибудь идеи относительно того, как можно разделять эти данные между процессами, не опасаясь возникновения проблем в подобных случаях? Может, стоит реализовать что-нибудь вроде следующего механизма: считыватель устанавливает блокировку, извлекает данные, а затем, по окончанию операции, проверяет некоторый статус, указывающий, верны ли считанные данные или нет. Тем временем, процесс, записывающий данные, пытается получить блокировку с некоторым таймаутом, и если таймаут истек, он все равно обновляет данные и каким-то образом устанавливает статус считывателя так, что считывателю становится известно, что полученные данные устарели и нужно перечитать их заново. В сущности, я не хочу, чтобы процесс, который считывает или записывает данные, завис только из-за того, что разработчик, скажем, просто установил точку останова в отладчике в самое неподходящее время.</p></blockquote>
<p>Эта задача может быть разрешена при помощи шаблона «Издатель – подписчик». Когда издателю нужно обновить значения, он уведомляет подписчиков об этом, просто сообщая им местоположение обновленных данных.</p>
<p>Давайте предположим, что разделяемые данные являются коллекцией четырех целых чисел типа int.</p>
<p><code>struct SHAREDDATA {<br />
&nbsp;&nbsp;int a, b, c, d;<br />
};</code></p>
<p>Также предположим, что существует некоторый лимит на частоту изменения этих данных. Это предположение, обычно, является допустимым, потому что в подобных ситуациях присутствует какое-либо внешнее ограничение, вроде: «Это значение изменяется в результате некоторого действия пользователя». (Даже если такой лимит отсутствует, большинство реализаций просто устанавливают какое-либо собственное значение в качестве лимита. К примеру, <a href="https://msdn.microsoft.com/ru-ru/library/ms684121.aspx">функции SLIST</a> предполагают, что процессор не будет заблокирован более чем 65535 раз подряд). Давайте предположим, что в нашем случае значение не будет изменяться более чем 64 раза в расчете на одну операцию чтения.</p>
<p><code>#define SHAREDDATA_MAXCONCURRENT 64<br /><br />
SHAREDDATA g_rgsd[SHAREDDATA_MAXCONCURRENT];<br />
UINT g_isd; // текущее местоположение корректного значения<br /><br />
void GetSharedData(__out SHAREDDATA *psd)<br />
{<br />
&nbsp;&nbsp;*psd = g_rgsd[g_isd];<br />
}</code></p>
<p>Считывателю данных нужно просто получить последнее опубликованное значение. Самой сложной частью является публикация значения.</p>
<p>Хотя, на самом деле, не так уж это и сложно.</p>
<p><code>LONG g_isdNext = 1;<br /><br />
void UpdateSharedData(__in const SHAREDDATA *psd)<br />
{<br />
&nbsp;&nbsp;UINT isd = (UINT)InterlockedIncrementAcquire(&g_isdNext);<br />
&nbsp;&nbsp;isd %= SHAREDDATA_MAXCONCURRENT;<br />
&nbsp;&nbsp;g_rgsd[isd] = *psd;<br />
&nbsp;&nbsp;InterlockedExchange(&g_isd, isd);<br />
}</code></p>
<p>Публикация данных заключается в простом получении слота для данных при помощи функции InterlockedIncrement, которая вернет нам уникальное местоположение для сохраняемых данных, или, по крайней мере, уникальное в течение SHAREDDATA_MAXCONCURRENT – 1 промежуточных публикаций. Мы сохраняем наши результаты в полученный нами слот памяти, а затем публикуем новый индекс. Публикация должна быть осуществлена с применением семантики освобождения (Release semantics), но поскольку другие вызовы функции InterlockedExchangeRelease отсутствуют, то мы просто выполним обновление значения с использованием полного барьера памяти.</p>
<p>Обратите внимание, что обновление данных не является атомарной операцией с точки зрения их считывания. Процессор может выполнить функцию GetSharedData, обработать полученные значения, а затем опубликовать их, перезаписывая тем самым публикацию, производимую в это время другим процессором. Если новые значения зависят от старых (например, если они являются нарастающим итогом), то в таком случае вы только что потеряли одно обновление данных.</p>
<p>Также обратите внимание, что если два потока попытаются обновить данные одновременно, то нельзя сказать с полной уверенностью, какой набор данных будет прочитан, потому что здесь действует правило «<i>выигрывает последний издатель</i>».</p>
<p>Так получилось, что в данном случае новые значения абсолютно не зависят от старых, следовательно, проблема с потерянными обновлениями попросту отсутствует. Кроме того, в системе клиента обновлением данных в каждый момент времени занимается только один процесс. (В системе присутствует главный контроллер, который обновляет эти значения, а все остальные процессы лишь считывают их). Таким образом, этот простой механизм удовлетворяет заданным требованиям.</p>
<p><strong>Упражнение</strong>: как бы вы изменили это решение, если бы новые значения зависели от старых?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Шаблоны использования функций InitOnce</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/initonce</link>
      <pubDate>Wed, 06 Jul 2011 04:57:31 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/07/06/initonce/</guid>
      <description><![CDATA[Поскольку от написания кода без использования блокировок может начать болеть голова, вам, вероятно,...]]></description>
      <content:encoded><![CDATA[<p>Поскольку от написания кода без использования блокировок <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/07/10150728.aspx#10151021">может начать болеть голова</a>, вам, вероятно, имеет смысл переложить эту обязанность на каких-нибудь других людей, чтобы голова болела у них. И такими людьми являются парни из команды работки ядра Windows, которые написали довольно много готовых программных компонентов, не использующих блокировки, которые теперь не нужно разрабатывать вам. Среди них, к примеру, есть набор функций для работы с <a href="https://msdn.microsoft.com/en-us/library/ms684121(VS.85).aspx">неблокирующими потокобезопасными списками</a>. Но сегодня мы рассмотрим <a href="https://msdn.microsoft.com/en-us/library/aa363808">функции однократной инициализации</a>.</p>
<p>На самом деле, в наиболее простых функциях однократной инициализации блокировки используются, но при этом они реализованы по шаблону блокировки с двойной проверкой, что позволяет вам не заботиться об этих деталях. Алгоритм использования <a href="https://msdn.microsoft.com/en-us/library/ms683493(v=VS.85).aspx">функции InitOnceExecuteOnce</a> является довольно простым. Вот самый примитивный вариант его использования:</p>
<p><code>int SomeGlobalInteger;<br /><br />
BOOL CALLBACK ThisRunsAtMostOnce(<br />
&nbsp;&nbsp;&nbsp;&nbsp;PINIT_ONCE initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID Parameter,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID *Context)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;calculate_an_integer(&amp;SomeGlobalInteger);<br />
&nbsp;&nbsp;&nbsp;&nbsp;return TRUE;<br />
}<br /><br />
void InitializeThatGlobalInteger()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;<br />
&nbsp;&nbsp;&nbsp;&nbsp;InitOnceExecuteOnce(&amp;initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ThisRunsAtMostOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nullptr, nullptr);<br />
}</code></p>
<p>В этом самом простом варианте вы передаете функции InitOnceExecuteOnce структуру INIT_ONCE (в которую функция записывает свое состояние) и ссылку на функцию обратного вызова. Если функция InitOnceExecuteOnce для заданной структуры INIT_ONCE выполняется впервые, она вызывает функцию обратного вызова. Функция обратного вызова может делать все, что ей заблагорассудится, но, скорее всего, она будет производить некоторую инициализацию, которая должна выполняться однократно. Если функция InitOnceExecuteOnce для той же самой структуры INIT_ONCE будет вызвана другим потоком, выполнение этого потока будет приостановлено до тех пор, пока первый поток не закончит выполнение своего инициализационного кода.</p>
<p>Мы можем сделать этот пример слегка интереснее, предположив, что операция вычисления целого числа может завершиться с ошибкой.</p>
<p><code>BOOL CALLBACK ThisSucceedsAtMostOnce(<br />
&nbsp;&nbsp;&nbsp;&nbsp;PINIT_ONCE initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID Parameter,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID *Context)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;return SUCCEEDED(calculate_an_integer(&amp;SomeGlobalInteger));<br />
}<br /><br />
BOOL TryToInitializeThatGlobalInteger()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;<br />
&nbsp;&nbsp;&nbsp;&nbsp;return InitOnceExecuteOnce(&amp;initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ThisSucceedsAtMostOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nullptr, nullptr);<br />
}</code></p>
<p>Если ваша инициализационная функция вернет FALSE, то инициализация будет считаться неуспешной и когда в следующий раз кто-нибудь вызовет функцию InitOnceExecuteOnce, она снова попытается выполнить инициализацию.
Еще чуточку более интересный вариант использования функции InitOnceExecuteOnce принимает во внимание параметр Context. Парни из команды разработки ядра Windows заметили, что структура INIT_ONCE в состоянии «проинициализировано» содержит множество неиспользуемых битов, и они предложили вам использовать их для собственных нужд. Это довольно удобно в том случае, когда то, что вы инициализируете, является указателем на объект C++, потому что это означает, что теперь вам нужно заботиться лишь об одной вещи вместо двух.</p>
<p><code>BOOL CALLBACK AllocateAndInitializeTheThing(<br />
&nbsp;&nbsp;&nbsp;&nbsp;PINIT_ONCE initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID Parameter,<br />
&nbsp;&nbsp;&nbsp;&nbsp;PVOID *Context)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;*Context = new(nothrow) Thing();<br />
&nbsp;&nbsp;&nbsp;&nbsp;return *Context != nullptr;<br />
}<br /><br />
Thing *GetSingletonThing(int arg1, int arg2)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;<br />
&nbsp;&nbsp;&nbsp;&nbsp;void *Result;<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (InitOnceExecuteOnce(&amp;initOnce,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AllocateAndInitializeTheThing,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nullptr, &amp;Result))<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return static_cast&lt;Thing*&gt;(Result);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;return nullptr;<br />
}</code></p>
<p>Последний параметр функции InitOnceExecuteOnce принимает «волшебные» данные, по размеру почти идентичные указателю, которые функция запомнит для вас. Затем ваша функция обратного вызова передает эти «волшебные» данные обратно, через параметр Context, а функция InitOnceExecuteOnce возвращает их вам в виде параметра Result.
Как и в предыдущем случае, если два потока вызовут функцию InitOnceExecuteOnce одновременно, используя неинициализированную структуру INIT_ONCE, один из них вызывет функцию инициализации, а другой поток будет приостановлен.</p>
<p>До этого момента мы рассматривали шаблоны синхронной инициализации. Для своей работы они используют блокировки: если вы вызовете функцию InitOnceExecuteOnce в тот момент, когда производится инициализация структуры INIT_ONCE, этот вызов будет ожидать завершения текущей попытки инициализации (вне зависимости от того, будет ли она успешной или окончится неудачей).</p>
<p>Гораздо интересней асинхронный шаблон. Вот пример такого шаблона применительно к нашей задаче с классом SingletonManager:</p>
<p><code>&nbsp;&nbsp;SingletonManager(const SINGLETONINFO *rgsi, UINT csi)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;: m_rgsi(rgsi), m_csi(csi),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_rgio(new INITONCE[csi]) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;for (UINT iio = 0; iio &lt; csi; iio++) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InitOnceInitialize(&amp;m_rgio[iio]);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;...<br />
&nbsp;&nbsp;// Массив, описывающий созданные объекты<br />
&nbsp;&nbsp;// объекты в этом массиве расположены параллельно элементам массива m_rgsi<br />
&nbsp;&nbsp;INIT_ONCE *m_rgio;<br />
};<br /><br />
ITEMCONTROLLER *SingletonManager::Lookup(DWORD dwId)<br />
{<br />
&nbsp;&nbsp;... все точно так же, как и в предыдущем варианте, вплоть до того места,<br />
&nbsp;&nbsp;где начинается реализация шаблона «singleton-конструктор»<br /><br />
&nbsp;&nbsp;void *pv = NULL;<br />
&nbsp;&nbsp;BOOL fPending;<br />
&nbsp;&nbsp;if (!InitOnceBeginInitialize(&amp;m_rgio[i], INIT_ONCE_ASYNC,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;fPending, &amp;pv)) return NULL;<br /><br />
&nbsp;&nbsp;if (fPending) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;ITEMCONTROLLER *pic = m_rgsi[i].pfnCreateController();<br />
&nbsp;&nbsp;&nbsp;&nbsp;DWORD dwResult = pic ? 0 : INIT_ONCE_INIT_FAILED;<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (InitOnceComplete(&amp;m_rgio[i],<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INIT_ONCE_ASYNC | dwResult, pic)) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pv = pic;<br />
&nbsp;&nbsp;&nbsp;&nbsp;} else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// проиграл в гонке — теперь уничтожь ненужную копию и получи результат победителя<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete pic;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InitOnceBeginInitialize(&amp;m_rgio[i], INIT_ONCE_CHECK_ONLY,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X&amp;fPending, &amp;pv);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return static_cast&lt;ITEMCONTROLLER *&gt;(pv);<br />
}</code></p>
<p>Таким образом, шаблон для асинхронной инициализации состоит из следующих шагов:
<ul><li>Вызовите функцию InitOnceBeginInitialize в асинхронном режиме.</li>
<li>Если она вернет fPending == FALSE, значит инициализация уже была произведена и вы можете использовать результат, переданный в качестве последнего параметра.</li>
<li>В противном случае инициализация еще не выполнялась (или еще не завершилась). Выполните свою инициализацию, но помните, что поскольку это алгоритм без использования блокировок, может оказаться, что эту инициализацию в данный момент выполняют еще несколько других потоков, следовательно, вам нужно быть очень осторожными с <a href="https://rus-it.org/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%D1%8B/%D0%A0%D1%8D%D0%B9%D0%BC%D0%BE%D0%BD%D0%B4_%D0%A7%D0%B5%D0%BD/%D0%91%D0%BB%D0%BE%D0%B3/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B1%D0%B5%D0%B7_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BE%D0%BA_%D0%BE%D0%B4%D0%BD%D0%BE%D0%BA%D1%80%D0%B0%D1%82%D0%BD%D0%B0%D1%8F_%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F">операциями над объектами, хранящими глобальное состояние</a>. Этот шаблон работает лучше всего, когда инициализация реализована в виде создания нового объекта (потому что в этом случае, если несколько потоков будут выполнять инициализацию, каждый из них будет создавать отдельный независимый объект).</li>
<li>Вызовите InitOnceComplete с результатами вашей инициализации.</li>
<li>Если выполнение функции InitOnceComplete завершится успешно, значит вы выиграли гонку и на этом процедура инициализации закончена.</li>
<li>Если выполнение функции InitOnceComplete закончится неудачей, значит вы проиграли инициализационную гонку и должны отменить результаты вашей неуспешной инициализации. Также в этом случае вам нужно вызвать InitOnceBeginInitialize еще один, последний раз, для того, чтобы получить результат инициализации, выполненной в потоке, который выиграл гонку.</li></ul></p>
<p>Несмотря на то, что описание алгоритма довольно объемно, с концептуальной точки зрения он довольно прост. По крайней мере, теперь он написан в форме пошаговой инструкции.</p>
<p><strong>Упражнение</strong>: что будет, если вместо вызова InitOnceComplete со значением INIT_ONCE_INIT_FAILED функция просто вернет управление без завершения однократной инициализации?</p>
<p><strong>Упражнение</strong>: что будет, если два потока попытаются выполнить асинхронную инициализацию и поток, который первым дойдет до конечного этапа инициализации, потерпит неудачу?</p>
<p><strong>Упражнение</strong>: объедините результаты двух предыдущих упражнений и предположите, что получится в итоге.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: однократная инициализация</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/761</link>
      <pubDate>Tue, 31 May 2011 23:56:46 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/31/761/</guid>
      <description><![CDATA[Частным случаем singleton-конструктора является обычная «ленивая» инициализация множества...]]></description>
      <content:encoded><![CDATA[<p>Частным случаем <a href="https://rus-it.org/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%D1%8B/%D0%A0%D1%8D%D0%B9%D0%BC%D0%BE%D0%BD%D0%B4_%D0%A7%D0%B5%D0%BD/%D0%91%D0%BB%D0%BE%D0%B3/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B1%D0%B5%D0%B7_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BE%D0%BA_singleton-%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80">singleton-конструктора</a> является обычная «ленивая» инициализация множества переменных. В однопоточном приложении вы можете написать примерно такой код:</p>
<p><code>// предположим, что все допустимые значения переменных a и b удовлетворяют условиям:<br />
// a ≥ 0 и b ≥ a. Таким образом, -1 не является допустимым значением<br />
// и мы будем использовать его для того, чтобы обозначить,<br />
// что эта переменная еще не проинициализирована.<br />
int a = -1, b = -1;<br /><br />
void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (a != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;a = calculate_nominal_a();<br />
&nbsp;&nbsp;b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;a = max(0, a);<br />
&nbsp;&nbsp;b = max(a, b);<br />
}</code></p>
<p>Этот код отлично работает в однопоточной программе, но если программа будет многопоточной, может возникнуть такая ситуация, когда два потока попытаются одновременно проинициализировать переменные, что приведет к состоянию гонки (race conditions). В результате один из потоков будет использовать значения переменных до того, как они будут проинициализированы:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (a != -1) [условие не выполняется]<br />a = calculate_nominal_a(); // возвращает 2</td><td></td></tr><tr><td></td><td>if (a != -1) return; // преждевременный возврат!</td></tr></table>
<p>Обратите внимание, что в случае, когда Поток 1 первым получил управление, то после операции записи значения в переменную a второй поток будет считать, что все уже проинициализировано и может сделать попытку использования неинициализированной переменной b.</p>
<p>«Ага», — скажете вы, — «это несложно исправить. Я просто буду использовать переменную b вместо a для того, чтобы обозначить, что инициализация уже завершена».</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (<b>b</b> != -1) return; // уже проинициализировано (проверяем b, а не a)<br /><br />
&nbsp;&nbsp;a = calculate_nominal_a();<br />
&nbsp;&nbsp;b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;a = max(0, a);<br />
&nbsp;&nbsp;b = max(a, b);<br />
}</code></p>
<p>Это все равно не исключает состояния гонки:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (b != -1) [условие не выполняется]<br />a = calculate_nominal_a(); // возвращает 2<br />b = calculate_nominal_b(); // возвращает 1</td><td></td></tr><tr><td></td><td>if (b != -1) return; // преждевременный возврат!</td></tr></table>
<p>«И это я могу исправить. Я просто посчитаю значения a и b и сохраню их в локальных переменных, а глобальные переменные буду обновлять только после того, как будут посчитаны окончательные значения. В этом случае второй поток не увидит частично вычисленные значения».</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (b != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;<b>// производим все вычисления и сохраняем их сначала в локальных переменных</b><br />
&nbsp;&nbsp;<b>int temp_a</b> = calculate_nominal_a();<br />
&nbsp;&nbsp;<b>int temp_b</b> = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;<b>temp_a</b> = max(0, <b>temp_a</b>);<br />
&nbsp;&nbsp;<b>temp_b</b> = max(<b>temp_a</b>, <b>temp_b</b>);<br /><br />
&nbsp;&nbsp;<b>// делаем временные значения постоянными<br />
&nbsp;&nbsp;a = temp_a;<br />
&nbsp;&nbsp;b = temp_b;</b><br />
}</code></p>
<p>Почти правильно, но <i>все равно</i> возникает состояние гонки:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (b != -1) [условие не выполняется]<br />temp_a = calculate_nominal_a(); // возвращает 2<br />temp_b = calculate_nominal_b(); // возвращает 1<br />temp_a = max(0, temp_a); // temp_a = 2<br />temp_b = max(temp_a, temp_b); // temp_b = 2<br />a = temp_a; // инициирована операция сохранения значения в память<br />b = temp_b; // инициирована операция сохранения значения в память<br />завершено сохранение в память переменной b<br /></td><td></td></tr><tr><td></td><td>if (b != -1) return; // преждевременный возврат!</td></tr><tr><td>завершено сохранение в память переменной a</td><td></td></table>
<p>Нет никаких гарантий того, что результат операции присвоения b = 2 станет видимым для остальных процессоров строго после операции присвоения a = 2. Даже несмотря на то, что операции сохранения значений инициируются в нужном нам порядке, контроллер памяти <a href="https://en.wikipedia.org/wiki/Memory_barrier#An_illustrative_example">может выполнить эти операции в обратном порядке</a>, что приведет к тому, что Поток 2 увидит значения a = -1 и b = 2.</p>
<p>Для того чтобы такого не происходило, запись в переменную b должна производиться с <a href="https://blogs.msdn.com/oldnewthing/archive/2008/10/03/8969397.aspx">семантикой освобождения</a> (Release semantics), требующей, чтобы все предыдущие операции записи в память завершились прежде, чем результат операции записи значения в переменную b станет виден другим процессорам.</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (b != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;// производим все вычисления и сохраняем их сначала в локальных переменных<br />
&nbsp;&nbsp;int temp_a = calculate_nominal_a();<br />
&nbsp;&nbsp;int temp_b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;temp_a = max(0, temp_a);<br />
&nbsp;&nbsp;temp_b = max(temp_a, temp_b);<br /><br />
&nbsp;&nbsp;// делаем временные значения постоянными<br />
&nbsp;&nbsp;a = temp_a;<br />
&nbsp;&nbsp;<b>// поскольку мы используем переменную b в качестве индикатора<br />
&nbsp;&nbsp;// завершения инициализации, мы должны присваивать ей значение в последнюю очередь<br />
&nbsp;&nbsp;// и использовать при этом семантику освобождения.<br />
&nbsp;&nbsp;InterlockedCompareExchangeRelease(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast&lt;LONG*&gt;&amp;b, temp_b, -1);</b><br />
}</code></p>
<p>Если вы взглянете на окончательный результат, вы заметите, что он является повторным изобретением шаблона singleton-инициализации, который гласит: выполните набор вычислений где-нибудь в сторонке, а затем опубликуйте результат в рамках единственной операции InterlockedCompareExchangeRelease.</p>
<p>Следовательно, общий шаблон будет следующим:</p>
<p><code>void LazyInitializePattern()<br />
{<br />
&nbsp;&nbsp;if (глобальная_сигнальная_переменная != маркерное_значение) return;<br /><br />
&nbsp;&nbsp;... выполнение вычислений и запись результатов в локальные переменные ...<br /><br />
&nbsp;&nbsp;глобальная_переменная1 = временная_переменная1;<br />
&nbsp;&nbsp;глобальная_переменная2 = временная_переменная2;<br />
&nbsp;&nbsp;...<br />
&nbsp;&nbsp;глобальная_переменнаяN = временная_переменнаяN;<br /><br />
&nbsp;&nbsp;// и в последнюю очередь — публикация сигнальной переменной<br />
&nbsp;&nbsp;// с использованием семантики освобождения для гарантии того,<br />
&nbsp;&nbsp;// что значения предыдущих переменных также будут видны<br />
&nbsp;&nbsp;InterlockedCompareExchangeRelease(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast&lt;LONG*&gt;&amp;глобальная_сигнальная_переменная,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;временная_сигнальная_переменная, маркерное_значение);<br />
}</code></p>
<p>Если это чересчур для вас (и учитывая то, что я в первый раз напортачил с коварной двойной проверкой-блокировкой, это чересчур и для меня), вы можете положиться на команду разработки ядра Windows, поручив ей думать обо всех этих деталях, и вместо написания этого кода вручную использовать <a href="https://msdn.microsoft.com/en-us/library/aa363808.aspx">готовые функции для однократной инициализации</a>, инкапсулирующие всю эту логику. (Мой приятель <a href="https://blogs.msdn.com/doronh/">Дорон</a> (Doron) <a href="https://blogs.msdn.com/doronh/archive/2006/11/29/support-for-double-checked-locking.aspx">некоторое время назад</a> обнародовал информацию об этих функциях однократной инициализации). Четвертая версия .NET Framework содержит соответствующую функциональность в <a href="https://msdn.microsoft.com/ru-ru/library/dd997286.aspx">классе Lazy&lt;T&gt;</a>.</p>
<p><strong>Упражнение</strong>: какие скрытые предположения были сделаны относительно функций calculate_nominal_a и calculate_nominal_b?</p>
<p><strong>Упражнение</strong>: какими будут последствия, если мы будем использовать функцию InterlockedExchange вместо функции InterlockedCompareExchangeRelease?</p>
<p><strong>Упражнение</strong>: действительно ли нужны переменные temp_a и temp_b в окончательной версии функции LazyInitialize или это просто пережитки предыдущих попыток избавиться от состояния гонки?</p>
<p><strong>Упражнение</strong>: какие изменения нужно внести (если они вообще требуются) в случае, когда глобальные переменные являются указателями? А если — переменными для чисел с плавающей точкой?</p>
<p><strong>Обновление</strong>: посмотрите переписку <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/07/10150728.aspx#10151639">между пользователями Niall и Anon</a> относительно необходимости семантики захвата (acquire semantics) при первоначальном чтении значений из переменных.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: выбор уникального значения (решения)</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/771</link>
      <pubDate>Tue, 31 May 2011 15:12:23 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/31/771/</guid>
      <description><![CDATA[В прошлый раз я опубликовал задание для разминки, содержащее фрагмент кода для выбора уникального в...]]></description>
      <content:encoded><![CDATA[<p>В прошлый раз я опубликовал <a href="https://rus-it.org/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%D1%8B/%D0%A0%D1%8D%D0%B9%D0%BC%D0%BE%D0%BD%D0%B4_%D0%A7%D0%B5%D0%BD/%D0%91%D0%BB%D0%BE%D0%B3/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B1%D0%B5%D0%B7_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BE%D0%BA_%D0%B2%D1%8B%D0%B1%D0%BE%D1%80_%D1%83%D0%BD%D0%B8%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F_%D1%80%D0%B0%D0%B7%D0%BC%D0%B8%D0%BD%D0%BA%D0%B0">задание для разминки</a>, содержащее фрагмент кода для выбора уникального в пределах процесса значения. Я приведу его еще раз:</p>
<p><code>dwUniqueId = InterlockedCompareExchange(&g_dwUniqueId,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId+1,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId);</code></p>
<p>Скорее всего, проще будет перечислить то, что в этой функции <i>корректно</i>, нежели перечислять то, что она делает неправильно.</p>
<p>Эм... все слова написаны без ошибок.</p>
<p>Это все.</p>
<p>Дэмиен (Damien) был первым, кто отметил, что <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150008">автор просто-напросто реализовал еще один вариант функции InterlockedIncrement. Неудовлетворительно.</a></p>
<p>Как мы видели ранее, алгоритм для выполнения сложных вычислений с использованием interlocked-функций содержит следующие стадии: <a href="https://blogs.msdn.com/b/oldnewthing/archive/2004/09/15/229915.aspx">захват (capture), вычисление (compute), сравнение-обмен (compare-exchange), повтор попытки (retry)</a>. Но вышеприведенный код ничего такого не делает.</p>
<p>Без захвата этих значений вышеприведенный код подвержен проблемам, когда два потока в одно и то же время изменяют значение g_dwUniqueId. Это означает, что шаг вычисления может потерпеть неудачу из-за несвоевременного считывания значения g_dwUniqueId, что приведет к тому, что значение, передаваемое в функцию InterlockedCompareExchange, станет неопределенным.</p>
<p>Итак, они правильно написали наименование функции InterlockedCompareExchange.
Но потом они забыли реализовать шаг повторения операции, в том случае, когда шаг сравнения-обмена завершился неудачей, что означает, что они просто-напросто продолжают использовать то значение g_dwUniqueId, которое находилось в этой переменной в момент вызова InterlockedCompareExchange и может быть как корректным, так и некорректным. Если это значение было увеличено на единицу другим потоком, то оба этих потока будут использовать одно и то же «уникальное» значение.</p>
<p>Джошуа (Joshua) отмечает, что <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150063">настройки оптимизации компилятора могут не позволить операции захвата выполнить действительный захват</a>. Однако я бы предпочел поставить ключевое слово «volatile» перед переменной g_dwUniqueId, нежели перед переменной scv, поскольку объект, помеченный ключевым словом volatile, является глобальной переменной, а не локальной. Пометка локальной переменной ключевым словом volatile заставляет выполнять все запросы к локальной переменной так, как указано в коде, но компилятор по-прежнему может оптимизировать операции доступа к переменной g_dwUniqueId. (К примеру, он может использовать значение переменной, полученное во время ее предыдущего считывания в функции).</p>
<p>И примите во внимание <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150047">предупреждение Лео Дэвидсона (Leo Davidson)</a>: эта серия статей предназначена для того, чтобы <i>заглянуть за кулисы и узнать, как там все устроено</i>, а не для того, чтобы <i>дать готовые инструкции и примеры для использования в реальных проектах</i>. Нам придется разобрать несколько тостеров, чтобы узнать, как они работают. Но в реальных проектах, когда это возможно, лучше используйте код, написанный людьми, которые умнее вас.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: однократная инициализация</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/43</link>
      <pubDate>Tue, 31 May 2011 12:56:46 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/31/43/</guid>
      <description><![CDATA[Частным случаем singleton-конструктора является обычная «ленивая» инициализация множества...]]></description>
      <content:encoded><![CDATA[<p>Частным случаем <a href="https://rus-it.org/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%D1%8B/%D0%A0%D1%8D%D0%B9%D0%BC%D0%BE%D0%BD%D0%B4_%D0%A7%D0%B5%D0%BD/%D0%91%D0%BB%D0%BE%D0%B3/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B1%D0%B5%D0%B7_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BE%D0%BA_singleton-%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D0%BE%D1%80">singleton-конструктора</a> является обычная «ленивая» инициализация множества переменных. В однопоточном приложении вы можете написать примерно такой код:</p>
<p><code>// предположим, что все допустимые значения переменных a и b удовлетворяют условиям:<br />
// a ≥ 0 и b ≥ a. Таким образом, -1 не является допустимым значением<br />
// и мы будем использовать его для того, чтобы обозначить,<br />
// что эта переменная еще не проинициализирована.<br />
int a = -1, b = -1;<br /><br />
void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (a != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;a = calculate_nominal_a();<br />
&nbsp;&nbsp;b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;a = max(0, a);<br />
&nbsp;&nbsp;b = max(a, b);<br />
}</code></p>
<p>Этот код отлично работает в однопоточной программе, но если программа будет многопоточной, может возникнуть такая ситуация, когда два потока попытаются одновременно проинициализировать переменные, что приведет к состоянию гонки (race conditions). В результате один из потоков будет использовать значения переменных до того, как они будут проинициализированы:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (a != -1) [условие не выполняется]<br />a = calculate_nominal_a(); // возвращает 2</td><td></td></tr><tr><td></td><td>if (a != -1) return; // преждевременный возврат!</td></tr></table>
<p>Обратите внимание, что в случае, когда Поток 1 первым получил управление, то после операции записи значения в переменную a второй поток будет считать, что все уже проинициализировано и может сделать попытку использования неинициализированной переменной b.</p>
<p>«Ага», — скажете вы, — «это несложно исправить. Я просто буду использовать переменную b вместо a для того, чтобы обозначить, что инициализация уже завершена».</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (<b>b</b> != -1) return; // уже проинициализировано (проверяем b, а не a)<br /><br />
&nbsp;&nbsp;a = calculate_nominal_a();<br />
&nbsp;&nbsp;b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;a = max(0, a);<br />
&nbsp;&nbsp;b = max(a, b);<br />
}</code></p>
<p>Это все равно не исключает состояния гонки:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (b != -1) [условие не выполняется]<br />a = calculate_nominal_a(); // возвращает 2<br />b = calculate_nominal_b(); // возвращает 1</td><td></td></tr><tr><td></td><td>if (b != -1) return; // преждевременный возврат!</td></tr></table>
<p>«И это я могу исправить. Я просто посчитаю значения a и b и сохраню их в локальных переменных, а глобальные переменные буду обновлять только после того, как будут посчитаны окончательные значения. В этом случае второй поток не увидит частично вычисленные значения».</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (b != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;<b>// производим все вычисления и сохраняем их сначала в локальных переменных</b><br />
&nbsp;&nbsp;<b>int temp_a</b> = calculate_nominal_a();<br />
&nbsp;&nbsp;<b>int temp_b</b> = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;<b>temp_a</b> = max(0, <b>temp_a</b>);<br />
&nbsp;&nbsp;<b>temp_b</b> = max(<b>temp_a</b>, <b>temp_b</b>);<br /><br />
&nbsp;&nbsp;<b>// делаем временные значения постоянными<br />
&nbsp;&nbsp;a = temp_a;<br />
&nbsp;&nbsp;b = temp_b;</b><br />
}</code></p>
<p>Почти правильно, но <i>все равно</i> возникает состояние гонки:</p>
<table><tr class="header"><th>Поток 1</th><th>Поток 2</th></tr><tr><td>if (b != -1) [условие не выполняется]<br />temp_a = calculate_nominal_a(); // возвращает 2<br />temp_b = calculate_nominal_b(); // возвращает 1<br />temp_a = max(0, temp_a); // temp_a = 2<br />temp_b = max(temp_a, temp_b); // temp_b = 2<br />a = temp_a; // инициирована операция сохранения значения в память<br />b = temp_b; // инициирована операция сохранения значения в память<br />завершено сохранение в память переменной b<br /></td><td></td></tr><tr><td></td><td>if (b != -1) return; // преждевременный возврат!</td></tr><tr><td>завершено сохранение в память переменной a</td><td></td></table>
<p>Нет никаких гарантий того, что результат операции присвоения b = 2 станет видимым для остальных процессоров строго после операции присвоения a = 2. Даже несмотря на то, что операции сохранения значений инициируются в нужном нам порядке, контроллер памяти <a href="https://en.wikipedia.org/wiki/Memory_barrier#An_illustrative_example">может выполнить эти операции в обратном порядке</a>, что приведет к тому, что Поток 2 увидит значения a = -1 и b = 2.</p>
<p>Для того чтобы такого не происходило, запись в переменную b должна производиться с <a href="https://blogs.msdn.com/oldnewthing/archive/2008/10/03/8969397.aspx">семантикой освобождения</a> (Release semantics), требующей, чтобы все предыдущие операции записи в память завершились прежде, чем результат операции записи значения в переменную b станет виден другим процессорам.</p>
<p><code>void LazyInitialize()<br />
{<br />
&nbsp;&nbsp;if (b != -1) return; // уже проинициализировано<br /><br />
&nbsp;&nbsp;// производим все вычисления и сохраняем их сначала в локальных переменных<br />
&nbsp;&nbsp;int temp_a = calculate_nominal_a();<br />
&nbsp;&nbsp;int temp_b = calculate_nominal_b();<br /><br />
&nbsp;&nbsp;// Приведение значений переменных в соответствие с нашими ограничениями.<br />
&nbsp;&nbsp;temp_a = max(0, temp_a);<br />
&nbsp;&nbsp;temp_b = max(temp_a, temp_b);<br /><br />
&nbsp;&nbsp;// делаем временные значения постоянными<br />
&nbsp;&nbsp;a = temp_a;<br />
&nbsp;&nbsp;<b>// поскольку мы используем переменную b в качестве индикатора<br />
&nbsp;&nbsp;// завершения инициализации, мы должны присваивать ей значение в последнюю очередь<br />
&nbsp;&nbsp;// и использовать при этом семантику освобождения.<br />
&nbsp;&nbsp;InterlockedCompareExchangeRelease(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast&lt;LONG*&gt;&amp;b, temp_b, -1);</b><br />
}</code></p>
<p>Если вы взглянете на окончательный результат, вы заметите, что он является повторным изобретением шаблона singleton-инициализации, который гласит: выполните набор вычислений где-нибудь в сторонке, а затем опубликуйте результат в рамках единственной операции InterlockedCompareExchangeRelease.</p>
<p>Следовательно, общий шаблон будет следующим:</p>
<p><code>void LazyInitializePattern()<br />
{<br />
&nbsp;&nbsp;if (глобальная_сигнальная_переменная != маркерное_значение) return;<br /><br />
&nbsp;&nbsp;... выполнение вычислений и запись результатов в локальные переменные ...<br /><br />
&nbsp;&nbsp;глобальная_переменная1 = временная_переменная1;<br />
&nbsp;&nbsp;глобальная_переменная2 = временная_переменная2;<br />
&nbsp;&nbsp;...<br />
&nbsp;&nbsp;глобальная_переменнаяN = временная_переменнаяN;<br /><br />
&nbsp;&nbsp;// и в последнюю очередь — публикация сигнальной переменной<br />
&nbsp;&nbsp;// с использованием семантики освобождения для гарантии того,<br />
&nbsp;&nbsp;// что значения предыдущих переменных также будут видны<br />
&nbsp;&nbsp;InterlockedCompareExchangeRelease(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast&lt;LONG*&gt;&amp;глобальная_сигнальная_переменная,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;временная_сигнальная_переменная, маркерное_значение);<br />
}</code></p>
<p>Если это чересчур для вас (и учитывая то, что я в первый раз напортачил с коварной двойной проверкой-блокировкой, это чересчур и для меня), вы можете положиться на команду разработки ядра Windows, поручив ей думать обо всех этих деталях, и вместо написания этого кода вручную использовать <a href="https://msdn.microsoft.com/en-us/library/aa363808.aspx">готовые функции для однократной инициализации</a>, инкапсулирующие всю эту логику. (Мой приятель <a href="https://blogs.msdn.com/doronh/">Дорон</a> (Doron) <a href="https://blogs.msdn.com/doronh/archive/2006/11/29/support-for-double-checked-locking.aspx">некоторое время назад</a> обнародовал информацию об этих функциях однократной инициализации). Четвертая версия .NET Framework содержит соответствующую функциональность в <a href="https://msdn.microsoft.com/ru-ru/library/dd997286.aspx">классе Lazy&lt;T&gt;</a>.</p>
<p><strong>Упражнение</strong>: какие скрытые предположения были сделаны относительно функций calculate_nominal_a и calculate_nominal_b?</p>
<p><strong>Упражнение</strong>: какими будут последствия, если мы будем использовать функцию InterlockedExchange вместо функции InterlockedCompareExchangeRelease?</p>
<p><strong>Упражнение</strong>: действительно ли нужны переменные temp_a и temp_b в окончательной версии функции LazyInitialize или это просто пережитки предыдущих попыток избавиться от состояния гонки?</p>
<p><strong>Упражнение</strong>: какие изменения нужно внести (если они вообще требуются) в случае, когда глобальные переменные являются указателями? А если — переменными для чисел с плавающей точкой?</p>
<p><strong>Обновление</strong>: посмотрите переписку <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/07/10150728.aspx#10151639">между пользователями Niall и Anon</a> относительно необходимости семантики захвата (acquire semantics) при первоначальном чтении значений из переменных.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: выбор уникального значения (решения)</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/53</link>
      <pubDate>Tue, 31 May 2011 04:12:23 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/31/53/</guid>
      <description><![CDATA[В прошлый раз я опубликовал задание для разминки, содержащее фрагмент кода для выбора уникального в...]]></description>
      <content:encoded><![CDATA[<p>В прошлый раз я опубликовал <a href="https://rus-it.org/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%D1%8B/%D0%A0%D1%8D%D0%B9%D0%BC%D0%BE%D0%BD%D0%B4_%D0%A7%D0%B5%D0%BD/%D0%91%D0%BB%D0%BE%D0%B3/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B1%D0%B5%D0%B7_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BE%D0%BA_%D0%B2%D1%8B%D0%B1%D0%BE%D1%80_%D1%83%D0%BD%D0%B8%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F_%D1%80%D0%B0%D0%B7%D0%BC%D0%B8%D0%BD%D0%BA%D0%B0">задание для разминки</a>, содержащее фрагмент кода для выбора уникального в пределах процесса значения. Я приведу его еще раз:</p>
<p><code>dwUniqueId = InterlockedCompareExchange(&g_dwUniqueId,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId+1,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId);</code></p>
<p>Скорее всего, проще будет перечислить то, что в этой функции <i>корректно</i>, нежели перечислять то, что она делает неправильно.</p>
<p>Эм... все слова написаны без ошибок.</p>
<p>Это все.</p>
<p>Дэмиен (Damien) был первым, кто отметил, что <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150008">автор просто-напросто реализовал еще один вариант функции InterlockedIncrement. Неудовлетворительно.</a></p>
<p>Как мы видели ранее, алгоритм для выполнения сложных вычислений с использованием interlocked-функций содержит следующие стадии: <a href="https://blogs.msdn.com/b/oldnewthing/archive/2004/09/15/229915.aspx">захват (capture), вычисление (compute), сравнение-обмен (compare-exchange), повтор попытки (retry)</a>. Но вышеприведенный код ничего такого не делает.</p>
<p>Без захвата этих значений вышеприведенный код подвержен проблемам, когда два потока в одно и то же время изменяют значение g_dwUniqueId. Это означает, что шаг вычисления может потерпеть неудачу из-за несвоевременного считывания значения g_dwUniqueId, что приведет к тому, что значение, передаваемое в функцию InterlockedCompareExchange, станет неопределенным.</p>
<p>Итак, они правильно написали наименование функции InterlockedCompareExchange.
Но потом они забыли реализовать шаг повторения операции, в том случае, когда шаг сравнения-обмена завершился неудачей, что означает, что они просто-напросто продолжают использовать то значение g_dwUniqueId, которое находилось в этой переменной в момент вызова InterlockedCompareExchange и может быть как корректным, так и некорректным. Если это значение было увеличено на единицу другим потоком, то оба этих потока будут использовать одно и то же «уникальное» значение.</p>
<p>Джошуа (Joshua) отмечает, что <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150063">настройки оптимизации компилятора могут не позволить операции захвата выполнить действительный захват</a>. Однако я бы предпочел поставить ключевое слово «volatile» перед переменной g_dwUniqueId, нежели перед переменной scv, поскольку объект, помеченный ключевым словом volatile, является глобальной переменной, а не локальной. Пометка локальной переменной ключевым словом volatile заставляет выполнять все запросы к локальной переменной так, как указано в коде, но компилятор по-прежнему может оптимизировать операции доступа к переменной g_dwUniqueId. (К примеру, он может использовать значение переменной, полученное во время ее предыдущего считывания в функции).</p>
<p>И примите во внимание <a href="https://blogs.msdn.com/b/oldnewthing/archive/2011/04/05/10149783.aspx#10150047">предупреждение Лео Дэвидсона (Leo Davidson)</a>: эта серия статей предназначена для того, чтобы <i>заглянуть за кулисы и узнать, как там все устроено</i>, а не для того, чтобы <i>дать готовые инструкции и примеры для использования в реальных проектах</i>. Нам придется разобрать несколько тостеров, чтобы узнать, как они работают. Но в реальных проектах, когда это возможно, лучше используйте код, написанный людьми, которые умнее вас.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: singleton-конструктор</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/63</link>
      <pubDate>Sat, 21 May 2011 15:21:53 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/21/63/</guid>
      <description><![CDATA[Содержимое первой половины статьи будет, скорее всего, знакомым для многих (или почти всех?)...]]></description>
      <content:encoded><![CDATA[<p>Содержимое первой половины статьи будет, скорее всего, знакомым для многих (или почти всех?) читателей, но в конце статьи есть интересное упражнение.</p>
<p>Весьма практичным шаблоном для Interlocked-* функций является ленивая инициализация без блокировок, которая использует InterlockedCompareExchangePointerRelease. Да, наименование у этой функции очень длинное, но, в действительности, каждая часть этого наименования важна.</p>
<p><code>Widget *g_pwidCached;<br /><br />
Widget *GetSingletonWidget()<br />
{<br />
&nbsp;&nbsp;Widget *pwid = g_pwidCached;<br />
&nbsp;&nbsp;if (!pwid) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;pwid = new(nothrow) Widget();<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (pwid) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Widget *pwidOld = reinterpret_cast&lt;Widget*&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(InterlockedCompareExchangePointerRelease(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;reinterpret_cast&lt;PVOID&amp;&gt;(g_pwidCached),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pwid, NULL));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (pwidOld) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete pwid; // проиграл в гонке — теперь уничтожь ненужную копию<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pwid = pwidOld; // используй старый экземпляр<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return pwid;<br />
}</code></p>
<p>Здесь реализована двойная проверка, но блокировка при этом не используется. Вместо нее во время первого создания объекта мы просто-напросто разрешаем создавать этот объект всем потокам, дошедшим до выполнения этой операции. Если все пять потоков дойдут до этой точки одновременно, то пусть создают пять объектов, мы не будем им мешать. После того, как все они создадут по одному объекту, который, как они считают, единственный, они должны вызвать InterlockedCompareExchangePointerRelease для попытки обновления глобального указателя.</p>
<p>Части наименования функции InterlockedCompareExchangePointerRelease означают примерно следующее:</p>
<ul><li>Interlocked (блокирующий): операция выполняется атомарно. Это важно для того, чтобы избежать ситуации, когда два потока одновременно обновляют значение g_pwidCached.</li>
<li>Compare (сравнение): значение g_pwidCached будет сравниваться со значением NULL.</li>
<li>Exchange (обмен): если значения равны, то значение g_pwidCached будет установлено равным значению pwid. Эта операция, вкупе со сравнением, гарантирует, что значение g_pwidCached будет установлено только одним потоком.</li>
<li>Pointer (указатель): операции работают с указателями.</li>
<li>Release (освобождение): операция выполняется с использованием <a href="https://blogs.msdn.com/oldnewthing/archive/2008/10/03/8969397.aspx">семантики освобождения</a> (release semantics). Это очень важно, поскольку объект pwid, создаваемый нами, должен быть проинициализирован полностью, прежде чем ссылка на него станет доступной другим процессам.</li></ul>
<p>Эта техника приемлема, когда допускается создание объекта из множества потоков (и уничтожение копий в потоках, которые не успели создать первый экземпляр объекта). Если создание объекта требует больших затрат ресурсов или содержит нежелательные побочные эффекты, тогда такой алгоритм неприменим.</p>
<p>Дополнительная информация для ознакомления:</p>
<ul><li>Вспомогательные функции для <a href="https://msdn.microsoft.com/en-us/library/aa363808.aspx">создания объектов с однократной инициализацией</a> избавляют от написания всего этого вспомогательного кода. Они работают со всеми видами синхронизаций и барьерами памяти (memory barrier) и поддерживают как алгоритмы, в которых создание экземпляра допускается только один раз, так и алгоритмы, в которых создание экземпляров допускается несколько раз в разных потоках (как в рассмотренном выше примере).</li>
<li>В статье «<a href="https://www.bluebytesoftware.com/blog/2007/06/09/ALazyInitializationPrimitiveForNET.aspx">Примеры использования ленивой инициализации в .NET</a>» описано то же самое, но с использованием языка C#.</li></ul>
<p>Отлично, а теперь — обещанное интересное упражнение. Здесь описана реально существовавшая проблема, которую я помогал решить, но некоторые ее детали были изменены для того, чтобы пример был более понятным.</p>
<blockquote><p>У нас есть структура данных, которая управляет группой singleton-объектов. Предположим, что все они являются экземплярами структуры ITEMCONTROLLER и имеют 32-битный ключ-идентификатор. Требуется найти способы модификации этого кода, чтобы сделать его потокобезопасным. Существующий код выглядит примерно так (он представлен в виде псевдокода):</p>
<p><code>struct ITEMCONTROLLER;<br />
struct SINGLETONINFO {<br />
&nbsp;&nbsp;DWORD dwId;<br />
&nbsp;&nbsp;ITEMCONTROLLER *(*pfnCreateController)();<br />
};<br /><br />
class SingletonManager {<br />
public:<br />
&nbsp;&nbsp;// rgsi — массив, описывающий, как следует создавать объекты, он является статическим<br />
&nbsp;&nbsp;// csi принимает значения в интервале от 20 до 50.<br />
&nbsp;&nbsp;SingletonManager(const SINGLETONINFO *rgsi, UINT csi)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;: m_rgsi(rgsi), m_csi(csi),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_rgcs(NULL), m_ccs(0), m_ccsAlloc(0) { }<br />
&nbsp;&nbsp;~SingletonManager() { ... }<br />
&nbsp;&nbsp;ITEMCONTROLLER *Lookup(DWORD dwId);<br /><br />
private:<br />
&nbsp;&nbsp;struct CREATEDSINGLETON {<br />
&nbsp;&nbsp;&nbsp;&nbsp;DWORD dwId;<br />
&nbsp;&nbsp;&nbsp;&nbsp;ITEMCONTROLLER *pic;<br />
&nbsp;&nbsp;};<br /><br />
private:<br />
&nbsp;&nbsp;const SINGLETONINFO *m_rgsi;<br />
&nbsp;&nbsp;int m_csi;<br /><br />
&nbsp;&nbsp;// Массив, описывающий созданные объекты<br />
&nbsp;&nbsp;CREATEDSINGLETON *m_rgcs;<br />
&nbsp;&nbsp;int m_ccs;<br />
};<br /><br />
ITEMCONTROLLER *SingletonManager::Lookup(DWORD dwId)<br />
{<br />
&nbsp;&nbsp;int i;<br /><br />
&nbsp;&nbsp;// Проверяем, создан ли уже объект<br />
&nbsp;&nbsp;for (i = 0; i &lt; m_ccs; i++) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (m_rgcs[i].dwId == dwId)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return m_rgcs[i].pic;<br />
&nbsp;&nbsp;}<br /><br />
&nbsp;&nbsp;// Объект еще не создан — самое время создать его<br />
&nbsp;&nbsp;ITEMCONTROLLER *pic;<br />
&nbsp;&nbsp;for (i = 0; i &lt; m_rgsi; i++) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (m_rgsi[i].dwId == dwId) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pic = m_rgsi[i].pfnCreateController();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;if (pic == NULL) return;<br /><br />
&nbsp;&nbsp;... если m_rgcs == NULL, тогда нужно выделить под него память и обновить значение m_ccsAlloc<br />
&nbsp;&nbsp;... иначе увеличить его размер, выделив под него дополнительную память, и обновить значение m_ccsAlloc<br /><br />
&nbsp;&nbsp;// добавим созданный объект в наш массив, чтобы в следующий раз мы смогли его найти<br />
&nbsp;&nbsp;m_rgcs[m_ccs].dwId = dwId;<br />
&nbsp;&nbsp;m_rgcs[m_ccs].pic  = pic;<br />
&nbsp;&nbsp;m_ccs++;<br /><br />
&nbsp;&nbsp;return pic;<br />
}</code></p>
<p>Как видно из этого кода, класс SingletonManager принимает массив структур SINGLETONINFO, каждая из которых содержит идентификатор и функцию, которую нужно вызывать для создания объекта с данным идентификатором. Прежде чем просматривать этот массив мы проверяем, не был ли создан данный объект ранее, и если он был создан, мы просто возвращаем его. В противном случае мы создаем объект (используя функцию pfnCreateController) и добавляем его в наш массив созданных объектов.</p>
<p>Нашим изначальным намерением было создание критической секции вокруг всей функции Lookup, но, возможно, есть какой-то более умный способ? Может, использовать <a href="https://msdn.microsoft.com/en-us/library/aa904937.aspx">тонкую блокировку чтения/записи</a> (slim reader-writer lock)?</p></blockquote>
<p><strong>В продолжение темы</strong>: несмотря на то, что на платформе Windows <a href="https://blogs.msdn.com/oldnewthing/archive/2008/10/03/8969397.aspx">обычные версии interlocked-функций предполагают и семантику захвата (acquire semantics), и семантику освобождения (release semantics)</a>, в других платформах все может быть иначе. В частности, на платформе XBOX360 обычные версии interlocked-функций <u>не предполагают</u> ни семантику захвата, ни семантику освобождения. Какое поведение реализовано на платформе Windows CE, мне неизвестно.</p>
<p><strong>Дополнения к статье</strong>: раньше я знал, но потом забыл о том, что вариант шаблона «Одиночка» (singleton), описанный в этой статье (с использованием InterlockedCompareExchangePointer), <a href="https://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html">не является потокобезопасным на некоторых архитектурах процессоров</a>. Для того чтобы гарантировать, что при использовании косвенного доступа в операции извлечения указателя будет получено новое значение, а не одна из его закэшированных копий, после операции извлечения следует добавить вызов MemoryBarrier().
<p><code>Widget *GetSingletonWidget()<br />
{<br />
&nbsp;&nbsp;Widget *pwid = g_pwidCached;<br />
&nbsp;&nbsp;if (!pwid) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
&nbsp;&nbsp;} <b>else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;// Нужно убедиться, что разыменование pwid будет возвращать новые,<br />
&nbsp;&nbsp;&nbsp;&nbsp;// а не старые, закэшированные значения.<br />
&nbsp;&nbsp;&nbsp;&nbsp;MemoryBarrier();</b><br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;return pwid;<br />
}</code></p>
<p>Обсуждение алгоритмов без использования блокировок (возможно, с еще большим количеством ошибок!) будет продолжено в следующий раз.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгоритмы без использования блокировок: выбор уникального значения (разминка)</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/781</link>
      <pubDate>Thu, 19 May 2011 16:31:49 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/19/781/</guid>
      <description><![CDATA[Вот небольшой фрагмент кода, который предназначен для генерации уникального числа в процессе. А вот...]]></description>
      <content:encoded><![CDATA[<p>Вот небольшой фрагмент кода, который предназначен для генерации уникального числа в процессе. <a href="https://blogs.msdn.com/oldnewthing/archive/2004/09/15/229915.aspx">А вот статья для поднятия настроения</a>. Предупреждение: она может оказаться полезной, а может и не оказаться.</p>
<p><code>dwUniqueId = InterlockedCompareExchange(&amp;g_dwUniqueId,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId+1,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g_dwUniqueId);</code></p>
<p>Покритикуйте этот фрагмент кода.</p>]]></content:encoded>
    </item>
    <item>
      <title>Самая смешная шутка, которую я когда-либо рассказывал (по мнению трехлетних малышей)</title>
      <link>https://docs.microsoft.com/archive/blogs/ruoldnewthing/107-5</link>
      <pubDate>Thu, 19 May 2011 12:02:32 GMT</pubDate>
      <dc:creator><![CDATA[Dmitry Andreev]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/ruoldnewthing/2011/05/19/107-5/</guid>
      <description><![CDATA[Я протестировал эту шутку на нескольких детях в возрасте трех и четырех лет, и каждый раз она...]]></description>
      <content:encoded><![CDATA[<p>Я протестировал эту шутку на нескольких детях в возрасте трех и четырех лет, и каждый раз она срабатывала.</p>
<blockquote><p>Два котенка шли по улице, и один из них шлепнулся на попу!</p></blockquote>
<p>Я разработал эту шутку для одной из моих <a href="https://blogs.msdn.com/b/oldnewthing/archive/2009/04/27/9570320.aspx">любимых племянниц</a>. Она только начала учиться рассказывать шутки и попросила меня рассказать ей какую-нибудь шутку. Один из основных ключей к успеху в рассказывании шуток — понимание своей аудитории. Вы потеряете внимание трехлетнего малыша, если шутка будет длинной. Тонкий юмор и чересчур умные шутки тоже не сработают. Шутка должна быть короткой и понятной. Моя племянница считает котят милыми, а когда кто-то падает на попу, это смешно, поэтому я подумал, дай-ка я объединю эти два момента.</p>
<p>Когда я рассказал ей эту шутку, она упала на пол и зашлась в безудержном смехе. Я нашел шутку-победителя.</p>]]></content:encoded>
    </item>
  </channel>
</rss>