Все технические форумы на одном сайте Удобный поиск информации с популярных форумов в одном месте
Вопрос: Многопоточное программирование с использованием семафоров на примере задачи о ганнибалах

необходимо осуществить многопоточную реализацию задачи о ганнибалах, при этом пользуясь семафорами.
текст задачи:
Племя из n дикарей ест вместе из большого горшка, который вмещает m кусков тушеного миссионера. Когда дикарь хочет обедать, он ест из горшка 1 кусок, если только горшок не пуст, иначе дикарь будит повара и ждет, пока тот не наполнит горшок. Повар, сварив обед, засыпает. Создать многопоточное приложение, моделирующее обед дикарей. При решении задачи пользоваться семафорами. На экран нужно выводить состояние каждого из n дикарей (ест или ждет), состояние повара (спит или готовит). сообщение о наполненности горшка (полон или пуст).
в С++ не силён, а с многопоточным программированием и подавно не знаком, но решить задачу нужно.
что хочу от вас? хотелось бы получить в идеале готовую реализацию с построчным комментированием, а в общем случае - подсказки, объяснения возможных нюансов, полезные справочники и прочую инфу, которая мне может понадобиться.
Кликните здесь для просмотра всего текста
мне дали и пример реализации похожей задачи, но всё же там другая задача и семафоров нет, а по отсутствию опыта тяжело сориентироваться
Ответ:

Не по теме:

Спасибо, Anhk. Благодаря этой теме представился случай вспомнить, .

Ганниба́л (247—183 до н. э.) — карфагенский полководец. Считается одним из величайших полководцев и государственных деятелей древности.

Вопрос: Что такое многопоточное программирование?

Здравствуйте, а можете пожалуйста объяснить чайнику что такое многопоточное программирование, если можно, то с примером на c#
Заранее благодарю!
Ответ: gregoro, другой пример:
у вас одна рука, вы ею держите и едите арбуз, но вам надо набрать глупый пост на форуме. С одной рукой надо отложить арбуз. И вы не сможете продолжить есть арбуз, пока не наберёте и не отправить пост на форум. Если у вас 2 руки вы можете одновременно держать арбуз и набирать глупый пост на форум.
Вопрос: Инструмент многопоточного программирования

Программирование многопоточных приложений крайне непростое дело. Приходится обходить много подводных камней: учитывать гонки, взаимоблокировки и др. Специально для облегчения разработки программ создан инструмент, с помощью которого эти затруднения легко разрешимы. Инструмент представляет собой библиотеку классов, написанную на C# и включающую достаточное количество методов, позволяющих просто писать довольно сложные многопоточные приложения. Всем, кто интересуется созданием параллельных программ, эта библиотека классов способна в значительной степени облегчить написание кода.
Если будут какие-то вопросы, с удовольствием отвечу. А также буду весьма признателен за выявленные недостатки.

Программное обеспечение исполняющей среды представлено классами пространства имен – BindingAndServisParallel.
Полная документация, библиотека классов и пример хранятся: sites.google.com/site/algonauts/Home/fajly
в файлах DocCoreParallel.docx, BindingAndServisCoreParallel_dll.dll, main.cs.
Пример компилируется в среде Visual Studio 2010 Express на 64-х битной платформе. При компиляции проекта следует добавить ссылки:
BindingAndServisCoreParallel_dll, PresentationCore, PresentationFramework, System, System.Xaml, WindowsBase

Библиотеку классов BindingAndServisCoreParallel_dll версия сборки 0.0.0.1 можно использовать без ограничения в любых целях. 
Ответ: Dedoc,
Код C#
1
2
         id11=  MyCoreParallel. CreateUnitAsServer(null,null,null, null, null,null);
         id12 = MyCoreParallel. CreateUnitAsClient(f1, null,f2,null, null,null);
чую-чую-чую! Вдохновлялись винапи?

Ну и не обижайтесь, но форматирование, именование, завязка на WPF и то, что минимальный пример занимает 160 строк, в котором происходит всякое непонятное непотребство, намекают на общее качество этой библиотеки.

Нужно показать собственно 2 вещи, о чем вам уже говорят неделю. Сначала код обычный, с использованием стандартной библиотеки. Где все всё знают и сразу из кода поймут, что происходит (хотя, конечно, словами сначала нужно рассказать, чтобы суть смотреть, а не разбираться в нюансах), причем желательно не более 100 строк, а потом "и вот приходит наша многопоточная библиотечка и помогает!", строк 50. Из чего делаем вывод, что она вся такая хорошая и т.п.

Пример может быть простым - 1-2 кнопки на формочке, или пара текстбоксов, что угодно, что умещается в ~100 строк.

___________________________________________

Пример приведу. Как в .Net преобразовать структуру в набор байт? Причем любую структуру? Ну примерно так:

Код C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        var data = GetSomeData();
        byte[] rawData = new byte[Marshal.SizeOf(data)];
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(data, rawDataPtr, false);
        }
        finally
        {
            handle.Free();
        }
        
        SomeType result;
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            result = (SomeType) Marshal.PtrToStructure(rawDataPtr, typeof(SomeType ));
        }
        finally
        {
            handle.Free();
        }
