Многонишкови програми

Публикувана на: 30.06.2008, от hristov_b
Кoментари:7

    
 1. Необходимост от разглеждането им.
 Преди два месеца, колегата Avatara написа следното:
"Изчетох внимателно всичко написано.
Искам да запитам някой може ли да ми обясни как се обработват "многонишкови процеси" в

C++?
Предварително благодаря за съдействието."
 Считам, че въпросът е интересен и си заслужава да го обособим в отделна тема. Сега вече ще бъдем по - свободни и може да я развием след много допълнително четене на литература. Предлагам следното :
 а/ да се даде необходимата теоритична постановка;
 б/ да се посочат реални проблеми от нашата практика, които изискват многонишковост;
 в/ да се представят работещи решения на такива проблеми, като се използват различни езици за програмиране;
 г/ всичко написано да е достатъчно ясно и просто обяснено, за да може да се разбира от ученици и учители с по - малък опит в програмирането.

 Следва въведение в проблема :

 2.Многозадачност и многонишковост
 a/ Многозадачност (многопроцесност)
 Под управлението на Windows е възможно едновременно да :
  -слушаме музика;
  -теглим файл по Интернет;
  -да въвеждаме текст.
 Тези три действия се изпълняват от три различни програми ( процеси), които работят едновременно, макър че компютъртът разполага с един процесор. Разпределянето на процесорното време между отделните процеси ( задачи ) се управлява от операционната система.
 б/ Многонишковост.
 В едно приложение понякога се налага да се изпълняват операции, отнемащи много време ( при дълбока рекурсия, при работа с големи обеми от данни). Докато те се изпълняват потребителският интерфейс на отговаря, тъй като приложението вече е поела съответната времеотнемаща операция. В такива случаи потребителят трябва да бъде известяван за статуса на извършваната работа и при необходимост да допуска потребителска намеса. Този и други подобни проблеми се решават чрез реализиране на многонишковост.
 Една програма е поредица от инструкции. Те образуват една нишка (thread). Ако искаме да намерим решение на горния проблем е необходимо да поставим дългите изчисления в една нишка, а потребителския интерфейс – в друга. След това да осигурим периодичност в изпълнението на инструкциите  от двете нишки.
 По този начин се вижда, че многонишковостта е подобна на многозадачността. Разликата е, че управлението на многозадачността се решава от операционната система, а за управлението на нишките следва да се осигури от програмиста.
 Друг пример: Ако искаме да напишем една програма, която да проследява поотделно действията на двама играчи, в програмата ще трябва да има две нишки – по една за всеки играч. Така в програмата ще трябва да се обособят две редици от инструкции.Такава програма се нарича многонишкова (multithread). Чрез управление на нишките ще се разпредели процесорното време между двамата играчи и те ще останат с впечатлението, че имат едновременне достъп. Причината е , че процесорът се предоставя на всяка нишка за някакъв определен интервал от време. След изтичането му, активната нишка се прекъсва и управлението се предава на следващата чакаща нишка. Този тип прекъсвания са известни като софтуерни прекъсвания – те са предварително планирани.

 3.Същност на нишките (threads)

( като основен източник на информация по проблема ползваме Глава 17. от книгата на Светлин Наков – Програмиране в .NET; автори на разлежданата глава са Александър Русев и Иван Митев)
 Тук ще обобщим казаното по – горе:


 -нишките съществуват в едно приложение, т.е. приложението се състои от една или повече нишки ( не бива да са много, защото трудно ще се управляват);
 -нишките предоставят възможност на процесора да изпълнява няколко подзадачи (подалгоритми, подпрограмиа) едновременно;
 -паралелното изпълнение се симулира чрез постоянно превключване между подзадачите през много кратки интервали от време;
 -всяка нишка изпълнява една подзадача ( програмен код), като за кратко време става активна (ангажира процесора) и след това го освобождава и предоставя на следващата чакаща нишка.
 -тъй като процесорът е един, създадените нишки се подреждат в опашка и в нея изчакват следващото си активиране;
 -при попадане на нишка в опашката, се запомня инструкцията от нейната последователност, която ще бъде първоизпълнена при следващото активиране.

 Надявам се, че написаното от мен е разбираемо.

Коментари 7

04.07.2008 iv_2007

Profile

Успях да настроя написаната програма. След като замених Console.WriteLine("....."); във втория цикъл с  Console.ReadLine(); установих, че въвеждането на стойности се прекратява така както и при въвеждане във файл при неопределен край на въвеждането, тоест с Ctrl+Z. Това ме навеждава на мисълта, че по същество нишките са като файловете.

03.07.2008 iv_2007

Profile

