Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебники 60141.doc
Скачиваний:
7
Добавлен:
01.05.2022
Размер:
1.2 Mб
Скачать

Лабораторная работа № 2. Практическое применение потоков. Создание многопоточных приложений

Цель работы: приобретение навыков создания многопоточных приложений.

Теоретический материал

Понятие о потоках

Подпроцесс, поток (thread), нить — это объект операционной системы, который представляет путь выполнения программы внутри отдельного процесса. Каждое приложение Win32 имеет по крайней мере один поток, часто называемый основным или стандартным, но приложения для выполнения других задач вольны создавать дополнительные потоки.

С помощью потоков реализуются средства одновременного выполнения отдельных подпрограмм. Конечно, если компьютер оснащен только одним про­цессором, то о настоящей одновременности работы двух потоков говорить не приходится. Но когда для обработки каждого потока операционная система по­очередно выделяет определенное время (измеряемое в мельчайших долях се­кунды), то создается впечатление одновременной работы нескольких приложе­ний.

Практическое применение потоков

Не секрет, что потоки — серьезное подспорье для программистов Windows. В своих приложениях вы можете создавать вторичные потоки, предназначенные для выполнения некоторых фоновых работ. Обычно потоки используются для вычислений по данным, содержащимся в ячейках электронных таблиц или для подкачки документов текстовых процессоров при выводе на печать. И то­гда задача разработчика — организовать необходимую обработку фоновых процессов, обеспечив при этом наилучшее время реакции для функционирова­ния пользовательского интерфейса.

Та часть библиотеки визуальных компонентов (VCL), которая связана с пользовательским интерфейсом, базируется на предположении, что в любой момент времени к ней может получить доступ только один поток, поэтому принципиально невозможно организовать управление интерфейсом пользова­теля со стороны нескольких потоков. VCL требует, чтобы все управление поль­зовательским интерфейсом происходило в контексте основного потока прило­жения (исключение составляет только поток TCanvas).

В любом деле важно не переборщить. И особенно в случае с потоками. Не­смотря на то, что потоки оказывают помошь в решении одних проблем, они приносят с собой целый набор новых проблем. Предположим, например, что вы создаете интегрированную среду разработки и хотите, чтобы компилятор рабо­тал в своем собственном потоке, а программист мог продолжать работу с при­ложением во время компиляции программы. Здесь могут возникнуть проблемы вот какого плана. Что будет, если внести изменения в файл, который компили­руется в данный момент? Можно предложить несколько вариантов решения этой проблемы: например, создавать временную копию файла в момент его компиляции или делать недоступными для редактирования файлы, компиляция которых еще не завершена. Все дело в том, что потоки не являются панацеей. Решая те или иные проблемы разработки, они неминуемо порождают новые, чреватые появлением ошибок, которые очень трудно поддаются отладке. Раз­работка и реализация кода, обеспечивающего устойчивую работу потоков, — задача не из легких, поскольку в этом случае приходится учитывать гораздо больше факторов, чем при разработке однопоточного приложения.

Основы работы объекта TThread

Класс TThread является прямым потомком класса TObject и, следовательно, не является компонентом. Нетрудно заметить, что метод TThread.Execute абстрактный. Это значит, что класс TThread сам является абстрактным, и вы никогда не сможете создать экземпляр самого класса TThread. Вам можно создавать только экземпляры потомков класса TThread.

Для создания функционального потомка класса TThread надо переопределить единственный метод - Execute.

Единственный логический параметр, который передается в конструктор Create класса TThread, называется CreateSuspended и показывает, начинать ли поток в приостановленном состоянии. Если этот параметр равен False, ме­тод Execute этого объекта будет автоматически вызван без промедления. Если этот параметр равен True, то для действительного начала работы потока в оп­ределенной точке приложения нужно воспользоваться методом Resume, кото­рый приведет к вызову метода Execute. Обычно параметр CreateSuspended устанавливаемся равным True в случае, когда перед работой потока нужно ус­тановить дополнительные свойства для потокового объекта. Установка же свойств после запуска потока может привести к возникновению проблем.

Если же метод в рамках потока будет иметь тип результата void (т.е. отсутствие типа) с пустым списком параметров, тогда создается делегат типа System.Threading.ThreadStart.

Таким образом, основной фрагмент листинга 1 (однопоточная версия) с последовательным вызовом трех функций:

Number (nNum);

Fibonacci (nFib);

Factorial (nFac);

можно трансформировать в соответствующий фрагмент листинга 2 (трехпоточная версия):