А вот как можно то же самое сделать с помощью моей мини-библиотечки:


Код C#
1
2
3
var data = GetSomeData();
byte[] rawData = data.Serialize();
SomeType result = StructInterOp.Deserialize<SomeType>(rawData);
Причем это будет работать и быстрее, и по памяти лучше. Из чего делаем вывод, что библиотечка хорошая и ей можно пользоваться.
Вопрос: Пример простого многопоточного приложения.

Пример посторения простого многопоточного приложения.

Рожден о причине большого числа вопросов о построении многопоточных приложений в Delphi.

Цель данного примера - продемонстрировать как правильно строить многопоточное приложение, с выносом длительной работы в отдельный поток. И как в таком приложении обеспечить взаимодействие основного потока с рабчим для передачи данных из формы (визуальных компонентов) в поток и обратно. 

Пример не прретендует на полноту, он лишь демонстрирует наиболее простые способы взаимодействия потоков. Позволяя пользователю "быстренько слепить" (кто бы знал как я этого не люблю) правильно работающее многопоточное приложение.
В нем все подробно (на мой взгляд) прокоментированно, но, если будут вопросы, задавайте.
Но еще раз предостерегаю: Потоки - дело не простое. Если Вы не представляете как все это работает, то есть огромная опасность что часто у Вас все будет работать нормально, а иногда программа будет вести себя более чем странно. Поведение неправильно написанной многопотчной программы очень сильно зависит от большого кол-ва факторов, которые порою невозможно воспроизвести при отладке.

Короче, читайте документацию, и статью  :)

Итак пример. Для удобства поместил и код, и прикрепил архив с кодом модуля и формы
Код

unit ExThreadForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

// константы используемые при передаче данных из потока в форму с помощью
// отсылки оконных сообщений
const
  WM_USER_SendMessageMetod = WM_USER+10;
  WM_USER_PostMessageMetod = WM_USER+11;

type
  // описание класса потока, потомка от tThread
  tMyThread = class(tThread)
    private
      SyncDataN :Integer;
      SyncDataS :String;
      procedure SyncMetod1;
    protected
      procedure  Execute; override;
    public
      Param1 :String;
      Param2 :Integer;
      Param3 :Boolean;
      Stopped :Boolean;
      LastRandom :Integer;
      IterationNo :Integer;
      ResultList :tStringList;

      constructor Create (aParam1 :String);
      destructor  Destroy; override;
  end;

  // описание класса использующей поток формы
  TForm1 = class(TForm)
    Label1: TLabel;
    Memo1: TMemo;
    btnStart: TButton;
    btnStop: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    CheckBox1: TCheckBox;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
  private
    { Private declarations }
    MyThread :tMyThread;
    procedure EventMyThreadOnTerminate (Sender :tObject);
    procedure EventOnSendMessageMetod (var Msg: TMessage);message WM_USER_SendMessageMetod;
    procedure EventOnPostMessageMetod (var Msg: TMessage); message WM_USER_PostMessageMetod;

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{
  Stopped - демонстрирует передачу данных от формы к потоку.
    Дополнительной синхронизации не требует, поскольку является простым
    однословным типом, и пишется только одним потоком.
}

procedure TForm1.btnStartClick(Sender: TObject);
begin
  Randomize(); // обеспечение случайнсти в последовательности по Random() - к потоком отношения не имеет

  // Создание экземпляра объекта потока, с передачей ему входного параметра
    {
    ВНИМАНИЕ!
    Конструктор потока написан таким образом что поток создается
    приостановленным, поскольку это позволяет:
    1. Контролировать момент его запуска. Это почти всегда удобнее, т.к.
    позволяет еще до запуска настроить поток, передать ему входные
    параметры, и т.п.
    2. Т.к. ссылка на созданный объект будет сохранена в поле формы, то
    после самоуничтожения потока (см.ниже) которое при запущенном потоке
    может произойти в любой момент, эта ссылка станет недействительной.
    }
  MyThread := tMyThread.Create(Form1.Edit1.Text);

  // Однако, поскольку поток создан приостановленным, то при любых ошибках
  // во время его инициализации (до запуска), мы должны его сами уничтожить
  // для чегу используем try / except блок
  try

    // Назначение обработчика завершения потока в котором будем принимать
    // результаты работы потока, и "затирать" ссылку на него
    MyThread.OnTerminate := EventMyThreadOnTerminate;

    // Поскольку результаты будем забирать в OnTerminate, т.е. до самоуничтожения
    // потока то снимем с себя заботы по его уничтожению
    MyThread.FreeOnTerminate := True;

    // Пример передачи входных параметров через поля объекта-потока, в точке
    // создания экземпляра, когда он еще не запущен.
    // Лично я, предпочитаю делать это через параметры переопределяемого
    // конструктора (tMyThread.Create)
    MyThread.Param2 := StrToInt(Form1.Edit2.Text);

    MyThread.Stopped := False; // своего рода тоже параметр, но меняющийся во
                               // время работы потока
  except
    // поскольку поток еще не запущен и не сможет самоуничтожиться, уничтожим его "вручную"
    FreeAndNil(MyThread);
    // а дальше пусть исключительная ситуация обрабатывается обычным порядком
    raise;
  end;

  // Поскольку объект потока успешно создан и настроен, настало время запустить его
  MyThread.Resume;

  ShowMessage('Поток запущен');
