Почему в .NET встроен Hashtable для Thread.Sleep(1)?

Недавно я читал реализацию .NET Hashtable и обнаружил фрагмент кода, который я не понимаю. Часть кода:

int num3 = 0;
int num4;
do
{
   num4 = this.version;
   bucket = bucketArray[index];
   if (++num3 % 8 == 0)
     Thread.Sleep(1);
}
while (this.isWriterInProgress || num4 != this.version);

Весь код находится в пределах public virtual object this[object key] от System.Collections.Hashtable (mscorlib Version = 4.0.0.0).

Возникает вопрос:

В чем причина наличия Thread.Sleep(1)?

Ответ 1

Sleep (1) является документированным способом в Windows, чтобы получить процессор и разрешить запуск других потоков. Этот код можно найти в справочном источнике с комментариями:

   // Our memory model guarantee if we pick up the change in bucket from another processor,
   // we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader.
   //
   int spinCount = 0;
   do {
       // this is violate read, following memory accesses can not be moved ahead of it.
       currentversion = version;
       b = lbuckets[bucketNumber];

       // The contention between reader and writer shouldn't happen frequently.
       // But just in case this will burn CPU, yield the control of CPU if we spinned a few times.
       // 8 is just a random number I pick.
       if( (++spinCount) % 8 == 0 ) {
           Thread.Sleep(1);   // 1 means we are yeilding control to all threads, including low-priority ones.
       }
   } while ( isWriterInProgress || (currentversion != version) );

Переменная isWriterInProgress - это volatile bool. У автора были некоторые проблемы с английским "нарушение чтения" - это "неустойчивое чтение". Основная идея заключается в том, чтобы попытаться избежать уступки, переключатели контекста потока очень дороги, и некоторые надеются, что писатель быстро справится. Если это не отключается, то явно дается возможность избежать сжигания процессора. Сегодня это, вероятно, было написано Spinlock, но Hashtable очень старый. Как и предположения о модели памяти.

Ответ 2

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

Тем не менее, похоже, что он пытается что-то обновить в Hashtable, либо в памяти, либо на диске, и делает бесконечный цикл, ожидая его завершения (как видно из проверки isWriterInProgress).

Если это одноядерный процессор, он может запускать только один поток за раз. Переход в непрерывный цикл, подобный этому, может легко означать, что у другого потока нет возможности запуска, но Thread.Sleep(1) дает процессору шанс дать время писателю. Без ожидания нить писателя никогда не сможет запустить и никогда не завершится.

Ответ 3

Я не читал источник, но он выглядит как незаблокированная concurrency вещь. Вы пытаетесь читать из хеш-таблицы, но кто-то может писать на нее, поэтому вы ждете, пока isWriterInProgress не будет отменен, и прочитанная версия не изменилась.

Это не объясняет, почему, например, мы всегда ждем хотя бы один раз. EDIT: это потому, что мы этого не делаем, спасибо @Maciej за указание на это. Когда нет споров, мы начинаем немедленно. Я не знаю, почему 8 - это магическое число, а не, например, 4 или 16.