Попытка понять многопоточность в C # - PullRequest
4 голосов
/ 06 июля 2011

Я пытаюсь понять основы многопоточности, поэтому я создал небольшую программу, которая подняла несколько вопросов, и я буду благодарен за любую помощь:)

Вот небольшая программа:

class Program
{
    public static int count;
    public static int max;
    static void Main(string[] args)
    {
        int t = 0;
        DateTime Result;
        Console.WriteLine("Enter Max Number : ");
        max = int.Parse(Console.ReadLine());
        Console.WriteLine("Enter Thread Number : ");
        t = int.Parse(Console.ReadLine());

        count = 0;

        Result = DateTime.Now;
        List<Thread> MyThreads = new List<Thread>();
        for (int i = 1; i < 31; i++)
        {
            Thread Temp = new Thread(print);
            Temp.Name = i.ToString();
            MyThreads.Add(Temp);
        }

        foreach (Thread th in MyThreads)
            th.Start();

        while (count < max)
        {
        }

        Console.WriteLine("Finish , Took : " + (DateTime.Now - Result).ToString() + " With : " + t + " Threads.");
        Console.ReadLine();
    }

    public static void print()
    {
        while (count < max)
        {
            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

Я проверил это с некоторыми тестами:

Я сделал максимальное число 100, и, похоже, самое быстрое время выполнения - с 2 потоками, что на 80% быстрее, чем время с 10 потоками.

Вопросы:

1) Потоки 4-10 не печатаются ни разу, как это может быть?

2) Разве больше потоков не должно быть быстрее?

Я набрал максимальное количество 10000 и отключил печать.

При такой конфигурации 5 потоков кажутся самыми быстрыми.

Почему происходит изменение по сравнению с первой проверкой?

А также в этой конфигурации (с печатью) все нити печатаются несколько раз. Почему это отличается от первого запуска, где напечатано всего несколько нитей?

Есть ли способ заставить все нити печатать одну за другой? В строке или что-то в этом роде?

Большое спасибо за помощь:)

Ответы [ 3 ]

7 голосов
/ 06 июля 2011

Ваш код, безусловно, является первым шагом в мир многопоточности, и вы только что испытали первую (из многих) головную боль!

Для начала, static может позволить вам передавать переменную между потоками, но это не делает поточно-безопасным способом. Это означает, что ваше count < max выражение и count++ не гарантированно будут в актуальном состоянии или эффективной защитой между потоками. Посмотрите на вывод вашей программы, когда max равен только 10 (t установлен на 4, на моей 8-процессорной рабочей станции):

T0 - 0
T0 - 1
T0 - 2
T0 - 3
T1 - 0 // wait T1 got count = 0 too!
T2 - 1 // and T2 got count = 1 too!
T2 - 6
T2 - 7
T2 - 8
T2 - 9
T0 - 4
T3 - 1 // and T3 got count = 1 too!
T1 - 5

На ваш вопрос о печати каждой нити одна за другой я предполагаю, что вы пытаетесь координировать доступ к count. Вы можете сделать это с помощью примитивов синхронизации (таких как оператор lock в C #). Вот наивное изменение вашего кода, которое обеспечит выполнение только max приращений:

static object countLock = new object();

public static void printWithLock()
{
    // loop forever
    while(true)
    {
        // protect access to count using a static object
        // now only 1 thread can use 'count' at a time
        lock (countLock)
        {
            if (count >= max) return;

            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

Эта простая модификация делает вашу программу логически правильной, но также медленной . Образец теперь демонстрирует новую проблему: блокировка конкуренции . Каждый поток теперь претендует на доступ к countLock. Мы сделали поток нашей программы безопасным, но без каких-либо преимуществ параллелизма!

Потоки и параллелизм не так-то просто получить правильно, но, к счастью, последние версии .Net поставляются с Task Parallel Library (TPL) и Parallel LINQ (PLINQ) .

Прелесть библиотеки в том, насколько легко было бы преобразовать ваш текущий код:

var sw = new Stopwatch();

sw.Start();
Enumerable.Range(0, max)
          .AsParallel()
          .ForAll(number =>
               Console.WriteLine("T{0}: {1}",
                                 Thread.CurrentThread.ManagedThreadId,
                                 number));

Console.WriteLine("{0} ms elapsed", sw.ElapsedMilliseconds);

// Sample output from max = 10
// 
// T9: 3
// T9: 4
// T9: 5
// T9: 6
// T9: 7
// T9: 8
// T9: 9
// T8: 1
// T7: 2
// T1: 0
// 30 ms elapsed

Вывод выше является интересной иллюстрацией того, почему многопоточность дает «неожиданные результаты» для новых пользователей. Когда потоки выполняются параллельно, они могут завершать фрагменты кода в разные моменты времени или один поток может быть быстрее другого. Вы никогда не знаете с многопоточностью!

0 голосов
/ 06 июля 2011

Осторожно смотри:

    t = int.Parse(Console.ReadLine());

    count = 0;

    Result = DateTime.Now;
    List<Thread> MyThreads = new List<Thread>();
    for (int i = 1; i < 31; i++)
    {
        Thread Temp = new Thread(print);
        Temp.Name = i.ToString();
        MyThreads.Add(Temp);
    }

Я думаю, вы пропустили переменную t (i <31). </p>

Вы должны прочитать много книг по параллельному и многопоточному программированию, прежде чем писать код, потому что язык программирования - это всего лишь инструмент. Удачи!

0 голосов
/ 06 июля 2011

Ваша функция печати далека от поточной безопасности, поэтому 4-10 не печатает.Все потоки имеют одинаковые переменные max и count.

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

Кроме того, когда вы создаете много потоков, система должна выделять новые.В большинстве случаев вместо этого рекомендуется использовать задачи, так как они извлекаются из пула потоков, управляемых системой.И, таким образом, не обязательно должны быть распределены.Создание совершенно новой темы довольно дорого.

В любом случае, посмотрите здесь: http://msdn.microsoft.com/en-us/library/aa645740(VS.71).aspx

...