end;

procedure TForm1.btnStopClick(Sender: TObject);
begin
  // Если экземпляр потока еще существует, то попросим его остановиться
  // Причем, именно "попросим". "Заставить" в принципе тоже можем, но это будет
  // исключительно аварийный вариант, требующий четкого понимания всей этой
  // потоковой кухни. Поэтому, здесь не рассматривается.
  if  Assigned(MyThread) then
    MyThread.Stopped := True
  else
    ShowMessage('Поток не запущен!');
end;

procedure TForm1.EventOnSendMessageMetod(var Msg: TMessage);
begin
  // метод обработки синхронного сообщения
  // в WParam адрес объекта tMyThread, в LParam тек.значение LastRandom потока
  with  tMyThread(Msg.WParam)  do begin
    Form1.Label3.Caption := Format('%d %d %d',[IterationNo,LastRandom,Msg.LParam]);
  end;
end;

procedure TForm1.EventOnPostMessageMetod(var Msg: TMessage);
begin
  // метод обработки асинхронного сообщения
  // в WParam тек.значение IterationNo, в LParam тек.значение LastRandom потока
  Form1.Label4.Caption := Format('%d %d',[Msg.WParam,Msg.LParam]);
end;

procedure TForm1.EventMyThreadOnTerminate (Sender :tObject);
begin
  // ВАЖНО!
  // Метот обработки события OnTerminate всегда вызывается в контексте основного
  // потока - это гарантируется реализацией tThread. Поэтому, в нем можно свободно
  // использовать любые свойства и методы любых объектов

  // На всякий случай, убедимся что экземпляр объекта еще существует
  if  not Assigned(MyThread) then Exit; // если его нет, то и делать нечего

  // получение результатов работы потока экземпляра объекта потока
  Form1.Memo1.Lines.Add(Format('Поток завершился с результатом %d',[MyThread.IterationNo]));
  Form1.Memo1.Lines.AddStrings((Sender as tMyThread).ResultList);

  // Уничтожение ссылки на экземпляр объекта потока.
  // Поскольку поток у нас самоуничтожающийся (FreeOnTerminate := True)
  // то после завершения обрабтчика OnTerminate, экземпляр объекта-потока будет
  // уничтожен (Free), и все ссылки на него станут недействительными.
  // Что бы случайно не напороться на такую ссылку, затрем MyThread
  // Еще раз замечу - не уничтожим объект, а только затрем ссылку. Объект
  // уничтожится сам!
  MyThread := Nil;
end;

{ tMyThread }

constructor tMyThread.Create (aParam1 :String);
begin
  // Создаем экземпляр ПРИОСТАНОВЛЕННОГО потока (см.коментарий при создании экземпляра)
  inherited Create(True);

  // Создание внутренних объектов (если необходимо)
  ResultList := tStringList.Create;

  // Получение исходных данных.

  // Копирование входных данных переданных через параметр
  Param1 := aParam1;

  // Пример получения входных данных из VCL-компонентов в конструкторе объекта-потока
  // Такое в данном случае допустимо, поскольку конструктор вызывается в контексте
  // основного потока. Следовательно, здесь можно обращаться к VCL-компонентам.
  // Но, я такого не люблю, поскольку считаю что плохо когда поток знает что-то
  // о какой-то там форме. Но, чего не сделаешь для демонстрации.
  Param3 := Form1.CheckBox1.Checked;
end;

destructor tMyThread.Destroy;
begin
  // уничтожение внутренних объектов
  FreeAndNil(ResultList);
  // уничтожение базового tThread
  inherited;
end;

procedure tMyThread.Execute;
var
  t :Cardinal;
  s :String;