Thread thread1 = new Thread(new ParameterizedThreadStart(Number)); thread1.Start(nNum); Thread thread2 = new Thread(new ParameterizedThreadStart(Fibonacci)); thread2.Start(nFib); Thread thread3 = new Thread(new ParameterizedThreadStart(Factorial)); thread3.Start(nFac); thread1.Join(); thread2.Join(); thread3.Join();

Здесь метод Start запускает отдельную нить вычислений и передает в нее соответствующую переменную, а метод Join ожидает реального завершения указанного потока.

Кстати, если бы ставилась гипотетическая задача передать результат вычисления функции Number в функцию Fibonacci в качестве аргумента, то перед запуском нити thread2 возникла бы необходимость дождаться завершения выполнения потока thread1 методом thread1.Join(), а уж затем запускать thread2.

Следует заметить, что метод Thread.Join является перегруженным, и факультативная переменная типа Int32 задавала бы время ожидания в миллисекундах. Так, метод thread3.Join(5000) ждал бы завершения потока в течение 5 с, после чего можно было бы форсировать завершение вычислительной нити thread3 методом thread3.Abort(), не дожидаясь вывода результатов.

В языке C# отдельный поток может находиться в одном из следующих состояний (определяемом свойством Thread.ThreadState):

- незапущенный (Unstarted),

- исполнение (Running),

- ожидание (WaitSleepJoin),

- приостановленный (Suspended),

- прерванный (Aborted),

- завершенный (Stopped)

Каждое состояние характеризуется своими методами, и детальную информацию о них (и не только) следует искать в удобной системе помощи Microsoft Visual Studio 2005.

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

Описание методов

Решето Эратосфена - метод, разработанный Эратосфеном и позволяющий отсеивать составные числа из натурального ряда. Сущность его заключается в следующем.

Зачеркивается единица. Число 2 - простое. Зачеркиваются все натуральные числа, делящиеся на 2. Число 3 - первое незачеркнутое число - будет простым. Далее зачеркиваем все натуральные числа, которые делятся на 3. Число 5 - следующее незачеркнутое число - будет простым. И так можно продолжать до бесконечности.

Числа Фибоначчи - это элементы числовой последовательности 1, 1, 2, 3, 5, 8, 13... , в которой каждый последующий член равен сумме двух предыдущих, а сама эта последовательность называется рядом Фибоначчи.

Таким образом, число Фибоначчи можно вычислить по формуле Fibonacci(n) = Fibonacci(n-2) + Fibonacci(n-1).

Например, 7-е число из ряда Фибоначчи соответствует Fibonacci(7) = Fibonacci(5) + Fibonacci(6) = 5 + 8 = 13.

Факториал - это произведение натуральных чисел от единицы до какого-либо данного натурального числа n (т. е. 1*2*3*...*n), обозначается "n!". Например, 7! = 1*2*3*4*5*6*7 = 5040.

Создание многопоточного приложения

Увеличение количества вычислительных компонентов в центральном процессоре способствует интенсивному развитию технологий многопоточного программирования. Однако эффективное управление потоками дело хлопотное и не во всех случаях приводит к росту производительности.

В частности, нет большого смысла в попытках мультипрограммирования решета Эратосфена, поскольку в нем натуральный ряд отбирается постепенным отсеиванием составных чисел. Или, например, алгоритм определения некоторого числа из ряда Фибоначчи не удастся сделать многопоточным, так как каждый последующий член ряда равен сумме двух предыдущих, а значит, надо шаг за шагом определять весь ряд. Да и ни к чему "параллелить" алгоритм определения факториала (n!=1*2*3*...*n), поскольку факториал числа 65 вычисляется за считанные миллисекунды, а подсчитать факториал большего числа уже проблематично, ведь разрядность целочисленных переменных в компиляторах имеет свои ограничения (например, переменная типа ulong является беззнаковым целым от 0 до 18446744073709551615).

Необходимо решить три перечисленные задачи в одном алгоритме, поэтому возможны любопытные варианты.

Можно написать классический последовательный код. В этом решении из тела программы в строгой последовательности вызываются основные функции:

- Number (для нахождения максимального простого числа в определенном диапазоне от 1 до nNum),

- Fibonacci (для определения члена под номером nFib из ряда Фибоначчи)

- Factorial (для вычисления факториала числа nFac), которые проводят соответствующие вычисления для целочисленных аргументов nNum, nFib, nFac.

По умолчанию nNum = 200 000, nFib = 500 000 000, nFac = 65, но при желании в исполнительный процесс можно передать другие параметры с помощью строки приглашения, например, вот так:

PCW_SingleThreadTest.exe 1000 100 10

Задание 1. Разработать программу, включающую процедуры расчета решета Эратосфена, чисел Фибоначчи, факториала.

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

Пример выполнения задания 1:

Результат работы программы представлен на рис. 2.