Въпросът ми не беше правилно зададен. Оказа се, че инсталацията на Visual Studio .NET изисква допълнителни компоненти, които не са на двата диска, а на отдалечен компютър. Сега вече видях как изглежда началния екран. Надявам се скоро да изпробвам поместените кодове. Smile

02.07.2008 hristov_b

Profile

Не разбирам въпроса ти. При стартиране на студиото и натискане на бутона [New Project] се отваря прозорец с заглавен ред New Project, който съдържа два прозореца – в левия се появяват следните възможности:

            Visual Basic Projects            Visual C# Projects            Visual J# Projects            Visual C++ Projects

Няма различни начини за инсталиране. Какъв по точно е твоят софтуер?

 

02.07.2008 iv_2007

Profile

Как точно да се направи инсталацията на Visual Studio .NET, за да заработи за С#? Embarrassed

02.07.2008 hristov_b

Profile

Още един вариант с използване на C++ и iostream

      Той е за онези от вас, които вече са се подразнили от използването на класа Console. Просто се добавят в началото редовете    
#include <iostream>
    
#include <conio.h> -
необходим е за задържането на екранa чрез getch().
    
using namespace std;
     След това навсякъде в програмата се заменя Console::Write или Console::WriteLine със cout<<, като в началото на извеждания текст се постави \n за слизане на нов ред.     Ето и целия код на едно място: 
#include "stdafx.h"

#include <iostream>

#include <conio.h>

#using <mscorlib.dll>

using namespace System;

using namespace System::Threading;

using namespace std;
 


public __gc class Class1

  
{ public:
    
static void myThread()
    
{for (int i = 0; i < 10; i++)
         
{
            
cout<<"\nmyThread: "<<i;
            
Thread::Sleep(100);
        
}
    
}
 
};

int _tmain()
{    
cout<<"\nMain thread: Start a second thread.";
      
Thread *t = new Thread(new ThreadStart(0, &Class1::myThread));
     
t->Start();Thread::Sleep(100);
    
for (int i = 0; i < 4; i++)
      
{
         cout<<"\nMain thread: Do some work.";           
Thread::Sleep(100);
    
}
    
cout<<"\nMain thread: Call Join(), to wait until myThread ends.";
    
t->Join();
    
cout<<"\nMain thread: myThread.Join has returned.  Press Enter any Key.";
    
getch();
    
return 0;


}

 

02.07.2008 hristov_b

Profile

Примерно решение с използване на C++

 

Ще използваме текста от решението на задачата с помощта на C#  и в него ще отразим необходимите промени (новите – ще удебеляваме).

 

A.Процедура за създаване на конзолно приложение чрез Visual Studio .NET 2003

     а/ стартиране на MSVS.NET;    
б/ щракване върху бутона [
New Project];
    
в/ определяне типа на проекта –
Visual C++ Projects и .NET
    
г/ уточняване на шаблона –
Console Application (.NET)
    
д/ Въвеждане името на проекта –
Example1_cpp
    
е/ Определяне на папката, в която ще се съхрани проекта и щракване върху [
Open];
    
ж/ с щракване върху [
Ok] се създава проект, който съдържа следния код (номерата на редовете са добавени от мен – там, където не са последователни по – късно ще се добави необходимия код):
 
// This is the main project file for VC++ application project
 
// generated using an Application Wizard.
 
1.
#include "stdafx.h"

2.
#using <mscorlib.dll>

3.
using namespace System;

4.

15.
int _tmain()

16.
{
    // TODO: Please replace the sample code below with your own.
28.
    Console::WriteLine(S"Hello World");

29.
  return 0;
30. }

 

Б. Осигуряване работа с нишки в приложението.

На мястото на свободния четвърти ред се въвежда следното: using namespace System::Threading;. По този начин се обявява, че ще бъде използвано пространството от имена Threading  - то съдържа  необходимите класове и типове за работа с нишки.

 

В. Обособяване на модул, съдържащ програмен код за втората нишка.

Над главната програма добавяме следния метод за управление на нишката.

5. public __gc class Class1

 6.  { public:
7.     static void myThread()

8.   {for (int i = 0; i < 10; i++)
 
9.         {

10.             Console::Write("myThread: ");Console::WriteLine(i);

11.             Thread::Sleep(100);

12.         }

13.     }

14. };

            Както се вижда, предвидено е да се изведат върху конзолата десет последователни числа, които се предхождат от текст, поясняващ, коя нишка работи в момента. След всяко едно извеждане върху екрана, чрез метода Sleep(100) от класа Thread,  временно се спира изпълнението на втората нишка, за да може да продължи изпълнението на първата

 

Г. Създаване и стартиране на втора нишка

В главната програма вмъкваме следния код:

17.  Console::WriteLine(S"Main thread: Start a second thread.");