begin
  IterationNo := 0; // счетчик результатов (номер цикла)

  // В моем примере тело потока представляет собой цикл, который завершается
  // либо по внешней "просьбе" завершиться передаваемый через изменяемый параметр Stopped,
  // либо просто совершив 5 циклов
  // Мне приятнее такое записывать через "вечный" цикл.

  while  True  do begin

    Inc(IterationNo); // очередной номер цикла

    LastRandom := Random(1000); // слючайное число - для демонстрации передачи параметров от потока в форму

    t := Random(5)+1; // время на которое будем засыпать если нас не завершат

    // Тупая работа (зависящая от входного параметра)
    if  not Param3 then
      Inc(Param2)
    else
      Dec(Param2);

    // Сформируем промежуточный результат
    s := Format('%s %5d %s %d %d',
                 [FormatDateTime('hh:nn:ss:zzz',Now) // время записи
                 ,IterationNo                        // номер цикла
                 ,Param1                             // то что было в параметре
                 ,Param2                             // текущий результат работы
                 ,t                                  // время на которое предполагаем заснуть
                 ]);

    // Добавим промежуточный результат к списку резуольтатов
    ResultList.Add(s);

    //// Примеры передачи промежуточного результата на форму

    //// Передача через синхронизируемый метод - классический способ
    //// Недостатки:
    ////   - синхронизируемый метод - это обычно метод класса потока (для доступа
    ////     к полям объекта-потока), но, для доступа к полям формы, он должен
    ////     "знать" про нее и ее поля (объекты), что обычно не очень хорошо с
    ////     точки зрения организации программы.
    ////   - текущий поток будет приостановлен до завершения выполнения
    ////     синхронизированного метода.
    ////   - требует существенных затрат процессорного времени на каждый вызов
    ////     (на переключение потоков) поэтому нежелателен очень частый вызов
    //// Достоинства:
    ////   - стандартность и универсальность
    ////   - в синхронизированном методе можно пользоваться
    ////     всеми полями объекта-потока.
    //
    // сначала, если необходимо, надо сохранить передаваемые данные в
    // специальных полях объекта объекта.
    SyncDataN := IterationNo;
    SyncDataS := 'Sync'+s;
    // и затем обеспечить синхронизированный вызов метода
    Synchronize(SyncMetod1);

    //// Передача через синхронную отсылку сообщения (SendMessage)
    //// в этом случае, данные можно передать как через параметры сообщения (LastRandom),
    //// так и через поля объекта, передав в параметре сообщения адрес экземпляра
    //// объекта-потока - Integer(Self).
    //// Недостатки:
    ////   - поток должен знать handle окна формы
    ////   - как и при Synchronize, текущий поток будет приостановлен до
    ////     завершения обработки сообщения основным потоком
    ////   - требует существенных затрат процессорного времени на каждый вызов
    ////     (на переключение потоков) поэтому нежелателен очень частый вызов
    //// Достоинства:
    ////   - как и при Synchronize, при обработке сообщения можно пользоваться
    ////     всеми полями объекта-потока (если конечно был передан его адрес)
    ////   - в отличии от синхронизированного вызова, обработчиком сообщения
    ////     является метод формы, который должен иметь знания об объекте-потоке,
    ////     или вовсе ничего не знать о потоке, если данные передаеются только
    ////     через параметры сообщения. Т.е., поток может ничего не знать о форме
    ////     вообще - только ее Handle, который может быть передан как параметр до
    ////     запуска потока.
    SendMessage(Form1.Handle,WM_USER_SendMessageMetod,Integer(Self),LastRandom);

    //// Передача через асинхронную отсылку сообщения (PostMessage)
    //// Поскольку в этом случае к моменту получения сообщения основным потоком,
    //// посылающий поток может уже завершиться, передача адреса экземпляра
    //// объекта-потока недопустима!
    //// Недостатки:
    ////   - поток должен знать handle окна формы;
    ////   - из-за асинхронности, передача данных возможна только через параметры
    ////     сообщения, что существенно усложняет передачу данных имеющих размер
    ////     более двух машинныхх слов. Удобно применять для передачи Integer и т.п.
    //// Достоинства:
    ////   - в отличие от предыдущих методов, текущий поток НЕ будет
    ////     приостановлен, а сразу же продолжит свое выполнение
    ////   - в отличии от синхронизированного вызова, обработчиком сообщения
    ////     является метод формы, который должен иметь знания об объекте-потоке,
    ////     или вовсе ничего не знать о потоке, если данные передаеются только
    ////     через параметры сообщения. Т.е., поток может ничего не знать о форме
    ////     вообще - только ее Handle, который может быть передан как параметр до
    ////     запуска потока.
    PostMessage(Form1.Handle,WM_USER_PostMessageMetod,IterationNo,LastRandom);



    //// Проверка возможного завершения

    // Проверка завершения по параметру
    if  Stopped  then  Break;

    // Проверка завершения по случаю
    if  IterationNo >= 10 then  Break;



    Sleep(t*1000); // Засыпаем на t секунд
  end;
end;

procedure tMyThread.SyncMetod1;
begin
  // этот метод вызывается посредством метода Synchronize.
  // Т.е., не смотря на то что он является методом потока tMyThread,
  // он выполняется в контексте основного потока приложения.
  // Следовательно, ему все можно, ну или почти все :)
  // Но помним, здесь не стоит долго "возиться"

  // Переданные параметры, мы можем извлечь из специальных поле, куда мы их
  // сохранили перед вызовом.
  Form1.Label1.Caption := SyncDataS;

  // либо из других полей объекта потока, например отражающих его тек.состояние
  Form1.Label2.Caption := Format('%d %d',[IterationNo,LastRandom]);
end;

end.


А вообще, примеру предшествовали следующие мои рассуждения на тему....

Во первых: 
ВАЖНЕЙШЕЕ правило многопоточного программирования на Delphi:
В контексте не основного потока нельзя, обращаться к свойствам и методам форм, да и вообще всех компонентов которые "растут" из tWinControl.


