Составители:
Рубрика:
Обычно объекты не имеют сопоставленной с ними SyncBlock запи-
си, однако она автоматически выделяется при первом использовании
монитора.
Класс Monitor, определенный в пространстве имен System.Threading,
предлагает несколько статических методов для работы с записями синхро-
низации. Методы Enter и Exit являются наиболее применяемыми и соот-
ветствуют функциям EnterCriticalSection и LeaveCriticalSection опера-
ционной системы. Аналогично критическим секциям Win32 API, монито-
ры могут использоваться одним потоком рекурсивно. Еще несколько ме-
тодов класса Monitor – Wait, Pulse и PulseAll – позволяют при необходи-
мости временно разрешить доступ к объекту другому потоку, ожидающему
его освобождения, не покидая критической секции.
Продолжим рассмотрение примера с многопоточным умножением
матриц. Помимо уже рассмотренной проблемы с назначением полос, в
процедуре потока есть еще одно некорректное место – прибавление нако-
плением к элементу результирующей матрицы произведения двух элемен-
тов исходной матрицы:
public static void ThreadProc()
{
int i,j,k, from, to;
from = (Interlocked.Increment(ref m_stripused)–1)
* m_stripsize;
to = from + m_stripsize;
if ( to > m_size ) to = m_size;
for ( i = 0; i < m_size; i++ ) {
for ( j = 0; j < m_size; j++ ) {
for ( k = from; k < to; k++ )
m_C[i,j] += m_A[i,k] * m_B[k,j];
}
}
}
Так как эта операция выполняется не атомарно, то вполне может быть
так, что один поток считывает значение m_C[i,j], прибавляет к нему вели-
чину m_A[i,k] * m_B[k,j] и, прежде чем успевает записать в m_C[i,j] резуль-
тат сложения, прерывается другим потоком. Второй поток успевает изме-
нить величину m_C[i,j], потом первый снова пробуждается и записывает
значение, вычисленное для предыдущего состояния элемента m_C[i,j], – то
есть некорректную величину. Собственно говоря, именно эта ситуация и
приводит к ошибкам, которые можно наблюдать в исходном примере.
Ситуацию можно исправить, используя синхронизацию при доступе
к элементу m_C[i,j] с помощью мониторов:
Разработка параллельных приложений для ОС Windows
263
Для этого с каждым объектом ссылочного типа сопоставляется запись
SyncBlock, являющаяся, по сути, аналогом структуры CRITICAL_SECTION в
Win32 API. Добавление такой записи к каждому объекту в управляемой ку-
че чересчур накладно, особенно если учесть, что используются они отно-
сительно редко. Поэтому все записи SyncBlock выносятся в отдельный
кэш, а в информацию об объекте включается ссылка на запись кэша
(см. рис. 7.2). Такой прием позволяет, с одной стороны, содержать кэш
синхронизирующих записей минимального размера, а с другой – любому
объекту при необходимости можно сопоставить запись.
Рис. 7.2. Использование кэша SyncBlock записей объектами управля-
емой кучи
262
CIL и системное программирование в Microsoft .NET
Управляемая куча
Объект i
Информация об объекте
...
Данные объекта
Индекс в SyncBlock кэше
Внутренние данные CLR
...
Кэш SyncBlock записей
SyncBlock[0] (пусто)
SyncBlock[1] (используется)
SyncBlock[2] (пусто)
SyncBlock[3] (пусто)
SyncBlock[4] (используется)
SyncBlock[5] (пусто)
...
Объект k
Информация об объекте
...
Данные объекта
...
Индекс в SyncBlock кэше
Объект j
Информация об объекте
...
Данные объекта
...
Индекс в SyncBlock (пусто)
Информация о типах, таблицы
указателей на методы и т.п.
262 CIL и системное программирование в Microsoft .NET Разработка параллельных приложений для ОС Windows 263
Для этого с каждым объектом ссылочного типа сопоставляется запись Обычно объекты не имеют сопоставленной с ними SyncBlock запи-
SyncBlock, являющаяся, по сути, аналогом структуры CRITICAL_SECTION в си, однако она автоматически выделяется при первом использовании
Win32 API. Добавление такой записи к каждому объекту в управляемой ку- монитора.
че чересчур накладно, особенно если учесть, что используются они отно- Класс Monitor, определенный в пространстве имен System.Threading,
сительно редко. Поэтому все записи SyncBlock выносятся в отдельный предлагает несколько статических методов для работы с записями синхро-
кэш, а в информацию об объекте включается ссылка на запись кэша низации. Методы Enter и Exit являются наиболее применяемыми и соот-
(см. рис. 7.2). Такой прием позволяет, с одной стороны, содержать кэш ветствуют функциям EnterCriticalSection и LeaveCriticalSection опера-
синхронизирующих записей минимального размера, а с другой – любому ционной системы. Аналогично критическим секциям Win32 API, монито-
объекту при необходимости можно сопоставить запись. ры могут использоваться одним потоком рекурсивно. Еще несколько ме-
тодов класса Monitor – Wait, Pulse и PulseAll – позволяют при необходи-
Управляемая куча Внутренние данные CLR мости временно разрешить доступ к объекту другому потоку, ожидающему
его освобождения, не покидая критической секции.
Объект i Информация о типах, таблицы Продолжим рассмотрение примера с многопоточным умножением
Информация об объекте указателей на методы и т.п. матриц. Помимо уже рассмотренной проблемы с назначением полос, в
процедуре потока есть еще одно некорректное место – прибавление нако-
Индекс в SyncBlock кэше плением к элементу результирующей матрицы произведения двух элемен-
тов исходной матрицы:
...
Данные объекта public static void ThreadProc()
{
int i,j,k, from, to;
Объект j Кэш SyncBlock записей from = (Interlocked.Increment(ref m_stripused)–1)
Информация об объекте SyncBlock[0] (пусто) * m_stripsize;
SyncBlock[1] (используется) to = from + m_stripsize;
Индекс в SyncBlock (пусто) SyncBlock[2] (пусто) if ( to > m_size ) to = m_size;
SyncBlock[3] (пусто) for ( i = 0; i < m_size; i++ ) {
... SyncBlock[4] (используется) for ( j = 0; j < m_size; j++ ) {
Данные объекта SyncBlock[5] (пусто) for ( k = from; k < to; k++ )
... ... m_C[i,j] += m_A[i,k] * m_B[k,j];
}
}
Объект k
}
Информация об объекте Так как эта операция выполняется не атомарно, то вполне может быть
так, что один поток считывает значение m_C[i,j], прибавляет к нему вели-
Индекс в SyncBlock кэше
... чину m_A[i,k] * m_B[k,j] и, прежде чем успевает записать в m_C[i,j] резуль-
... тат сложения, прерывается другим потоком. Второй поток успевает изме-
Данные объекта нить величину m_C[i,j], потом первый снова пробуждается и записывает
... значение, вычисленное для предыдущего состояния элемента m_C[i,j], – то
есть некорректную величину. Собственно говоря, именно эта ситуация и
приводит к ошибкам, которые можно наблюдать в исходном примере.
Рис. 7.2. Использование кэша SyncBlock записей объектами управля- Ситуацию можно исправить, используя синхронизацию при доступе
емой кучи к элементу m_C[i,j] с помощью мониторов:
Страницы
- « первая
- ‹ предыдущая
- …
- 136
- 137
- 138
- 139
- 140
- …
- следующая ›
- последняя »
