Многопоточные приложения в Visual Studio.NET. Разработка приложения, демонстрирующего многопоточность для одного процессора

Автор работы: Пользователь скрыл имя, 09 Декабря 2013 в 22:08, курсовая работа

Описание работы


Современные операционные системы (OC) нацелены на наиболее эффективное использование ресурсов компьютера. По большей части эффективность достигается за счет разделения ресурсов компьютера между несколькими процессами. Многопоточность является естественным продолжением многозадачности, точно также как виртуальные машины, позволяющие запускать несколько ОС на одном компьютере, представляют собой логическое развитие концепции разделения ресурсов. В многопоточном приложении одновременно работает несколько потоков. Иногда вместо термина "поток" используется термин "нить". Потоки – это независимые друг от друга задачи, выполняемые в контексте процесса. Поток использует код и данные родительского процесса, но имеет свой собственный уникальный стек и состояние процессора, включающее указатель команд.

Содержание работы


РЕФЕРАТ…………………………………………………….…………
4
СПИСОК СОКРАЩЕНИЙ…………………………………………….
5
СОДЕРЖАНИЕ…………………………………………………………
6
ВВЕДЕНИЕ…………………………………………………………….
7
1 КОНЦЕПЦИЯ МНОГОПОТОЧНОСТИ……………………………
9
2 БИБЛИОТЕКА ПАРАЛЛЕЛЬНЫХ ЗАДАЧ (TPL) ………………..
11
2.1 Основные нововведения TPL……………………………………
11
2.2 Параллелизм данных……………………………………………
17
2.3 Параллелизм задач……………………………………………….
20
2.4 Потенциальные ошибки, связанные с параллелизмом

данных и задач……………………………………………………
21
3 СОЗДАНИЕ МНОГОПОТОЧНОГО ПРИЛОЖЕНИЯ В СРЕДЕ

VISUAL STUDIO.NET………………………………………………
23
3.1 Структурная схема программы …………………………………
23
3.2 Разработка и оптимизация кода программы на C#…………….
25
4 ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЯ………………………………..
30
ЗАКЛЮЧЕНИЕ………………………………………………………..
35
Литература…………………………………………………………

Файлы: 1 файл

Курсяк C#.docx

— 192.86 Кб (Скачать файл)

 

Однако есть еще один значительный недостаток. В использовании данного  подхода нельзя указать порядок  выполнения методов. Последовательность, в которой методы передаются в Invoke(), вовсе не определяет порядок их выполнения. К тому же TPL не поддерживает возможность  явного указания приоритетов задач (они присваиваются автоматически  планировщиком задач среды .NET Framework). Это является очень существенным недостатком библиотеки параллельных задач. Тот факт, что управление потоками полностью контролируется планировщиком  задач, с одной стороны, значительно  облегчает работу разработчика программного обеспечения, а с другой, – делает систему управления потоками и задачами менее гибкой. Возможно именно поэтому, построение многопоточных приложений на основе класса Thread остается по-прежнему популярным.

 

 2.4 Потенциальные ошибки, связанные с параллелизмом данных и задач

 

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

 

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

- Используя метод Parallel.Invoke(), следует помнить, что функция ожидания завершения задачи уже включена в данный метод, и использование методов ожидания в данной ситуации нежелательно, а порой и недопустимо;

- Необходимо помнить, что при использовании циклов For() и ForEach() итерации могут выполняться как параллельно, так и нет. Поэтому нельзя создавать код, правильность исполнения которого возможна лишь при выполнении цикла параллельно или при соблюдении некой последовательности исполнения;

- В .NET Framework большая часть статических методов потокобезопасна, следовательно, они могут быть вызваны из нескольких параллельно выполняющихся потоков одновременно. Но следует обратить внимание, что действующая в данных ситуациях синхронизация значительно замедлит исполнение программы. Следует учитывать данные издержки;

 

- Параллельное использование потокоопасных методов может привести к повреждению или потере данных (иногда это остается незаметным, но потеря данных все равно происходит). Следует стараться избегать использования данных методов одновременно несколькими потоками;

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

 

3 СОЗДАНИЕ МНОГОПОТОЧНОГО ПРИЛОЖЕНИЯ В СРЕДЕ VISUAL STUDIO.NET

 