Это означает (несколько упрощенно) что ни в методе Execute унаследованного от TThread, ни в других методах/процедурах/функциях вызываемых из Execute, нельзя напрямую обращаться ни к каким свойствам и методам визуальных компонентов.

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

Если коротенько на пальцах:

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

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

Во втором случае, когда работа подразумевает активный обмен с внешним миром, то во время вынужденных «простоев». В ожидании получения/отправки данных, можно параллельно делать еще что-то, например, опять же другие посылать/принимать данные.

Существуют и другие случаи, но реже. Впрочем, это и не важно. Сейчас не об этом.

Теперь, как все это пишется. Естественно рассматривается некий наиболее частый случай, несколько обобщенный. Итак.

Работа, выносимая в отдельный поток, в общем случае имеет четыре сущности (уж и не знаю как назвать точнее):
1.    Исходные данные
2.    Собственно сама работа (она может зависеть от исходных данных)
3.    Промежуточные данные (например, информация о текущем состоянии выполнения работы)
4.    Выходные данные (результат)

Чаще всего для считывания и вывода большей части данных используются визуальные компоненты. Но, как было сказано выше – нельзя из потока напрямую обращаться к визуальным компонентам. Как же быть?
Разработчики Delphi предлагают использовать метод Synchronize класса TThread. Здесь я не буду описывать то, как его применять – для этого есть вышеупомянутая статья. Скажу лишь, что его применение, даже правильное, не всегда оправдано. Имеются две проблемы:

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

Во вторых, выполнение метода через Synchronize, это «дорогое» удовольствие, вызванное необходимостью двух переключений между потоками.

Причем, обе проблемы взаимосвязаны, и вызывают противоречие: с одной стороны, для решения первой, надо «размельчать» методы вызываемые через Synchronize, а с другой, их тогда чаще приходится вызывать, теряя драгоценный процессорный ресурс.

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

Исходные данные
Все данные которые передаются в поток, и не изменяются во время его работы, нужно передавать еще до его запуска, т.е. при создании потока. Для их использования в теле потока, нужно сделать их локальную копию (обычно в полях потомка TThread).
Если есть исходные данные которые могут меняться во время работы потока, то доступ к таким данным нужно осуществлять либо через синхронизируемые методы (методы вызываемые через Synchronize), либо через поля объекта-потока (потомка TThread). Последнее требует определенной осторожности.

Промежуточные и выходные данные
Здесь, опять же есть несколько способов (в порядке моих предпочтений):
-    Метод асинхронной отсылки сообщений главному окну приложению. 
Используется обычно для отсылки основному окну приложения сообщений о состоянии протекания процесса, с передачей незначительного объема данных (например, процента выполнения)
-    Метод синхронной отсылки сообщений главному окну приложению.
Используется обычно для тех же целей что и асинхронная отсылка, но позволяет передать больший объем данных, без создания отдельной копии.
-    Синхронизируемые методы, по возможности, объединяя в один метод передачу как можно большего объема данных.
Можно использовать и для получения данных с формы.
-    Через поля объекта-потока, обеспечением взаимоисключающего доступа.
Подробнее, можно почитать в статье.

Эх. Коротенько опять не получилось 


Это сообщение отредактировал(а) Петрович - 17.7.2007, 15:12

Присоединённый файл ( Кол-во скачиваний: 214 )
 
Ответ:
Уважаемый коллега!
Не подскажите о тонкостях организации многопоточного консольного приложения в Delphi. Необходимо организовать параллельную работу многопоточного приложения на многоядерном процессоре (для конкретики 4 ядра) в режиме распараллеливания по данным (SPMD-модель). Алгоритм в кратце таков: корневой процесс считывает данные из файла, рассылает их по потокам. Затем каждый поток обрабатывает свою порцию данных. После чего корневой процесс собирает данные и выводит их в файл результатов. 
P.S.
Был опыт параллельного программирования на вычислительном кластере параллельной архитектуры в системе MPI (Message Passing Interface). Но это было давно.
Заранее спасибо.
Вопрос: Финальный экзамен в курсе "Многопоточное программирование на С/С++" (stepic)

Работа большая, оценивается в 300 баллов.

1. Нужно создать проект на github и написать веб-сервер. Протокол HTTP 1.0, нужно реализовать только команду GET (POST - опционально), ответы 200 и 404, а также MIME-тип text/html (другие типы, например image/jpeg - опционально).

2. Запустить виртуальную машину и сохранить http-путь до вашего github репозитория в файле /home/box/final.txt

3. Первым делом тестовая среда проверяет наличие этого файла и сама клонирует репозиторий в /home/box/final.

4. Проект должен собираться с помощью cmake (изучается самостоятельно). На виртуалке стоит cmake версии 2.8. Тестовая среда в папке /home/box/final выполняет команды:

cmake .
make
5. В результате сборки должен появиться исполняемый файл /home/box/final/final - ваш веб-сервер. Тестовая среда проверяет его наличие.

