Здравствуй Ачат. Как известно, в более-менее серьезных многопоточных приложениях(МП)(где множество потоков выполняют одну задачу), набирается большое количество разнообразных данных, доступ к которым необходимо синхронизировать. Это могут быть простые типы данных, строки, объекты, списки, потоки в памяти, файлы и т.п. Если при этом использовать для всего одну критическую секцию(далее КС), то это скажется на производительности - зачем блокировать доступ и к объекту А, если мы хотим всего лишь получить объект Б? По-этому я считаю что лучше для каждого экземпляра создавать свою КС. Но этот подход является довольно утомительным: надо создавать много КС, писать код входа-выхода для каждой из них. При этом всегда есть возможность ошибиться, "перепутать" к какому объекту относится данная Кс, и совершить тем самым дедлок, и попробуй потом найти ошибку - мультитредные проги отлаживать на порядок сложнее. К тому же, мне очень нравится оператор lock в C#. С появлением generics в Delphi(2009 и выше) я понял что можно все это упростить. И ниже описано как. Прилагаемый к статье проект также может пригодиться: 1. Наглядный пример создания МП для новичов. 2. Пример использования дженериков для тех, кто еще не знаком с этим. 3. Пример использования анонимных методов(TThread.Synchronize), что также сильно упрощает синхронизацию при доступе к VCL компонентам из потоков(об этом отдельно). Объявляем класс как показано ниже: PHP: // основной родительский класс, базовые определения TThreadSafe<T> = class strict protected FValue: T; FCritSec: TCriticalSection; // экземпляр критической секции, будет использоваться в наследниках public function Lock: T; // функция возвращающая значение и блокирующая его procedure Unlock; // процедура разблокировки procedure Write(Avalue: T); // запись значение БЕЗ блокировки function Read: T; // чтение БЕЗ блокировки procedure SafeWrite(Avalue: T); // запись с блокировкой, в аргументе - новое значение constructor Create(AValue: T); // аргумент - значение для инициализации destructor Destroy; override; end; Механизм работы таков: мы объявляем и создаем экземпляр класса с ЛЮБЫМ интересующим нас типом, будь то string, real, TDateTime, TFileStream или любым пользовательским(т.е. созданным вами). <T> тут - указание компилятору что автор класса заранее не знает с каким типом будут работать его экземпляры. Этот конкретный тип будет определяться пользователем класса и компилятор просто его подставит вместо <T>. И далее мы просто используем его например так: PHP: var SafeInteger: TThreadSafe<Integer>; SafeMemoryStream: TThreadSafe<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(AValue: T); function GetValue: T; public property Value: T read GetValue write SetValue; end; Для записей: PHP: TRecordTS<T: record> = class(TThreadSafe<T>) end; Для классов: PHP: TClassTS<T: class> = class(TThreadSafe<T>) end; Определения вроде PHP: i: TClassTS<Integer>; stringlist: TRecordTS<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 Мой первый опыт такого рода, прошу высказаться стоит ли мне продолжать
Ты бы лучше рассказал что физически процессор держит кол-во потоков = кол-ву ядер или процессоров. В все основы параллельного программирования разрабатывались для распределенных, кластерных систем.
Это здесь причем? Я тут не собирался устраивать ликбез по параллельному программированию. Те кто этого не знает, должны читать книги, изучать основы а не статьи на античате. fd00ch Свою точку зрения на это изложу завтра, сейчас уже не хочу, т.к. писать буду много.
Отлично всё расписал! Нужно будет сегодня посмотреть на практике, как это работает. И да. Тебе стоит продолжать писать подобного рода статьи. Думаю, многим интересно будет.
thread_demo.rar (523.2 КБ) Файл удален. MD5 ce07a1fb90f0b37e39ff6f3066b30757 SHA1 95c53b5a344155147e27710192c4cd0201fdb76a
Сэнкс Работает так как и должно - уже далеко не один коммерческий проект на этих классах успешно выполнен buket перезалил
Расписывать реально лень но раз уж так хочется: Условные обозначения: ПБ - потокобезасные, КС - крит. секции Отсутствие универсальности. Ок, ты реализовал ПБ варианты самых часто используемых классов RTL - коллекции, потоки ввода/вывода, ридеры/райтеры, мб какие-то свои классы. Но рано или поздно ты будешь все-таки либо использовать КС напрямую в пользовательском коде, либо писать ПБ-обертку для одного проекта, "на раз". Ибо задачи в проектах бывают весьма разнообразные. В первом случае получается мешанина подходов внутри одного проекта: тут мы используем свой враппер, а тут с КС работаем напрямую. Не знаю, как для тебя, а у меня смешивание двух методов синхронизации вызывает чувство отсутствия единого стиля и универсальности. Во-втором случае получится просто много ненужной работы. К тому же часто использую сторонние библы. Писать для всего этого врапперы, да еще под все методы и свойства - ужос на крыльях ночи. Далее, эта синхронизация просто нужна не для всего и не всегда. Лучше уж пользователю решать когда лочить когда нет. С переходом на сишарп я тоже столкнулся с дилеммой - с одной стороны есть lock, с другой такое же искушение писать врапперы для постоянного использования. Подумав решил что проще уж lock юзать, чем реализовывать over 9000 методов например класса List<> вместе со всеми методами-расширениями. Мне кажется именно по этим причинам паттерн "потокобезопасная обертка" не сильно распостранен у авторов фрэймворков/либ в отличие от более универсальных решений типа Monitor. Кстати написал уже более удобный вариант типа монитора на основе твоей идеи с анонимными методами, все руки никак доходят оформить и дополнить здесь. Дженерики там уже не используются
Понравилась фитча, мол потоку не надо дожидаться выполнения функции которая через Queue вызывается, себе для семерки такое прикручу
Ну, это конечно очень хорошо что ты собрался сделать. Но опять же, без анонимных методов это все же не так удобно. Наверняка же ты будешь делать либо на основе сообщений виндоуз, либо писать собвственный лесопед с пулом задач разгружаемых по таймеру. Я угадал? А все-таки, ведь ты же профессиональный разработчик, почему бы и не перейти наконец с 7-ки? Да, она популярна, ибо в конторах лицензия на нее с 2002 года, на более новые не переходят ибо выбирают уже вижуал_студио с сишарпом, а поддерживать старый быдлокод можно и на семерке. Но ведь ты скорее всего юзаешь дельфю с торрентов. Я угадал?
Ну да, я просто переделаю Classes и Forms Ой, спасибо Я просто уже у себя много чего переделал для семерки(в особенности Indy), очень удобно. В дженериках у меня пока нет необходимости, анонимные процедуры это конечно хорошо и удобно. Я бы еще не отказался бы от макросов, вот это очень полезно и от inline функций. Пока что то не готов, думаю скоро буду освивать. Нет, не угадалЯ купил диск на радио-рынке где то в 2004-ом году, и ооочень нехотя перешел с 5-ой версии, уж очень она мне нравилась по сравнению с этой новой седьмой, щас видно ситуация тажа.
Ну в принципе почти тоже самое Ну а как же дженериковые коллекции? Это намного более удобнее чем динамические массивы
я не знаю какой вам рынок говорит. Но я работая на рынке прикладного ПО вижу следующую тенденцию Программа на Делфи имеет преимущество над программой на С шарпе. В том плане что многие сразу говорят что среда разработки любая кроме шарпа. Может это конечно вызвано тем что много формашлепов развелось которые только и умеют в шарпе на формочку перетянуть компонент. Хотя плюсам в этом аспекте делфи проигрывает.
Извините arnis вы из какой вселенной? В нашей вселенной быдлокодеры плодятся на дельфи, но никак не на шарпе, ибо чтобы там что-то рабочее изобразить надо и изучить больше.