Для демонстрации работы многопоточности создадим многопоточное приложение, которое занимается скачиванием сайтов в несколько потоков. В качестве исходных данных – у нас будет выступать очередь из URL сайтов, а на выходе мы должны получить список скачанных HTML страничек. При этом у нас должна быть возможность вручную задавать количество потоков работы приложения, для того, чтобы мы могли впоследствии сравнить работу приложения с разным количеством потоков и сделать соответствующие выводы. Назовем приложение downloader.

 

3.1 Структурная схема программы

На рисунке 1 показана структурная схема нашего приложения.

 

Рисунок 1 – Структурная схема приложения downloader

 

3.2 Разработка и оптимизация кода программы на C#

 

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

В качестве данных у нас будет  очередь из URL адресов

 

 

            URLs.Enqueue("http://google.com");

            URLs.Enqueue("http://ya.ru");

            URLs.Enqueue("http://vorstu.ru");

            URLs.Enqueue("http://mail.ru");

            URLs.Enqueue("http://ru.akinator.com");

            URLs.Enqueue("http://kinopoisk.ru");

            URLs.Enqueue("http://internat.ax3.net");

            URLs.Enqueue("http://kaspersky.ru");

 

Общий код – это код для  загрузки очередной web страницы:

 

WebRequest request = WebRequest.Create(URL);

  HttpWebResponse response = (HttpWebResponse)request.GetResponse();

string HTML = (new StreamReader(response.GetResponseStream())).ReadToEnd();

 

Синхронизацию легче всего делать с помощью оператора lock:

 

Object locker = new Object();

….

void CriticalMethod()

{

lock(locker)

{

//критическая  секция

//здесь мы  работаем с общими для потоков  данными

}

}

 

Оператор lock принимает как аргумент объект синхронизации locker и допускает внутрь критической секции только один поток, остальные потоки подошедшие к lock – ожидают пока завершится поток, находящийся внутри критической секции. В качестве объекта синхронизации может выступать любой созданный (не null) объект ссылочного типа.

 

Первый вариант рабочей программы  выглядит так:

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

using System.Net;

using System.IO;

 

namespace Downloader

{

    class Program

    {

        //очередь адресов для закачки

        static Queue<string> URLs = new Queue<string>();

        //список скачанных страниц

        static List<string> HTMLs = new List<string>();

        //локер для очереди адресов

        static object URLlocker = new object();

        //локер для списка скачанных страниц

        static object HTMLlocker = new object();

 

        static void Main(string[] args)

        {

            URLs.Enqueue("http://microsoft.com");

            URLs.Enqueue("http://google.com");

            URLs.Enqueue("http://ya.ru");

             //создаем и запускаем 3 потока

            for (int i = 0; i < 3;i++)

                (new Thread(new ThreadStart(Download))).Start();

            //ожидаем нажатия Enter

            Console.ReadLine();

        }

 

        public static void Download()

        {

            //будем крутить цикл, пока не  закончатся ULR в очереди

            while (true)

            {

                string URL;

                //блокируем очередь URL и достаем  оттуда один адрес

                lock (URLlocker)

                {

                    if (URLs.Count == 0)

                        break;//адресов больше нет, выходим из метода, завершаем поток

                    else

                        URL = URLs.Dequeue();

                }

                Console.WriteLine(URL + " - start downloading ...");

                //скачиваем страницу

                WebRequest request = WebRequest.Create(URL);

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                string HTML = (new StreamReader(response.GetResponseStream())).ReadTo End();

                //блокируем список скачанных страниц, и заносим туда свою страницу

                lock (HTMLlocker)

                    HTMLs.Add(HTML);

                //

                Console.WriteLine(URL + " - downloaded (" + HTML.Length+" bytes)");

            }

        }

    }

}

 

Этот пример – рабочий, но в нем нет таймера, выбора потоков, обработки ошибок и контроля завершения потоков.