6. Веб-сервер должен запускаться командой:

/home/box/final/final -h <ip> -p <port> -d <directory>
Для парсинга параметров командной строки используйте getopt (изучается самостоятельно). После запуска сервер обязан вернуть управление, то есть должен демонизироваться. Иначе тесты встанут и отвалятся по таймауту!

7. Тестовая среда исполняет HTTP-запросы.

8. Самое главное. Сервер должен быть или многопоточным или многопроцессным с передачей дескрипторов.

9. Следующие трюки считаются читерством:

Держать в памяти настроенный nginx (или, например, apache) при наличии программы-заглушки в github.
Использовать реализацию HTTP-протокола из libevent.

Успехов!

Кто еще не затащил, не стесняйтесь обсуждать.
Ответ:
Сообщение от ture
Я не знаю, куда двигать дальше.
Не с того вопроса начинаешь. Сначала подумай, чего хочешь.
Вопрос: Умножение матриц с использоанием многопоточного программирования

Нужно переделать однопоточное в многопоточное
C#
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[,] m1 = new int[10, 10]; //объяление матрицы 1
            int[,] m2 = new int[10, 10];//объяление матрицы 2
            int[,] m3 = new int[10, 10]; //объяление произведения матрицы
            Random ran = new Random();  // Заполнение случайными числами
 
            for (int m = 0; m < 10; m++)
            {
 
                for (int je = 0; je < 10; je++)
                {
                    m1[m, je] = ran.Next(30); // ran.Next(функция задает максимально допустимое значение);
                    m2[m, je] = ran.Next(30);
                }
            }
 
            for (int m = 0; m < 10; m++)
            {
                for (int je = 0; je < 10; je++)
                {
                    m3[m, je] = m1[m, je] * m2[m, je]; // Произведение матриц
                }
            }
 
 
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Матриц m1 и m2:");
                for (int i = 0; i < 10; i++)
                {
                    for (int j = 0; j < 10; j++)
                    {
                        Console.Write(m1[i, j].ToString("000") + " "); // вывод в консоль м1
                    }
                    Console.WriteLine();
                }
            }
 
            Console.WriteLine("Матриц m2:");
            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    Console.Write(m2[i, j].ToString("000") + " "); // вывод в консоль м2
                }
                Console.WriteLine();
            }
            Console.WriteLine("Произведение двумерных матриц m1 и m2:");
            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    Console.Write(m3[i, j].ToString("000") + " "); // вывод в консоль *
                }
                Console.WriteLine();
            }
            Console.ReadKey();
        }
 
 
        }
    }
Ответ:

Добавлено через 50 секунд
Вопрос: Асинхронное vs. Многопоточное программирование

Здравствуйте. Изучая темы многопоточности и асинхронного программирования, у меня возник вопрос. В чем разница вызвать метод асинхронно или в отдельном потоке?
Ответ:
Сообщение от 8Observer8
Вот к примеру механизм await/async - это и многопоточность и асинхронность?
Это в первую очередь асинхронность.
Многопоточность ли — это зависит исключительно от реализации асинхронного метода.

Сообщение от 8Observer8
Ведь этот механизм преобразуется в ассинхронные методы обратного вызова, а сами операции запускаются во многих потоках?
Механизм преобразуется в конечный автомат, в котором проверяется наличие результата и выполняется продолжающее действие.
Наличие отдельного потока там не требуется.

Сообщение от 8Observer8
а сама асинхронная операция начинает выполняться в другом потоке.
Не обязательно
Вопрос: Многопоточное программирование

Приветствую!

Написала программу умножения матриц, теперь нужно сделать его параллельным.
Хочу распараллелить вычисление строк, т.е. в функцию умножения предстоит передача номера строки.

Не могу разобраться, где в моем случае будет находиться run() - вместо функции Multiply (которая в свою очередь будет вычислять не всю матрицу, а только строку)?

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

Заранее спасибо за участие.
Кликните здесь для просмотра всего текста

Код Java(TM) 2 Platform Standard Edition 5.0
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
package javaapplication2;
 
import java.util.Random;
import static javaapplication2.javaapplication2.Matrix.Multiply;
/**
 *
 * @author User
 */
public class javaapplication2 {
 
    public static class Matrix 
    {
        public int[][] matrix;
        public int height;
        public int width;
        Random rand=new Random();
        
        public Matrix(int height, int width)
        {
           this.height=height;
           this.width=width;
           this.matrix=new int [height][width];
        }
        
        public void fillRand ()
        {
           for (int i=0; i<height; i++)
            {
                for (int j=0; j<width; j++)
                {
                    matrix[i][j]=rand.nextInt(10);
                }
            }
        }
        
        public void print ()
        {
            System.out.println("printing");
            for (int i=0; i<height; i++)
            {
                for (int j=0; j<width; j++)
                {
                    System.out.print(matrix[i][j]+" ");
                }
                System.out.println();
            }
        }
        
        public void set (int i, int j, int value)
        {
            matrix[i][j]=value;
        }
        
