Новый пул потоков работает немного другим образом. Для каждого рабочего потока в пуле существует своя локальная очередь. Задача, которая попала в локальную очередь, может породить дочернюю и они размещаются в эту же локальную очередь. Также задачи выполняются в локальной очереди в LIFO (last-in-first-out) порядке, в отличие от пула в 3.5, где задачи из глобальной очереди выполняются рабочими потоками в FIFO (first-in-first-out) порядке. Так как только рабочий поток разгребает свою собственную кучу, вернее имеет доступ к ее началу (head), то на уровне локальной очереди не требуется никаких синхронизаций. Поэтому добавление и извлечение задачи из этой очереди происходят очень быстро. Побочным эффектом такого выполнения является то, что очередь разгребается в обратном порядке. Хотя делать какие-то допущения в программе о порядке выполнения задач в очереди нельзя – так как пул потоков своеобразный черный ящик.
Когда рабочий поток видит что делать ему больше нечего, то есть его локальная очередь пустая, он пытается своровать задачу у другого рабочего потока. Но ворует он элементы с хвоста чужой локальной очереди. Такое действие уже требует синхронизации. И производительность немного снизится. Если же все локальные очереди пустые, то рабочий поток попытается забрать задачу из глобальной очереди в FIFO порядке. Если же глобальная очередь окажется пустой, тогда рабочий поток «заснет» пока не появится работа для него. Если рабочий поток проспал слишком много времени и работы так и не получил, он проснется и уничтожится, освободив занимаемые ресурсы.
Почему глобальная очередь на картинке lock-free, если все равно нужна синхронизация для доступа к ней рабочих потоков из пула?. Потому что она действительно lock-free, теперь для синхронизации используются не lock-и (Monitor), а некий аналог interlocked операций и это позволило увеличить быстродействие очереди.
Источник