[Delphi] Многопоточная работа с memo

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Diamantx, 4 Jun 2013.

  1. Diamantx

    Diamantx New Member

    Joined:
    17 Feb 2012
    Messages:
    64
    Likes Received:
    1
    Reputations:
    0
    Пишу программу для работы с почтовыми ящиками

    Вот процедура кнопки:
    Code:
    procedure TForm1.Button1Click(Sender: TObject);
    var
      i: integer;
    begin
      Button1.Enabled := False;
      s := 0;
      for i := 0 to strtoint(Edit2.text) - 1 do
      begin
        FindLetterT := TFindLetter.Create(False);
        FindLetterT.Priority := tpNormal;
      end;
    end;
    Вот кусок кода потока:
    Code:
      for s := 0 to Form1.Memo2.Lines.count - 1 do // Пока есть аккаунты
      begin
        Form1.Memo3.Lines.Add('Поток вошел');
        acc := Form1.Memo2.Lines[s];
    Вот что происходит в программе:

    [​IMG]

    По скрину видно, что в цикл вошло пять потоков, хотя должен был один (так как от S до Form1.Memo2.Lines.count - 1 один шаг). Почему так? Как исправить?
     
  2. BigBear

    BigBear Escrow Service
    Staff Member Гарант - Escrow Service

    Joined:
    4 Dec 2008
    Messages:
    1,801
    Likes Received:
    920
    Reputations:
    862
    Использовать критические секции.

    var CS:TcriticalSection;


    CS.Enter; //это чтоб потоки не косячили
    code;
    CS.Leave;
     
    _________________________
  3. Chrome~

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

    Joined:
    13 Dec 2008
    Messages:
    936
    Likes Received:
    162
    Reputations:
    27
    Также не изменять GUI из дополнительного потока, могут появиться ошибки. Используй метод TThread.Synchronize.
     
  4. Diamantx

    Diamantx New Member

    Joined:
    17 Feb 2012
    Messages:
    64
    Likes Received:
    1
    Reputations:
    0
    Тогда есть смысл использовать вместо for while и в критическую секцию запихивать прибавление единицы к счетчику и присваивание аккаунта переменной. Я все правильно понял? Или что-то еще требуется поместить туда?
     
  5. Eich3

    Eich3 Member

    Joined:
    27 Jan 2013
    Messages:
    22
    Likes Received:
    7
    Reputations:
    5
    Это небезопасный код. Если есть другой участок, также работающий с проблемным компонентом, снова появится гонка.

    Нужно использовать TThread.Synchronize, как тут и советовали - он заблокирует всё VCL глобально.

    И, кроме того, нужно использовать try/finally, чтобы гарантированно выйти из критической секции в случае исключения
     
  6. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    http://grabberz.com/showthread.php?t=24619
    Годная статья по многопоточности в Delphi, во второй части статьи еще расписано очень подробно что почему и как.
     
  7. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Code:
    CS.Enter; //это чтоб потоки не косячили
    Поржал) Надо уж хотя бы так -
    Code:
    CS.Enter; //Ну ты это...не косячь!
     
    1 person likes this.
  8. Eich3

    Eich3 Member

    Joined:
    27 Jan 2013
    Messages:
    22
    Likes Received:
    7
    Reputations:
    5
    Ознакомился. Отличная статья о том, как НЕ надо работать с потоками в delphi.

    Вранье. Эффективность потоков под windows заканчивается вместе с количеством процессорных ядер - дальше идет спад. Это при условии, что процессоры загружают процессор полностью.

    Но даже если потоки простаивают, остается оверхэд на:
    1. Переключение контекстов
    2. Мегарасход памяти
    3. Под виндой еще голодают другие процессы, поэтому система начинает тормозить.

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


    Про синхронизацию в VCL - обычная TCriticalSection не пригодна в принципе. Дело в том, что любой компонент может внезапно потянуть за собой обработчики событий, или получить сообщение от UI-потока => появится вероятность разрушить данные.

    Поэтому, если разделяется что-то унаследованное от TObject, необходимо поток завернуть в TThread и вызывать процедуру через Synchronize.

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

    Про передачу сообщений не упомянуто. Между тем, это наиболее эффективно - создать пул потоков и очередь заданий на выполнение.
     
  9. e|\|ot

    e|\|ot New Member

    Joined:
    26 Sep 2012
    Messages:
    0
    Likes Received:
    0
    Reputations:
    0
    Как по мне так лучше в многопоточности не использовать графических компонентов, или минимально использовать. Сделать через масив и т.д.
     
  10. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Если правильно писать, к примеру для приостановки использовать сигнальные объекты ядра(критические секции - это не объекты ядра), то все будет норм, переключений никаких не будет. А иногда даже требуется это ваше переключение контекстов(в этом случае ставят Sleep(0);), в моей практике такое было. "Мегарасхода" памяти тоже не будет, если руки не из жопы.
    Бред, не верно написано. Критическая секция дает монопольный доступ на выполнение отрезка кода, к VCL это никак не относится вообще. Synchronize добавляет выполнение метода или процедуры в очередь, которая выполняется и соответственно очищается, когда приложение переходит в режим простоя(все Windows сообщения обработаны) и вызываются в контексте главного потока. Получается, что все данные, относящиеся к главному потоку и выполняются в нем.

    А вообще читать можно без всяких Synchronize данные главного потока, но только в том случае, когда точно известно, что они изменяться не будут в главном потоке во время чтения в левом потоке. Проще говоря можно без всякой синхронизации читать, а вот писать нельзя все кроме простейших типов данных, у которых запись происходит за одну команду процессора, в случае со строками или классами будет печаль у памяти и рано или поздно программа рухнет.
     
  11. Eich3

    Eich3 Member

    Joined:
    27 Jan 2013
    Messages:
    22
    Likes Received:
    7
    Reputations:
    5
    Помимо синхронизации на уровне ядра, переключение еще будет вызываться после вызовов функций блокирующего I/O. Так что любой вызов socket/connect/getaddrinfo/send/recv приведет к смене потока.

    Ну будет это переключение заменено активным ожиданием освобождения критической секции. Сути не меняет - это процессорное время по прежнему будет тратиться неэффективно, чем если бы мы выбирали, на примере работы с сетью, все события через select.

    Потоки для распараллеливания I/O - это плохо масштабируемый костыль - уже доказано не раз.

    Примеры? Разве что добавить искусственно тормозов, для эстетики :) Любое переключение, как минимум, сбрасывает кэш - а это влияет на скорость.

    Вот именно, что синхронизируется участок кода, а не факт доступа к объекту. В главном потоке может сработать обработчик, который изменит состояние объекта и, вместе с потоком, вызовет гонку. А Synchronize в основе использует глобальный лок (Classes.ThreadLock), и потому через него могут синхронизироваться все потокобезопасные компоненты.

    И как это гарантировать? Разве что вывести уравнение для мироздания и доказать, что программист не забудет о такой "ловушке" через некоторое время и не добавит компоненту новый обработчик события (например, OnMouseOver), который изменит состояние визуального объекта. Но при наличии такого уравнения писать очередную говноутилиту уже не захочется - появятся более глобальные цели :D

    Поскольку топикстартера интересовала именно синхронизация изменения визуального компонента, я считаю synchronize наилучшим решением данного вопроса, а все остальное - для любителей говнокода.
     
    1 person likes this.