        public int get (int i, int j)
        {
            return matrix[i][j];
        }
        
        public static Matrix Multiply (Matrix mat_1, Matrix mat_2)
        {
         int h1=mat_1.height;
         int w1=mat_1.width;
         int w2=mat_2.width;
         Matrix mat_fin=new Matrix(h1, w2);
         int sum;
         for (int i=0; i<h1; i++)
         {
           for (int j=0; j<w2; j++)
           {
               sum=0;
               for (int w=0; w<w1; w++){
                  sum+=mat_1.get(i, w)*mat_2.get(w, j);
               }
               //System.out.println("sum"+i+" ,"+j+"="+sum);
               mat_fin.set(i, j, sum);
           }
       }
        return mat_fin;
    }
     }
    
    
     /**
     * @param args the command line arguments
     */    
    
    public static void main(String[] args) {
        // TODO code application logic here
        
        int m, n, k;
        m=200;
        n=300;
        k=400;
        Matrix mat_1=new Matrix(m, n);
        Matrix mat_2=new Matrix(n, k);
        //Matrix mat_fin=new Matrix(m, k);
        
        mat_1.fillRand();
        mat_2.fillRand();
        
        mat_1.print();
        mat_2.print();
        
        Matrix mat_fin=Multiply(mat_1, mat_2);
               
        mat_fin.print();
             
        
    }
    
}
Ответ: Распараллеливание вычисления одного значения результирующей матрицы будет более оптимальным, так как неизвестно количество строк результирующей матрицы. Может оказаться всего одна строка, что нивелируюет преимущество многопоточного умножения.


Матрица (без проверок границ):
Matrix
Код Java(TM) 2 Platform Standard Edition 5.0
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
import java.util.Arrays;
 
public class Matrix {
 
    private final double[][] data;
 
    public Matrix(int height, int width) {
        this.data = new double[height][width];
    }
 
    public Matrix(double[][] data) {
        this.data = copy(data);
    }
 
    public int getHeight() {
        return data.length;
    }
 
    public int getWidth() {
        return data[0].length;
    }
 
    public double get(int row, int col) {
        return data[row][col];
    }
 
    public void set(int row, int col, double value) {
        data[row][col] = value;
    }
 
    /**
     * Return a new copy of specified row
     *
     * @param n row number to copy
     * @return a new instance of double array
     */
    public double[] getRow(int n) {
        return Arrays.copyOf(data[n], data[n].length);
    }
 
    /**
     * Return a new copy of specified column
     *
     * @param n column number to copy
     * @return a new instance of double array
     */
    public double[] getColumn(int n) {
        double [] result = new double[data.length];
        for (int i = 0; i < data.length; ++i) {
            result[i] = data[i][n];
        }
        return result;
    }
 
    /**
     * Returns a copy of matrix data in form of a two-dimension array
     *
     * @return a new two-dimension array of doubles
     */
    public double[][] getAsDoubleArray() {
        return copy(data);
    }
 
    private double[][] copy(double[][] data) {
        double[][] result = new double[data.length][];
        for (int i = 0; i < data.length; ++i) {
            result[i] = Arrays.copyOf(data[i], data[i].length);
        }
        return result;
    }
 
    @Override
    public String toString() {
        return "Matrix{" +
                "data=" + Arrays.deepToString(data) +
                '}';
    }
}


Операции над матрицами. Генерация случайной матрицы, умножение матриц в один поток, умножение матриц в несколько потоков. Количество потоков зависит от количества процессоров.
Matrices
Код Java(TM) 2 Platform Standard Edition 5.0
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
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
public final class Matrices {
 
    public static final Random RANDOM = new SecureRandom();
 
    private Matrices() {
    }
 
    public static Matrix generate(int height, int width, int min, int max) {
        final Matrix result = new Matrix(height, width);
        for (int i = 0; i < height; ++i) {
            for (int j = 0; j < width; ++j) {
                result.set(i, j, min + RANDOM.nextInt(max - min));
            }
        }
        return result;
    }
 
    public static Matrix multiply(Matrix a, Matrix b) {
        if (a.getWidth() != b.getHeight()) {
            throw new IllegalArgumentException("first matrix width should be equal to second matrix height");
        }
        final Matrix result = new Matrix(a.getHeight(), b.getWidth());
        for (int i = 0; i < a.getHeight(); ++i) {
            for (int j = 0; j < b.getWidth(); ++j) {
                for (int r = 0; r < a.getWidth(); ++r) {
                    result.set(i, j, result.get(i, j) + a.get(i, r) * b.get(r, j));
                }
            }
        }
        return result;
    }
 
    public static Matrix parallelMultiply(final Matrix a, final Matrix b) throws InterruptedException {
        if (a.getWidth() != b.getHeight()) {
            throw new IllegalArgumentException("first matrix width should be equal to second matrix height");
        }
        final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        final Matrix result = new Matrix(a.getHeight(), b.getWidth());
        for (int i = 0; i < a.getHeight(); ++i) {
            for (int j = 0; j < b.getWidth(); ++j) {
                executorService.submit(new ParallelMatrixMultiplier(a, b, result, i, j));
            }
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.DAYS);
        return result;
    }
 
