Составители:
Рубрика:
Обычно объекты не имеют сопоставленной с ними 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
- …
- следующая ›
- последняя »