В приведенном  примере мы создали три потока. Стартовым методом для каждого  потока был метод Download(). Поток существует до тех пор, пока не выполнится метод Download(). Как только этот метод завершается, завершается и поток. В этом примере  главный поток (тот, который создает  остальные потоки) не ожидает завершения дочерних потоков и не отслеживает  их завершения. Как только пользователь нажмет Enter в точке Console.ReadLine();), главный поток завершится, а дочерние будут продолжать работать, пока не будет исчерпана очередь адресов. Приложение завершится только тогда, когда завершатся все его потоки.

Для того, чтобы сделать ожидание завершения множества потоков из главного потока создаем массив флажков, по одному флажку на поток. Когда поток отработал – он устанавливает свой флажок. Главный поток в это время ожидает, пока все флажки будут установлены - и когда это происходит – продолжает свое выполнение. Для нашего примера программы скачивания сайтов это будет выглядеть так:

 

static void Main(string[] args)

        {

            URLs.Enqueue("http://microsoft.com");

            URLs.Enqueue("http://google.com");

            URLs.Enqueue("http://ya.ru");

            //создаем массив хендлеров, для контроля завершения потоков

            ManualResetEvent[] handles = new ManualResetEvent[3];

            //создаем и запускаем 3 потока

            for (int i = 0; i < 3; i++)

            {

                handles[i] = new ManualResetEvent(false);

                (new Thread(new ParameterizedThreadStart(Download))).Start(handles  [i]);

            }

            //ожидаем, пока все потоки отработают

            WaitHandle.WaitAll(handles);

            //

            Console.WriteLine("Download completed");

            Console.ReadLine();

     }

 

        public static void Download(object handle)

        {

            //будем крутить цикл, пока не  закончатся ULR в очереди

            while (true)

            {

                …

            }

            //устанавливаем флажок хендла, что  бы сообщить главному потоку  о том, что мы отработали

            ((ManualResetEvent)handle).Set();

        }

Здесь главный  поток запускает три дочерних потока, которые начинают скачивать  сайты. Пока они работают – главный  поток ожидает в точке WaitHandle.WaitAll(handles);. Как только все три потока завершат работу – главный поток продолжит  свое выполнение.

Что бы при любой возникающей ошибке в потоках, потоки продолжали работу, но в конце работы программы, ошибки отобразились бы пользователю мы будем использовать блок try{}catch{} в методе который создает потоки – не сможет отловить ошибки самих потоков. Это значит, что отлавливать ошибки потоков нужно внутри того метода, где поток и работает.

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

 

4 ТЕСТИРОВАНИЕ  ПРИЛОЖЕНИЯ

 

Протестировав приложение с разным количеством  потоков, был получен такой результат: см. рисунок 2 – рисунок 6.

 

Рисунок 2 – Работа приложения в однопоточном режиме

 

 

Рисунок 3 – Работа приложения в два потока

 

 

Рисунок 4 – Работа приложения в три потока

 

 

Рисунок 5 – Работа приложения в четыре потока

 

 

 

Рисунок 6 – Работа приложения в пять потоков

 

 

Таблица 1

Таблица отношения времени загрузки страниц  к количеству потоков

Количество запущенных одновременных  потоков

Время в секундах

1

8.792

2

4.856

3

3.422

4

3.263

5

3.191


 

 

 

Рисунок 7 – График зависимости времени на загрузку страниц от количества одновременных потоков

 

 

Проанализировав данные таблицы 1 и графика зависимости времени на загрузку страниц от количества потоков см. рисунок 7, можно сделать вывод что, использование многопоточности дает хороший результат при работе с удаленными объектами, такими как web-страницы или базы данных. Если запрос к web-странице или базе данных выполняется достаточно долго, то для этого лучше создать отдельный поток, предоставив пользователю возможность продолжить работу с другими данными.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ЗАКЛЮЧЕНИЕ

 

В курсовой работе были изучены методы создания многопоточных приложений с использованием языка C# в Visual Studio.NET.

Построены алгоритмы  и структура программы многопоточного приложения. Полученное приложение синхронизирует выполнение процессов загрузки web-страниц. Данная программа реализована на языке C#, исполняемый файл занимает 7 680 байт, в приложении приведен полный текст программы.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Информация о работе Многопоточные приложения в Visual Studio.NET. Разработка приложения, демонстрирующего многопоточность для одного процессора