Тупик с блокировкой вокруг и внутри Parallel.ForEach

Не могли бы вы объяснить, почему этот код взаимоблокируется?

int[] testlist = new int[ ] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
lock ( testlock ) {
    Parallel.ForEach( testlist, new ParallelOptions( ) { MaxDegreeOfParallelism = 90 }, ( int i ) => {
        Console.WriteLine( "hi there before " + i + " " + Monitor.IsEntered( testlock ) );
        lock ( testlock ) {
            Console.WriteLine( "hi there inner " + i + " " + Monitor.IsEntered( testlock ) );
        }
        Console.WriteLine( "hi there after " + i + " " + Monitor.IsEntered( testlock ) );
    } );
}

Конечно, без окружающего замка этот код не блокируется.

Редактировать:

Спасибо за объяснение. Типичный вывод:

hi there before 3 True
hi there inner 3 True
hi there after 3 True
hi there before 4 True
hi there inner 4 True
hi there after 4 True
hi there before 5 True
hi there inner 5 True
hi there after 5 True
hi there before 6 True
hi there inner 6 True
hi there after 6 True
hi there before 7 True
hi there inner 7 True
hi there after 7 True
hi there before 8 True
hi there inner 8 True
hi there after 8 True
hi there before 9 True
hi there inner 9 True
hi there after 9 True
hi there before 10 True
hi there inner 10 True
hi there after 10 True

На самом деле типичное выполнение включает в себя два потока на моей машине: один заблокирован в ожидании блокировки («1»), а другой выполняет другие итерации (от 3 до 10, обратите внимание на линейность вывода). Поток «1» ждет вечно. Теперь понятно, спасибо!


person sgatto    schedule 15.09.2014    source источник
comment
Предположим, что основным потоком является thread1. Thread1 блокирует testLock. Каждый другой поток (отличный от потока 1!), созданный Parallel.ForEach, также хочет заблокировать testLock, но он все еще заблокирован потоком 1. Thread1 ожидает завершения всех Parallel-потоков = взаимоблокировка.   -  person    schedule 15.09.2014
comment
Единственный способ, которым этот код мог бы когда-либо работать, — это если Parallel решил запустить все итерации в текущем основном потоке (тот, который вызывает Parallel.ForEach). Насколько я знаю, он может сделать это, если у вас есть только одно ядро... во всех остальных случаях: да, он заблокируется... потому что вы записали тупик.   -  person Marc Gravell    schedule 15.09.2014


Ответы (2)


Это не может работать, потому что вы определенно создаете здесь тупик:

Внешний lock(testlock) блокирует testlock, а затем Parallel.ForEach начинает обрабатывать ваш testlist в нескольких потоках. Теперь эти потоки попадают во внутренний lock(testlock), но поскольку он все еще заблокирован внешним в своем потоке, он не может его снова заблокировать => внешний lock не освобождается, пока обработка не будет завершена, но обработка не может завершиться, пока не будет завершена обработка. внешние lock релизы -> тупик...

Вы можете легко проверить это, используя MaxDegreeOfParallelism = 1: Затем все выполняется в основном потоке, и каскадная блокировка не блокируется, поскольку она может снова заблокироваться, потому что это тот же поток.

Что вы пытаетесь сделать - можем ли мы помочь, если вы объясните?

ПРИМЕЧАНИЕ.
Если в вашей системе нет процессоров 90!, использование MaxDegreeOfParallelism = 90 НЕ повышает производительность — рекомендуется, чтобы это число было меньше или равно вашему Количество ЦП/ядер (вы можете получить это через Environment.ProcessorCount ).

person Christoph Fink    schedule 15.09.2014
comment
Я установил это на 90, просто чтобы попробовать поиграть с разными значениями (но да, было бы неплохо иметь что-то с 90 процессором :)) - person sgatto; 15.09.2014

Вы заблокировали testLock перед запуском Parallel.ForEach (1-я блокировка). Внутри вашего Parallel.ForEach вы блокируете тот же объект. ForEach использует другой поток в какой-то момент, кроме того, который взял 1-ю блокировку. Этот поток будет ждать, пока блокировка не будет доступна, однако она никогда не будет доступна, потому что 1-я блокировка не будет снята до тех пор, пока ForEach не будет завершен.

Поскольку ForEach не завершится, пока все потоки не будут завершены, вы в конечном итоге застряли.

person vcsjones    schedule 15.09.2014
comment
Я думаю, стоит упомянуть, что основной поток ожидает завершения всех потоков, созданных Parallel. - person ; 15.09.2014
comment
@pwas На самом деле это не совсем точно. Основной поток на самом деле может быть одним из потоков, используемых Parallel.ForEach, поэтому сказать, что этот поток просто сидит и ждет, неверно, но я думаю, что понимаю, к чему вы клоните. - person vcsjones; 15.09.2014
comment
@pwas Ах, хорошо, вы сказали, что темы созданные, так что я думаю, что теперь я понял, о чем вы говорите. - person vcsjones; 15.09.2014