Поиск

Планирование потоков

При переключении процессора по окончании выделенного потоку кванта времени, процесс выбора следующего потока, предназначенного для исполнения, далеко не произволен. У каждого потока есть приоритет, указывающий процессору, как должно планироваться выполнение этого потока по отношению к другим потокам системы. Для потоков, создаваемых в период выполнения, уровень приоритета по умолчанию равен Normal. Потоки, созданные не в период выполнения, сохраняют свой исходный приоритет. Для просмотра и установки этого значения служит свойство Thread.Priority. Установщик свойства Thread.Priority принимает аргумент типа Thread. ThreadPriority, который представляет собой enum, определяющий значения Highest, AboveNormal, Normal, BelowNormal и Lowest.

Чтобы проиллюстрировать, как приоритеты могут влиять даже на простейший код, взгляните на следующий пример, где один рабочий поток считает от 1 до 10, а другой — от 11 до 20. Обратите внимание на вложенный цикл в каждом методе WorkerThread. Циклы используются здесь для представления действий, которые выполняло бы настоящее приложение. Поскольку на самом деле эти методы ничего не делают, то без циклов каждый поток завершил бы свою работу в течение первого же своего кванта времени!

using System;
using System.Threading;
class ThreadSchedulelApp <
public static void WorkerThreadMethodK)
<
Console.WriteLine("Worker thread started");
Console.WriteLine
("Worker thread - counting slowly from 1 to 10"); for (int 1=1; i < 11; i++)
{
for (int j = 0; j < 100; j++) {
Console.Write(".");
// Код, который имитирует работу, подлежащую
// выполнению.
int a;
а = 15; }
Console.Write("{0}", i); }
Console.WriteLine("Worker thread finished"); }
public static void WorkerThreadMethod2() {
Console.WriteLine("Worker thread started");
Console.WriteLine
("Worker thread - counting slowly from 11 to 20"); for (int i = 11; i < 20; i++) <
for (int j = 0; j < 100; j++) {
Console.Write(".");
// Код, который имитирует работу, подлежащую // выполнению, int a; а = 15; }
Console.Write("{0}", i); >
Console.WriteLine("Worker thread finished"); }
public static void Main() {
ThreadStart workerl = new ThreadStart(WorkerThreadMethodl);
ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
Console.WriteLine("Main - Creating worker threads");
Thread t1 = new Thread(workerl); Thread t2 = new Thread(worker2);
t1.Start(); t2.Start(); } >

Запустив это приложение, вы получите следующее (для ясности я урезал выходную информацию — убрал большую часть точек):

Main - Creating worker threads
Worker thread started
Worker thread started
Worker thread - counting slowly from 1 to 10
Worker thread - counting slowly from 11 to 20
......1......11......2......12......3......13

Как видите, оба потока получают одинаковое время для "игры" с процессором. А теперь изменим свойство Priority каждого потока, как это сделано в следующем коде. Я дал наибольший приоритет из возможных первому потоку и наименьший — второму. Результаты, как вы увидите, будут во многом отличаться от предыдущих.

using System;
using System.Threading;
class ThreadSchedule2App {
public static void WorkerThreadMethodK)
{
Console.WriteLine("Worker thread started");
Console.WriteLine
("Worker thread - counting slowly from 1 to 10"); for (int 1=1; i < 11; i++) <
for (int j = 0; j < 100; j++) {
Console.Write(".");
// Код, который имитирует работу, подлежащую
// выполнению.
int a;
a = 15; }
Console.Write("{0}", i); }
Console.WriteLine("Worker thread finished"); }
public static void WorkerThreadMethod2() {
Console.WriteLine("Worker thread started");
Console.WriteLine
("Worker thread - counting slowly from 11 to 20");
for (int i = 11; i < 20; i++) {
for (int j = 0; J < 100; J++) {
Console.Write(".");
// Код, который имитирует работу, подлежащую // выполнению, int a; а = 15; }
Console.Write("{0}", i); }
Console.WriteLine("Worker thread finished"); }
public static void Main() {
ThreadStart workerl = new ThreadStart(WorkerThreadMethodl);
ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
Console.WriteLine("Main - Creating worker threads");
Thread t1 = new Thread(workerl); Thread t2 = new Thread(worker2);
t1.Priority = ThreadPriority.Highest; t2.Priority = ThreadPriority.Lowest;
t1.Start(); t2.Start(); } }

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

Main - Creating worker threads
Worker thread started
Worker thread started
Worker thread - counting slowly from 1 to 10
Worker thread - counting slowly from 11 to 20
......1......2......3......4......5......6......7......8......Э......
10......11......12......13......14......15......16......17......
18......19......20

Помните, что когда вы указываете процессору приоритет, который вы хотите установить для данного потока, то его значение в конечном счете используется ОС как часть алгоритма планирования распределения процессорного времени. В .NET этот алгоритм основан на уровнях приоритета, которые вы только что использовали (с помощью свойства Thread.Priority), а также на классе приоритета (priority class) процесса и значении динамического повышения (dynamic boost) приоритета. С помощью всех этих значений создается численное значение (для процессоров Intel — 0—31), представляющее приоритет потока. Потоки с наибольшим значением являются самыми высокоприоритетными.

И последнее замечание о планировании потоков: будьте осторожны. Допустим, у вас есть приложение с графическим интерфейсом и пара рабочих потоков, асинхронно выполняющих второстепенную работу. Если установить слишком высокий приоритет рабочих потоков, работа пользовательского интерфейса может замедлиться, поскольку главный поток, в котором выполняется приложение с графическим интерфейсом, получает меньше циклов процессора. Если у вас нет особой причины планировать поток с высоким приоритетом, лучше всего оставить у потока приоритет по умолчанию — Normal.

ПРИМЕЧАНИЕ Несколько потоков с одинаковым приоритетом получают равное количество процессорного времени. Это называется циклическим планированием (round robin scheduling).