18.       Thread *t = new Thread(new ThreadStart(0, &Class1::myThread));
19.      t->Start();Thread::Sleep(100);

 

            Ред 17 извежда съобщение, че започва изпълнението на първата / главната нишка и че ще бъде създадена втора нишка. С ред 18 се създава обект t от класа Thread и като параметър се посочва името на модула, който трябва да бъде изпълнен от втората нишка. Самото стартиране на втората нишка се реализира с ред 19.

 

Д. Разпределяне поравно на процесорното време между двете нишки

20.     for (int i = 0; i < 4; i++)

21.  {
22.         Console::WriteLine("Main thread: Do some work.");

23.       Thread::Sleep(100);

24.     }

 

            Чрез горния програмен код е предвидено четири пъти да се прекъсва временно едната нишка и да се предава управлението на втората. Това се реализира чрез метода Sleep на класа Thread. За да се отразява на екрана работата на първата нишка е предвидено извеждане на едно и също съобщение.

 

Е. Блокиране изпълнението на първата нишка, докато не приключи изпълнението на втората.

25.     Console::WriteLine("Main thread: Call Join(), to wait until myThread ends.");

26.     t->Join();
27.     Console::WriteLine("Main thread: myThread.Join has returned.  Press Enter to end program.");

28.     Console::ReadLine();

 

            Това действие се реализира от метода Join() на класа Thread. Преди и след неговото действие са извеждат подсказващи съобщения. Ред 28 предизвиква задържане на конзолния прозорец, докато не бъде натиснат клавиша Enter.

 

Ж. Резултат от изпълнението на програмата

Те са същите, както при C#.
 
З. Обобщаване на направеното
        - не се налага да се прави някаква промяна в алгоритъма при преминаване от C#към C++;
        - промените са само синтактични;
        - по – бавно се компилира, но се изпълнява по – бързо и трудно може да се приспи нишка за по – малко от 100 милисекунди.

01.07.2008 hristov_b

Profile

3. Обобщен алгоритъм за работа с нишки

 

            А. Създаване на нишка в разработваната програма.

            Б. Стартиране на желаната нишка.

            В. Управляване на жизнения цикъл на нишката, чрез изпълнение на един от следните :методи:

                        а/ Временно приспиване на нишката (Sleep())– за указан брой милисекунди;

                        б/ Приспиване на нишката за неопределено време (Suspend()) – за изваждането и от това състояние е необходим специален метод (Resume());

                        в/ Изчакване (Join()) – извикващата нишка изчаква, докато извиканата приключи;

                        г/ Блокиране на викащата нишка (Wait())– разблокирването се осъществява чрез специален метод, който трябва да се изика от неблокирана нишка.

            Г. Окончателно спиране на нишката.

 

4. Видове приложения , езици и среди за програмиране

 

            В училише се разработват два вида приложения : конзолни и Windows  - приложения. При конзолните прилижения се използва преди всичко модулно – ориентирания подход за създаване на програмни продукти, а при Windows – приложенията – обектно – ориентирания подход. Програмите се пишат на Basic, Pascal, C++, Java. Като среди  за програмиране се използват  Turbo Pascal, Borland Pascal, MS Visual Basic Std. 6.0., Microsoft Visual Studio .NET 2003. Аз работя само с последната и горещо препоръчвам на колегите си да я ползват. Предимствата са следните :

            - допуска да се разработват двата вида приложения – конзолни и Windows – приложения.;

            - програмите могат да се пишат на Basic, C++, C#, J# ;

            - има богата помощна система (MSDN Library)

            - има създадени класове за работа с нишки.

 

Тъй като трябва отнякъде да се започне, аз предлагам това да са конзолни приложения, създадени чрез C#. Изборът ми е повлиян от следното :

-         в Help – системата има няколко работещи примера;

-         от тях ще се поучим и след това ще създаваме наши такива;

 

5. Задача 1. Да се състави конзолно приложение е име Example1, в което може да се работи с нишки и се реализират следните възможности:

а/ да се обособи програмен код за втората нишка в самостоятелен модул;

б/ да се създаде и стартира втората нищка;

в/ да се разпредели процесорното време поравно между двете нишки;

г/ да се блокира първата нишка, докато не приключи изпълнението на втората.

 

Примерно решение с използване на C#

 

A.Процедура за създаване на конзолно приложение чрез Visual Studio .NET 2003

     а/ стартиране на MSVS.NET; 

    б/ щракване върху бутона [New Project];

     в/ определяне типа на проекта – Visual C# Projects    
г/ уточняване на шаблона –
Console Application
    
д/ Въвеждане името на проекта –
Example1
 

    е/ Определяне на папката, в която ще се съхрани проекта и щракване върху [Open];

     ж/ с щракване върху [Ok] се създава проект, който съдържа следния код (номерата на редовете са добавени от ментам, където не са последователни по – късно ще се добави необходимия код):  
