Упрощаем разработку многопоточных программ [Delphi]

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by GhostOnline, 21 Jan 2011.

  1. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Здравствуй Ачат.

    Как известно, в более-менее серьезных многопоточных приложениях(МП)(где множество потоков выполняют одну задачу),
    набирается большое количество разнообразных данных, доступ к которым необходимо синхронизировать. Это могут быть простые типы данных, строки,
    объекты, списки, потоки в памяти, файлы и т.п. Если при этом использовать для всего одну критическую секцию(далее КС),
    то это скажется на производительности - зачем блокировать доступ и к объекту А, если мы хотим всего лишь получить объект Б?
    По-этому я считаю что лучше для каждого экземпляра создавать свою КС. Но этот подход является довольно утомительным: надо создавать много КС,
    писать код входа-выхода для каждой из них. При этом всегда есть возможность ошибиться, "перепутать" к какому объекту относится данная Кс,
    и совершить тем самым дедлок, и попробуй потом найти ошибку - мультитредные проги отлаживать на порядок сложнее.
    К тому же, мне очень нравится оператор lock в C#. С появлением generics в Delphi(2009 и выше) я понял что можно все это упростить. И ниже описано как.
    Прилагаемый к статье проект также может пригодиться:
    1. Наглядный пример создания МП для новичов.
    2. Пример использования дженериков для тех, кто еще не знаком с этим.
    3. Пример использования анонимных методов(TThread.Synchronize), что также сильно упрощает синхронизацию при доступе
    к VCL компонентам из потоков(об этом отдельно).

    Объявляем класс как показано ниже:
    PHP:
      // основной родительский класс, базовые определения
      
    TThreadSafe<T> = class
      
    strict protected
        
    FValueT;
        
    FCritSecTCriticalSection// экземпляр критической секции, будет использоваться в наследниках
      
    public
        function 
    LockT// функция возвращающая значение и блокирующая его
        
    procedure Unlock// процедура разблокировки

        
    procedure Write(AvalueT); // запись значение БЕЗ блокировки
        
    function ReadT// чтение  БЕЗ блокировки

        
    procedure SafeWrite(AvalueT); // запись с блокировкой, в аргументе - новое значение

        
    constructor Create(AValueT); // аргумент - значение для инициализации
        
    destructor Destroyoverride;
      
    end;
    Механизм работы таков: мы объявляем и создаем экземпляр класса с ЛЮБЫМ интересующим нас типом, будь то string, real, TDateTime, TFileStream
    или любым пользовательским(т.е. созданным вами). <T> тут - указание компилятору что автор класса заранее не знает
    с каким типом будут работать его экземпляры. Этот конкретный тип будет определяться пользователем класса и компилятор
    просто его подставит вместо <T>. И далее мы просто используем его например так:

    PHP:
    var
        
    SafeIntegerTThreadSafe<Integer>;
        
    SafeMemoryStreamTThreadSafe<TMemoryStream>;
    begin
        SafeInteger 
    := TThreadSafe<Integer>.Create(666); // тут мы передаем 666 для инициализации числа
        
    SafeMemoryStream := TThreadSafe<TMemoryStream>.Create(TMemoryStream.Create); // инициализируем , можно передать nil 
        
    SafeInteger.SafeWrite(1000); // записываем другое число. в это время доступ блокируется
        // уничтожаем объекты
        
    SafeInteger.Free// для простых типов не нужо еще уничтожать экземпляр, просто удаляем сам объект
        
    SafeMemoryStream.Read.Free// для составных нужно
        
    SafeMemoryStream,Free// удаляем сам объект
    end;
    Чтобы не вносить путаницу я создал три класса:
    Для простых типов добавил свойство Value для удобства
    PHP:
      TSimpleTS<T> = class(TThreadSafe<T>)
      
    strict private
        
    procedure SetValue(AValueT);
        function 
    GetValueT;
      public
        
    property ValueT read GetValue write SetValue;
      
    end;
    Для записей:
    PHP:
        TRecordTS<Trecord> = class(TThreadSafe<T>)
        
    end;
    Для классов:
    PHP:
        TClassTS<T: class> = class(TThreadSafe<T>)
        
    end;
    Определения вроде
    PHP:
      iTClassTS<Integer>;
      
    stringlistTRecordTS<TStringList>;
    НЕ скомпилируются.
    Если вам нужен объект - используйте TClasTS<>, если запись - TRecordTS<>, а для простых типов - TSimpleTS<>.

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

    Для объектов удобно использовать следующую конструкцию:
    PHP:
        with SafeStringList.Lock do
            try
                
    // работаем с объектом, доступ заблокирован
                
    Add('blablabla');
                
    Sorted := True;
            finally
                
    Unlock;
            
    end;
    За исходниками классов и примерами использования смотрите проект прилагаемый к статье.
    -------------------------------------------------------------------------------------------
    Теперь по анонимным методам.
    Все знают что VCL не-потокобезопасна, и для работы с компонентами на форме из потоков разработчики предлагают нам использовать
    метод Synchronize класса TThread. А если в коде потока очень много мест где происходит обращение к форме то приходится создавать
    и много методов и временных полей в класс потока. С приходом анонимных методов наша жизнь облегчилась.
    Вот пример, внутри метода Execute мы пишем:

    PHP:
    Synchronize(procedure
                begin
                    Form1
    .Caption := 'Test';
                    
    Form1.Memo1.lines.Add('parapampam');
                    
    Form1.Tag := 42;
                    
    // и т.д.
                
    end);
    Код между begin и end выполняется в контексте потока VCL. Никаких лишних методов и полей.
    Есть также асинхронный вариант этого метода - Queue:
    PHP:
    Queue(procedure
          begin
            Form1
    .Caption := 'Test';
            
    // и т.д.
          
    end);    
    Разница в том, что код между begin end выполняется не сразу, а только когда VCL поток будет разгружать очередь сообщений.
    Действительно, зачем нам блокировать ход выполнения если мы не хотим ничего "брать" из формы, а просто кинуть строку в мемо?

    Рекомендуемые к прочтению материалы:
    Generics
    Обзор новинок Delphi 2009
    Справка Delphi

    Демо-проект: http://zalil.ru/30640671

    (c) G-[Host](GhostOnline) icq 1501075 для forum.antichat.ru

    PS Мой первый опыт такого рода, прошу высказаться стоит ли мне продолжать
     
    #1 GhostOnline, 21 Jan 2011
    Last edited: 9 Mar 2011
    7 people like this.
  2. Gar|k

    Gar|k Moderator

    Joined:
    20 Mar 2009
    Messages:
    1,166
    Likes Received:
    266
    Reputations:
    82
    Ты бы лучше рассказал что физически процессор держит кол-во потоков = кол-ву ядер или процессоров. В все основы параллельного программирования разрабатывались для распределенных, кластерных систем.
     
    _________________________
  3. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Это здесь причем? Я тут не собирался устраивать ликбез по параллельному программированию. Те кто этого не знает, должны читать книги, изучать основы а не статьи на античате.

    fd00ch
    Свою точку зрения на это изложу завтра, сейчас уже не хочу, т.к. писать буду много.
     
  4. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    под TThreadProcedure подразумевается анонимный метод?
    Вообще да, хорошая идея.
     
  5. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    936
    Likes Received:
    162
    Reputations:
    27
    Я не программирую в Delphi 2009 пока что, но разве в этой части кода нету никакой ошибки?
     
  6. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Ну разве только опечатка - вместо точки запятая
     
  7. fenixelite

    fenixelite Banned

    Joined:
    7 Feb 2010
    Messages:
    294
    Likes Received:
    56
    Reputations:
    6
    Отлично всё расписал! Нужно будет сегодня посмотреть на практике, как это работает.

    И да. Тебе стоит продолжать писать подобного рода статьи. Думаю, многим интересно будет.
     
  8. buket

    buket New Member

    Joined:
    7 Apr 2007
    Messages:
    21
    Likes Received:
    0
    Reputations:
    0
    thread_demo.rar (523.2 КБ)
    Файл удален.
    MD5 ce07a1fb90f0b37e39ff6f3066b30757
    SHA1 95c53b5a344155147e27710192c4cd0201fdb76a
     
  9. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Сэнкс
    Работает так как и должно - уже далеко не один коммерческий проект на этих классах успешно выполнен
    buket
    перезалил
     
  10. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Расписывать реально лень но раз уж так хочется:
    Условные обозначения: ПБ - потокобезасные, КС - крит. секции
    Отсутствие универсальности. Ок, ты реализовал ПБ варианты самых часто используемых классов RTL - коллекции, потоки ввода/вывода, ридеры/райтеры, мб какие-то свои классы. Но рано или поздно ты будешь все-таки либо использовать КС напрямую в пользовательском коде, либо писать ПБ-обертку для одного проекта, "на раз". Ибо задачи в проектах бывают весьма разнообразные. В первом случае получается мешанина подходов внутри одного проекта: тут мы используем свой враппер, а тут с КС работаем напрямую. Не знаю, как для тебя, а у меня смешивание двух методов синхронизации вызывает чувство отсутствия единого стиля и универсальности. Во-втором случае получится просто много ненужной работы. К тому же часто использую сторонние библы. Писать для всего этого врапперы, да еще под все методы и свойства - ужос на крыльях ночи.
    Далее, эта синхронизация просто нужна не для всего и не всегда. Лучше уж пользователю решать когда лочить когда нет.
    С переходом на сишарп я тоже столкнулся с дилеммой - с одной стороны есть lock, с другой такое же искушение писать врапперы для постоянного использования. Подумав решил что проще уж lock юзать, чем реализовывать over 9000 методов например класса List<> вместе со всеми методами-расширениями.
    Мне кажется именно по этим причинам паттерн "потокобезопасная обертка" не сильно распостранен у авторов фрэймворков/либ в отличие от более универсальных решений типа Monitor.

    Кстати написал уже более удобный вариант типа монитора на основе твоей идеи с анонимными методами, все руки никак доходят оформить и дополнить здесь. Дженерики там уже не используются :)
     
  11. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Понравилась фитча, мол потоку не надо дожидаться выполнения функции которая через Queue вызывается, себе для семерки такое прикручу:)
     
  12. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Ну, это конечно очень хорошо что ты собрался сделать. Но опять же, без анонимных методов это все же не так удобно. Наверняка же ты будешь делать либо на основе сообщений виндоуз, либо писать собвственный лесопед с пулом задач разгружаемых по таймеру. Я угадал? :)

    А все-таки, ведь ты же профессиональный разработчик, почему бы и не перейти наконец с 7-ки? Да, она популярна, ибо в конторах лицензия на нее с 2002 года, на более новые не переходят ибо выбирают уже вижуал_студио с сишарпом, а поддерживать старый быдлокод можно и на семерке. Но ведь ты скорее всего юзаешь дельфю с торрентов. Я угадал? :)
     
    #12 GhostOnline, 14 Mar 2011
    Last edited: 14 Mar 2011
  13. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Ну да, я просто переделаю Classes и Forms
    Ой, спасибо:) Я просто уже у себя много чего переделал для семерки(в особенности Indy), очень удобно. В дженериках у меня пока нет необходимости, анонимные процедуры это конечно хорошо и удобно. Я бы еще не отказался бы от макросов, вот это очень полезно и от inline функций. Пока что то не готов, думаю скоро буду освивать.
    Нет, не угадал:)Я купил диск на радио-рынке где то в 2004-ом году, и ооочень нехотя перешел с 5-ой версии, уж очень она мне нравилась по сравнению с этой новой седьмой, щас видно ситуация тажа.
     
  14. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Ну в принципе почти тоже самое :D
    Ну а как же дженериковые коллекции? Это намного более удобнее чем динамические массивы
     
    #14 GhostOnline, 14 Mar 2011
    Last edited: 14 Mar 2011
  15. arnis

    arnis Member

    Joined:
    30 Jan 2011
    Messages:
    280
    Likes Received:
    23
    Reputations:
    0
    я не знаю какой вам рынок говорит.
    Но я работая на рынке прикладного ПО вижу следующую тенденцию
    Программа на Делфи имеет преимущество над программой на С шарпе.
    В том плане что многие сразу говорят что среда разработки любая кроме шарпа.
    Может это конечно вызвано тем что много формашлепов развелось которые только и умеют в шарпе на формочку перетянуть компонент.
    Хотя плюсам в этом аспекте делфи проигрывает.
     
  16. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Извините arnis вы из какой вселенной?
    В нашей вселенной быдлокодеры плодятся на дельфи, но никак не на шарпе, ибо чтобы там что-то рабочее изобразить надо и изучить больше.