    private static class ParallelMatrixMultiplier implements Runnable {
        private final Matrix a;
        private final Matrix b;
        private final Matrix result;
        private final int i;
        private final int j;
 
        private ParallelMatrixMultiplier(Matrix a, Matrix b, Matrix result, int i, int j) {
            this.a = a;
            this.b = b;
            this.result = result;
            this.i = i;
            this.j = j;
        }
 
        @Override
        public void run() {
            for (int r = 0; r < a.getWidth(); ++r) {
                result.set(i, j, result.get(i, j) + a.get(i, r) * b.get(r, j));
            }
        }
    }
 
}


Проверочный класс, измеряющий время умножения двух крупных матриц в один поток и в несколько потоков.
(на четырех процессорных системах многопоточное умножение должно быть раза в два быстрее)
Launcher
Код Java(TM) 2 Platform Standard Edition 5.0
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
public class Launcher {
 
    private static volatile Matrix result;
 
    private static void multiplyMatrix() {
        final Matrix a = Matrices.generate(700, 700, 0, 4);
        final Matrix b = Matrices.generate(700, 700, 0, 4);
        result = Matrices.multiply(a, b);
    }
 
    private static void multiplyMatrixParallel() {
        final Matrix a = Matrices.generate(700, 700, 0, 4);
        final Matrix b = Matrices.generate(700, 700, 0, 4);
        try {
            result = Matrices.parallelMultiply(a, b);
        } catch (InterruptedException e) {
            throw new IllegalStateException("This should never happen");
        }
    }
 
    private static long measure(Runnable runnable) {
        final long start = System.currentTimeMillis();
        runnable.run();
        return System.currentTimeMillis() - start;
    }
 
    public static void main(String[] args) throws InterruptedException {
        long a = measure(Launcher::multiplyMatrix);
        long b = measure(Launcher::multiplyMatrixParallel);
 
        System.out.printf("Single threaded multiplication: %dms, multi-threaded multiplication: %dms%n", a, b);
    }
}
Вопрос: С помощью чего пишутся многопоточные программы?

а с использованием чего осуществляется многопоточное программирование под линукс, библиотека, функции,
а то я смотрел книжку, так там много чего есть я так понял

Добавлено через 3 минуты
тоже самое относится к межпроцессному взаимодействию.
Ответ: 1. Для создания потоков используется pthread. Это Сишная библиотека.
В плюсах с 11-го стандарта используется std::thread. В *никсах вроде как основана на pthread как раз.

2. Для межпроцессного взаимодействия -- pipes + fork + (иногда) exec*. И тоже это Си.
Вопрос: Многопоточная обработка данных + ввод/вывод

Доброго времени суток! Пишу небольшое консольное приложение, и у меня возникли некоторые вопросы. Надеюсь, кто-нибудь мне поможет.

Суть приложения в следующем. Нужно организовать многопоточное сжатие файла большого размера (превышающего размер RAM). И использовать нужно .NET версии не выше 3.5 (соответственно никаких классов Task, Parallel и т.д.)

Я решил в этом деле применить шаблон поставщик/потребитель. Создаем несколько рабочих потоков, по одному на каждое ядро процессора (или число потоков следует выбирать иначе?). Основной поток считывает входной файл по блокам, и раздает их рабочим потокам, которые возвращают сжатые блоки для записи. На словах все достаточно просто. Но при реализации возникли следующие вопросы.

1. Как правильно реализовать очередь из блоков? Это может быть одна очередь, либо же отдельная очередь на каждый поток (лично я считаю последний вариант более оптимальным).
2. На блоки какого размера следует разбивать файл? Хотя этот вопрос не очень насущный, потому как это легко можно определить тестами.
3. Нумерация блоков. Так как в сжатый файл блоки нужно поместить в том же порядке, в котором они были считаны их исходного файла, то их необходимо нумеровать. Хорошо. Записали пронумерованные блоки в очередь (или в очереди), откуда их будет извлекать записывающий поток. Тут-то и проблема. Учитывая, что запись и так самое узкое место, записывающему потоку еще придется выискивать нужные блоки для организации правильного порядка записи. А если какой-то из блоков запоздает, то совсем плохая картина получается. И, собственно, сам вопрос: Как быть?
4. И, пожалуй, самый трудный вопрос. Как правильно организовать подачу этих самых блоков? Чтобы очередь на запись не переполнялась, а очередь на сжатие никогда не оставалась пустой.
5. И, наконец, как нужно выбирать размер памяти, отведенный приложению? Как это делаю нормальные программисты? Или без зазрения совести занимать всю свободную?
Ответ:
Сообщение от eg__13
многопоточное сжатие файла большого размера (превышающего размер RAM)
Начать следует с грамотного (строчного, блочного и\или многопоточного) прочтения огромадного файла.
Остальное приложиться - потоки, очереди, сжатие - исходников до кучи.