Рис. 2. Результат работы многопоточного приложения

Листинг программы:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Threading;

namespace лаб2

{

public partial class Form1 : Form

{

//целые беззнаковые числа

//для считывания из формы исходных данных

ulong nNum;

ulong nFib;

ulong nFac;

//глобальные переменные

//для хранения результатов вычислений

public static ulong rNum;

public static ulong rFib;

public static ulong rFac;

public Form1()

{

InitializeComponent();

}

//Вычисление без использования потоков

private void button1_Click(object sender, EventArgs e)

{

start(); //обнуление полей и значений

//Считывание чисел из формы в программу

try

{

nNum = Convert.ToUInt32(textBox1.Text);

nFib = Convert.ToUInt32(textBox2.Text);

nFac = Convert.ToUInt32(textBox3.Text);

}

catch // Обнаружение возможных исключений

{

MessageBox.Show("введенные данные имеют неверный формат");

}

//Вызов функций рассчета решета Эратосфена, числа Фибоначчи и факториала

try

{

Number(nNum);

textBox6.Text = rNum.ToString();

Fibonacci_2(nFib);

textBox5.Text = rFib.ToString();

Factorial(nFac);

textBox4.Text = rFac.ToString();

}

catch

{

MessageBox.Show("В ходе вычислений возникла ошибка.\nВозможно введено слишком большое число");

}

}

//Процедура рассчета решета Эратосфена

public static void Number(object param)

{

//приведение к соответствующему типу

ulong param_ = (ulong)param;

//создание массива беззнаковых чисел

ulong[] mas = new ulong[param_];

//заполнение массива числами от 1 до введенного на форме числа

for (ulong i = 0; i < param_; i++)

mas[i] = i + 1;

ulong j ;

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

//вычеркивание заменяется изменением значения на 0

for (j = 0; j < param_; j++)

{

for (ulong i = j + 2; i < param_; i++)

{

if (mas[j + 1] != 0)

{

if (mas[i] % mas[j + 1] == 0)

{ mas[i] = 0; }

}

else break;

}

}

//результат - последнее не нулевое значение в массиве

for (ulong i = param_ - 1; i > 0; i--)

{

if (mas[i] != 0)

{

rNum = mas[i];

break;

}

}

}

//Процедура рассчета числа Фибоначчи

public static void Fibonacci_2(object param)

{

//приведение типов

ulong param_ = (ulong)param;

//создание массива беззнаковых чисел

ulong[] mas = new ulong[param_];

//первые 2 элемента единицы

mas[0] = 1;

mas[1] = 1;

//рассчет остальных значений

for (ulong i = 2; i < (ulong)param; i++)

mas[i] = mas[i-1] + mas[i-2];

//результат - значение последнего элемента массива

rFib = mas[param_-1];

}

//Процедура рассчета факториала

public static void Factorial(object param)

{

ulong param_=1; //вспомагательная переменная

ulong newparam = (ulong)param; //приведение типов

for (uint i = 1; i <= newparam; i++)

{

param_ = param_ * i;

}

rFac = param_;

}

private void button2_Click(object sender, EventArgs e)

{

start(); //обнуление полей и значений

try //считывание значений из формы

{

nNum = Convert.ToUInt32(textBox1.Text);

nFib = Convert.ToUInt32(textBox2.Text);

nFac = Convert.ToUInt32(textBox3.Text);

}

catch //Обнаружение возможной ошибки

{

MessageBox.Show("введенные данные имеют неверный формат");

}

try

{

//Создание потоков для каждой процедуры рассчета

//и их запуск

Thread t1 = new Thread(new ParameterizedThreadStart(Number));

t1.Start(nNum);

Thread t2 = new Thread(new ParameterizedThreadStart(Fibonacci_2));

t2.Start(nFib);

Thread t3 = new Thread(new ParameterizedThreadStart(Factorial));

t3.Start(nFac);

//приостановление потоков

t1.Join();

t2.Join();

t3.Join();

//вывод результатов

textBox6.Text = rNum.ToString();

textBox5.Text = rFib.ToString();

textBox4.Text = rFac.ToString();

}

catch

{

MessageBox.Show("В ходе вычислений возникла ошибка.\nВозможно введено слишком большое число");

}

}

//Процедура очистки полей и значений

private void start()

{

nNum = 0;

nFib = 0;

nFac = 0;

rNum = 0;

rFib = 0;

rFac = 0;

textBox4.Text = "0";

textBox5.Text = "0";

textBox6.Text = "0";

}

}

}

Задание 2. Составить постановку задачи и реализовать программу, создающую многопоточное приложение.

Результаты работы:

  • отлаженные программы по всем заданиям, принятые преподавателем;

  • отчет по заданию 2.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]