Параллельные вычисления: класс MultiThread

Современные персональные компьютеры стали многопроцессорными (и/или многоядерными), это — факт! Пока еще не вышел .NET Framework 4.0 и C# 4.0, в котором заявлена поддержка многопоточности, и поэтому программисты, как правило, никак не используют параллельность в своих вычислительных программах. Характерно, что активно вычисляющая программа грузит процессор лишь на 50% (на 2-ядерной машине) или на 25% (на 4-ядерной машине), а ведь всегда требуется считать быстрее и грузить процессор по полной.

Например, вы производите какую-либо обработку данных в цикле, выполнение которого занимает на 4-ядерной машине в среднем 20 секунд. Для конечного пользователя иногда это — целая вечность. Согласитесь, пользователь не стал бы жаловаться на тормоза программы, если бы тот же цикл выполнялся за 5 секунд.
Для этого может быть полезен следующий класс:

C#:Select code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System.Threading;

namespace UsingsRU.Utils
{
    /// <summary>
    /// Класс предназначен для параллельного выполнения вычислений.
    /// Эффективен в случае многоядерных процессоров (включая процессора с HT).
    /// <remarks>В вычислительной функции для потоков: при использовании локальных переменных вызывающего метода вместо внутренних полей класса, содержащего этот метод - резкое снижение эффективности.</remarks>
    /// </summary>
    public class MultiThread
    {
        /// <summary>
        /// Вычислительная функция для потока threadIndex
        /// </summary>
        /// <param name="threadIndex">Порядковый номер потока</param>
        /// <param name="threadCount">Количество созданных потоков</param>
        public delegate void ThreadHandler(int threadIndex, int threadCount);


        static MultiThread()
        {
            int maxIOThreads;

            ThreadPool.GetMinThreads(out minThreads, out maxIOThreads);
        }

        /// <summary>
        /// Создать класс с максимально допустимым количеством потоков MaxThreadsCount
        /// </summary>
        /// <param name="MaxThreadsCount">Максимально допустимое количество потоков</param>
        public MultiThread(int MaxThreadsCount)
        {
            threadCount = MaxThreadsCount < minThreads ? MaxThreadsCount : minThreads;
        }

        /// <summary>
        /// Создать класс с максимально допустимым количеством потоков MaxThreadsCount
        /// </summary>
        /// <param name="MinThreadsCount">Миниимально допустимое количество потоков</param>
        /// <param name="MaxThreadsCount">Максимально допустимое количество потоков</param>
        public MultiThread(int MinThreadsCount, int MaxThreadsCount)
        {
            if (MinThreadsCount > MaxThreadsCount)
                MinThreadsCount = 1;

            threadCount = MaxThreadsCount < minThreads ? MaxThreadsCount : minThreads;

            if (MinThreadsCount > threadCount)
                threadCount = MinThreadsCount;
        }

        /// <summary>
        /// Создать класс с максимально допустимым количеством потоков MaxThreadsCount и дождаться окончания выполнения вычислительной функции для потоков handler
        /// </summary>
        /// <param name="MaxThreadsCount">Максимально допустимое количество потоков</param>
        /// <param name="handler">Вычислительная функция для потоков</param>
        public MultiThread(int MaxThreadsCount, ThreadHandler handler)
            : this(MaxThreadsCount)
        {
            Run(handler);
        }

        /// <summary>
        /// Создать класс с максимально допустимым количеством потоков MaxThreadsCount и дождаться окончания выполнения вычислительной функции для потоков handler
        /// </summary>
        /// <param name="MinThreadsCount">Миниимально допустимое количество потоков</param>
        /// <param name="MaxThreadsCount">Максимально допустимое количество потоков</param>
        /// <param name="handler">Вычислительная функция для потоков</param>
        public MultiThread(int MinThreadsCount, int MaxThreadsCount, ThreadHandler handler)
            : this(MinThreadsCount, MaxThreadsCount)
        {
            Run(handler);
        }

        /// <summary>
        /// Запустить вычисления в функции для потоков handler и дождаться окончания выполнения
        /// </summary>
        /// <param name="MaxThreadsCount">Максимально допустимое количество потоков</param>
        /// <param name="handler">Вычислительная функция для потоков</param>
        public static void RunThreads(int MaxThreadsCount, ThreadHandler handler)
        {
            new MultiThread(MaxThreadsCount, handler);
        }

        /// <summary>
        /// Запустить вычисления в функции для потоков handler и дождаться окончания выполнения
        /// </summary>
        /// <param name="handler">Вычислительная функция для потоков</param>
        public void Run(ThreadHandler handler)
        {
            Thread[] t = new Thread[threadCount];

            for (int j = 0; j < threadCount; j++)
            {
                t[j] = new Thread(delegate(object tn)
                  {
                      handler((int)tn, threadCount);
                  });
                t[j].Start(j);
            }

            for (int j = 0; j < threadCount; j++)
                t[j].Join();
        }

        /// <summary>
        /// Количество созданных потоков
        /// </summary>
        public int ThreadCount
        {
            get { return threadCount; }
        }

        private readonly int threadCount;
        private readonly static int minThreads;
    }
}

Данный класс позволяет производить вычисления в несколько потоков. При инициализации, класс сам определит оптимальное количество потоков для той машины, на которой он выполняется.
Для многих, хорошо распараллеливаемых задач, его эффективность близка к 100%.

Рассмотрим на примере:

C#:Select code
int total_min = 0;
int total_len = 10000000;
double[] A = new double[total_len];

MultiThread.RunThreads(total_len,
(threadIndex, threadCount) => 
{
    int len = total_len / threadCount;
    int lowBound = total_min + len * threadIndex;
    int hiBound = (threadIndex == threadCount - 1) ? total_len : lowBound + len;
    for (int i = lowBound; i < hiBound; i++)
        A[i] = 0.01 * i * i;
});

Данный код будет выполнен примерно в 2 раза быстрее на 2-ядерном компьютере, по сравнению с классическим кодом:

C#:Select code
int total_min = 0;
int total_len = 10000000;
double[] A = new double[total_len];

for (int i = total_min; i < total_len; i++)
    A[i] = 0.01 * i * i;

Убедитесь сами. Удачи!

Обсуждение на форуме: http://usings.ru/forum/viewtopic.php?t=9

Один ответ Оставить комментарий

  1. #1mogikanin @ 2009-9-17 22:00 Ответ

    Спасибо!!! Используем этот классик повсеместно в проекте. Много раз он нас выручал при решении довольно сложных задач. Респект автору.

Ответить

(Ctrl + Enter)