1.
using System;
 
2.
 
3.
namespace Example1
  
4.
{
     /// <summary>      ///Summary description for Class1.  

       /// </summary>

 5.  class Class1  
6
.  {
                /// <summary>  

        /// The main entry point for the application.

          /// </summary>  

        [STAThread]

  15.       static void Main(string[] args) 
1
6.       {
                   //                   // TODO: Add code to start application here 

     //

  
29
.       }
   
30
.  }
   
31
.}

 

Б. Осигуряване работа с нишки в приложението.

На мястото на свободния втори ред се въвежда следното: using System.Threading;. По този начин се обявява, че ще бъде използвано пространството от имена Threading  - то съдържа  необходимите класове и типове за работа с нишки.

 

В. Обособяване на модул, съдържащ програмен код за втората нишка.

Над главната програма добавяме следния метод за управление на нишката.

 7..    public static void myThread()

 8..   {

 9.        for (int i = 0; i < 10; i++)

10.         {

11.            Console.WriteLine("Second Thread: {0}", i);

12.            Thread.Sleep(0);

13.        }

14.   }

            Както се вижда, предвидено е да се изведат върху конзолата десет последователни числа, кито се предхождат от текст, почсняващ, коя нишка работи в момента. След всяко едно извеждане върху екрана, чрез метода Sleep(0) от класа Thread,  временно се спира изпълнението на втората нишка, за да може да продължи изпълнението на първата

 

Г. Създаване и стартиране на втора нишка

В главната програма вмъкваме следния код:

17.        Console.WriteLine("Main thread: Start a second thread.");

18.        Thread t = new Thread(new ThreadStart(myThread));

19.        t.Start();

            Ред 17 извежда съобщение, че започва изпълнението на първата / главната нишка и че ще бъде създадена втора нишка. С ред 18 се създава обект t от класа Thread и като параметър се посочва името на модула, който трябва да бъде изпълнен от втората нишка. Самото стартиране на втората нишка се реализира с ред 19.

 

Д. Разпределяне поравно на процесорното време между двете нишки

20.        for (int i = 0; i < 4; i++)

21         {

22.            Console.WriteLine("Main thread: Do some work.");

23.            Thread.Sleep(0);

24.        }

            Чрез горния програмен код е предвидено четири пъти да се прекъсва временно едната нишка и да се предава управлението на втората. Това се реализира чрез метода Sleep на класа Thread. За да се отразява на екрана работата на първата нишка е предвидено извеждане на едно и също съобшение.

 

Е. Блокиране изпълнението на първата нишка, докато не приключи изпълнението на втората.

25.        Console.WriteLine("Main thread: Call Join(), to wait until myThread ends.");

26.        t.Join();

27.        Console.WriteLine("Main thread: myThread.Join has returned.  Press Enter to end program.");

28.        Console.ReadLine();

            Това действие се реализира от метода Join() на класа Thread. Преди и след неговото действие са извеждат подсказващи съобщения. Ред 28 предизвиква задържане на конзолния прозорец. Докато не бъде натиснат клавиша Enter.

 

Ж. Резултат от изпълнението на програмата

Main thread: Start a second thread.
Main thread: Do some work.
Second Thread: 0
Main thread: Do some work.
Second Thread: 1
Main thread: Do some work.
Second Thread: 2
Main thread: Do some work.
Second Thread: 3
Main thread: Call Join(), to wait until ThreadProc ends.
Second Thread: 4
Second Thread: 5
Second Thread: 6
Second Thread: 7
Second Thread: 8
Second Thread: 9
Main thread: ThreadProc.Join has returned.  Press Enter to end program.
 
З. Обобщаване на направеното
        - наложително е да се използва именованото пространство Threading. В него се намира декларацията на класа Thread, който осигурява необходимата фунционалност за стртиране и управление на нишки;
        - за всяка нишка трябва да се създаде обект и да се посочи името на метода, който ще трябва да започне да се изпълнява, когато нишката се стартира; Състоянието на нишката е Unstarted, т.е. тя е създадена, но не е стртирана;
        - нишката се стртира от метода Start(). След стартиране нейното състояние се определя на Running; 
        - чрез метода Sleep(милискунди) текущата нишка се „приспива” за указания брой милисекунди. Състоянието й се определя на WaitSleepJoin,т.е. тя е блокирана.След изтичането на зададения интервал, тя ще продължи работата си.
        - чрез метода Join() извикващата нишка изчаква, докато извиканата приключи. Може да се укаже и време. Извикващата нишка изпада в състояние WaitSleepJoin. 
        - състоянието на нишката се определя като Stopped след като е прекратена или е бил извикан методът Abort() или след като е приключила по естествен път.