TThread and updating dynamically created component lazarus / 21-General ---------------------------------------------------------------------- r.lukasiak · 2022-10-26 03:21 hi everyone! I'm trying to learn threads, I'm following this tutorial https://wiki.freepascal.org/Multithreaded_Application_Tutorial and I managed to fire a thread that updates one of existing components but now I need to fire several threads with their own components (TImage in this case). I've tried several solutions and all come with some issues. I tried to create needed TImage inside TThread, it failed on instant: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TSomeThread = class(TThread)    private      img: TImage;      procedure ShowStatus;    protected      procedure Execute; override;    public    Constructor Create(CreateSuspended : boolean);    end; constructor TSomeThread.Create(CreateSuspended : boolean);begin  inherited Create(CreateSuspended);  FreeOnTerminate := True;  img:=TImage.Create(Form1);  img.Parent:=Form1end;  Now I create TImage before starting the thread, inside the procedure that's firing the thread and I assign it to TSomeThread.img. It seems to work but only the first one, firing second thread comes with SIGSEV or other error. I suppose it's because multiple threads are using the same instance of TImage? I read something about Threadvar which is supposedly making a copy of variable for every thread but I can't figure out how to use. Shall I declare TImage in Threadvar? Is someone able to show me the right way? Thanks in advance! ---------------------------------------------------------------------- KodeZwerg · 2022-10-26 04:02 Quote from: r.lukasiak on October 26, 2022, 05:21:53 amCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i constructor TSomeThread.Create((CreateSuspended : boolean);Is there a copy paste error? Should be: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i constructor TSomeThread.Create(CreateSuspended : boolean); ---------------------------------------------------------------------- PascalDragon · 2022-10-26 05:32 Quote from: r.lukasiak on October 26, 2022, 05:21:53 amIs someone able to show me the right way? The most important point: don't interact with the GUI from within a thread. If you want to update something in the GUI then you need to use methods like TThread.Synchronize or TThread.Queue so that they are executed in the context of the main thread. Thus you can prepare the data you need to display in the GUI in your thread, but the real “assignment” needs to be done separately. This is even mentioned in the tutorial you linked. ---------------------------------------------------------------------- MarkMLl · 2022-10-26 07:25 Quote from: PascalDragon on October 26, 2022, 07:32:28 amThe most important point: don't interact with the GUI from within a thread. If you want to update something in the GUI then you need to use methods like TThread.Synchronize or TThread.Queue so that they are executed in the context of the main thread. Thus you can prepare the data you need to display in the GUI in your thread, but the real “assignment” needs to be done separately. This is even mentioned in the tutorial you linked. I'd add that when I asked a similar question somebody pointed out that even reading a GUI component's property might be unsafe, since if it goes via a getter function it might have complex side-effects. Starting off with heavy use of Synchronize() is a good idea, followed by optimisation to try to keep the number of calls down. If that doesn't fix any performance issues there are alternatives, but at that point you have to start being very careful that the stub invoked by Synchronize() doesn't contain (or call anything that contains) an Application.ProcessMessages since that will cause reentry problems. MarkMLl ---------------------------------------------------------------------- r.lukasiak · 2022-10-26 07:56 @KodeZwerg, it's a typo. I'm sorry, I didn't copy-paste. I typed it. I will edit it. @PascalDragon, yes, I update any GUI elements though SomeThread.Synchronize. This part is working as expected. I'm just not sure how I should point the right TImage to be updated by each thread. That's what I got: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i typeTAnimText = class(TThread)    private      bmp:    TBGRABitmap;    effect: TBGRATextEffectFontRenderer;      shader: TPhongShading;    procedure ShowStatus;    protected      procedure Execute; override;    public      img:       TImage;      FIter:     integer;      FColorR:   byte;      FColorG:   byte;      FColorB:   byte;      FInitX:    word;      FInitY:    word;      FText:     string;      Constructor Create(CreateSuspended : boolean);    end;  constructor TAnimText.Create(CreateSuspended : boolean);  begin    inherited Create(CreateSuspended);    FreeOnTerminate := True;end;    procedure TAnimText.ShowStatus;begin    img.Left:=FInitX-(bmp.Width div 2);    img.Top:=FInitX-(bmp.Height div 2);    img.Width:=bmp.Width;    img.Height:=bmp.Height;    img.Picture.Assign(bmp);  end;      procedure TAnimText.Execute;var  i: integer;begin  shader:=TPhongShading.Create;  effect:=TBGRATextEffectFontRenderer.Create(shader,true);  for i:=0 to FIter-1 do    begin      effect.FontEmHeight:=i;      bmp:=TBGRABitmap .Create(effect.TextSize(FText).cx,effect.TextSize(FText).cy,BGRA(0,0,0 ,0));      {here it generates the bmp image, I removed it to shorten the code}      Synchronize(@ShowStatus);      bmp.Free;    end;   effect.Free;  shader.Free;  img.Free; end; procedure FireAnimText(x,y: integer; r,g,b: byte; len: integer; txt: string);var  ATthr: TAnimText;  img: TImage;       //I also tried to declare it globally in Threadvar section but it didn't change anythingbegin  img:=TImage.Create(sq);  img.Parent:=sq;  ATthr:=TAnimText.Create(true);  ATthr.img:=img;  //I assigne the newly created TImage to the thread here  ATthr.FInitX:=x;  ATthr.FInitY:=y;  ATthr.FColorR:=r;  ATthr.FColorG:=g;  ATthr.FColorB:=b;  ATthr.FIter:=len;  ATthr.FText:=txt;  ATthr.Start;end;    procedure Tsq.BoardGridMouseUp(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);begin  FireAnimText(BoardGrid.Left + BoardGrid.Width div 2,BoardGrid.Top + BoardGrid.Height div 2,255,0,0,128,'Test');end;  This way it's working once, when I try to fire another one before the previous one finished, it's flickering, it looks like both threads use the same TImage instance, but that's just my idea. one of "weird errors" I sometimes get is: Quote In file 'laz_avl_tree.pp' at line 962: OldRightLeft:=OldRight.Left; ---------------------------------------------------------------------- cdbc · 2022-10-26 13:01 Hi You are in effect playing a 5 sec. video... 128 frames @ 25 fps = 5,12 seconds, although i don't see any 40ms delay anywhere, which would mean, your thread is running full throttle. Could be, it's too much stress to handle for the LCL, especially, when you start another one, before the first has ended, even if you use syncronize... I think the avl tree issue, is the LCL wining... Regards Benny ---------------------------------------------------------------------- BrunoK · 2022-10-26 14:54 You create shader and effect in lines 41-42 then free them in lines 49-50. If "i" in the loop becomes >0 it looks to me that lines 45-46 are likely to have invalid shader and effect instances. ---------------------------------------------------------------------- r.lukasiak · 2022-10-27 02:33 @BrunoK, ohh yes, I've already corrected the post. I was copy-pasting the code in pieces, I just mis-pasted it. In my code, shader and effect are freed beyond the loop. @cdbc, Im not sure what you refered to with "128 frames @ 25 fps = 5,12 seconds" but with "i don't see any 40ms delay anywhere" you gave me an idea to solve other problem, other animation was a little bit choppy, I added a couple of ms after each iteration and it solved the problem. Thanks! I've created a couple of other threaded animations and all work perfectly, the only difference to this one is that this one works on dynamically created component and it looks like all threads are using the same instance of TImage. It's freed inside the thread so as soon as the first thread ends, other threads fail. I will try to create a separate Image for each thread. ---------------------------------------------------------------------- PascalDragon · 2022-10-27 05:22 Quote from: r.lukasiak on October 27, 2022, 04:33:17 amI've created a couple of other threaded animations and all work perfectly, the only difference to this one is that this one works on dynamically created component and it looks like all threads are using the same instance of TImage. It's freed inside the thread so as soon as the first thread ends, other threads fail. I will try to create a separate Image for each thread. You are freeing the image that's part of the form inside the thread after the Synchronize call, however the form might still be using it. You can free it instead in the thread's OnTerminate event which is again called synchronized. Or you don't create and free the component, but instead keep it around and only update it. ---------------------------------------------------------------------- cdbc · 2022-10-27 06:01 Hi @r.lukasiak: In film and video business, you need to show 25 frames, i.e. pictures drawn, per second to get fluid motion. In games the fps (frames per second) rates are much higher. 1 sec -> 1000 msecs ~ 1000 / 25 = 40 msecs. I'm glad it inspired you, to solve the other problem. regards Benny ---------------------------------------------------------------------- Warfley · 2022-10-27 19:18 I would generally recommend to not use TThread.Synchronize, because it basically forces serialization of the execution over the mainthread. This is because synchronize puts your thread to sleep until the synchronized function call has finished. Synchronize is considered quite easy to use, because it avoids so called race conditions when accessing any memory, and this is true, because you cant have problems through multithreading if you just stop the multithreading to do your buisness. But simultaniously you also loose all the benefits from multithreading if you stop your threads all the time. This gets worse the more number of threads you have, because your thread waits for the synchronize to finish, if another thread is already in the synchronize context, you not just need to wait for your function to finish but also for the one of the other thread. Let me give you an example on how bad synchronize really is. The following programm creates a number of progress bars and has them filled by threads using synchronize and measures the time it takes to fill these progressbars: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    Memo1: TMemo;    procedure Button1Click(Sender: TObject);  private    function CreateProgressBar(n: Integer): TProgressBar;    function RunTest(numThreads: Integer): QWord;  public   end;   { TIncrementThread }   TIncrementThread = class(TThread)  private    procedure UpdateProgressBar;  protected    procedure Execute; override;  public    FProgressBar: TProgressBar;    FStartTime, FRunTime: QWord;    constructor Create(AProgressBar: TProgressBar);  end; var  Form1: TForm1; implementation {$R *.lfm} { TIncrementThread } procedure TIncrementThread.UpdateProgressBar;begin  FProgressBar.Position := FProgressBar.Position + 1;end; procedure TIncrementThread.Execute;var  i: Integer;begin  for i:=0 to 999999 do    if i mod 100 = 0 then      Synchronize(@UpdateProgressBar);  FRunTime := GetTickCount64 - FStartTime;end; constructor TIncrementThread.Create(AProgressBar: TProgressBar);begin  inherited Create(True);  FProgressBar := AProgressBar;  FStartTime := GetTickCount64;end; { TForm1 } function TForm1.CreateProgressBar(n: Integer): TProgressBar;begin  Result := TProgressBar.Create(Self);  Result.Parent := Self;  Result.Top := Result.Height * n + 8;  Result.Left := 0;  Result.Width := ClientWidth;  Result.Max := 10000;end; function TForm1.RunTest(numThreads: Integer): QWord;var  Threads: array of TIncrementThread;  i: Integer;begin  SetLength(Threads, numThreads);  for i:=0 to numThreads - 1 do    Threads[i] := TIncrementThread.Create(CreateProgressBar(i));  for i:=0 to numThreads - 1 do    Threads[i].Start;  for i:=0 to numThreads -1 do    Threads[i].WaitFor;  for i:=0 to numThreads -1 do  begin    Result := 0;    if Threads[i].FRunTime > Result then      Result := Threads[i].FRunTime;    Threads[i].FProgressBar.Free;    Threads[i].Free;  end;end;  procedure TForm1.Button1Click(Sender: TObject);var  i: Integer;begin  Memo1.Lines.Clear;  for i := 1 to 10 do    Memo1.Lines.Add('%d Bars: %dms'.Format([i, RunTest(i)]));end;Some results on my machine: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 1 Bars: 203ms2 Bars: 281ms3 Bars: 406ms4 Bars: 547ms5 Bars: 687ms6 Bars: 812ms7 Bars: 938ms8 Bars: 1094ms9 Bars: 1234ms10 Bars: 1375ms  Now lets compare this with an alternative, TThread.Queue: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TIncrementThread.Execute;var  i: Integer;begin  for i:=0 to 999999 do    if i mod 100 = 0 then      Queue(@UpdateProgressBar);  FRunTime := GetTickCount64 - FStartTime;end;Queue basically does the same as Synchronize, but that the thread does not wait for the function to be executed but just says for the mainthread to execute that function and then the worker thread continues whatever it is doing: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 1 Bars: 94ms2 Bars: 203ms3 Bars: 296ms4 Bars: 406ms5 Bars: 515ms6 Bars: 782ms7 Bars: 719ms8 Bars: 797ms9 Bars: 953ms10 Bars: 1000msIt scales as bad as synchronize (linearly, so doubling the number of threads doubles the time it takes), which is because there are still locking mechanisms that require waiting for the other threads, but still the time it takes overall is cut by 1/3 just by not having to wait for the finishing of the method. And now lets compare this with the fastest method, which is polling without any locks. Caution: operations must be atomic for this to be thread safe (and reading an integer as in this example is): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   { TIncrementThread }   TIncrementThread = class(TThread)  private    procedure UpdateProgressBar;  protected    procedure Execute; override;  public    FProgressState: Integer;    FProgressBar: TProgressBar;    FStartTime, FRunTime: QWord;    constructor Create(AProgressBar: TProgressBar);  end;[...] procedure TIncrementThread.UpdateProgressBar;begin  FProgressState := FProgressState + 1;end; procedure TIncrementThread.Execute;var  i: Integer;begin  for i:=0 to 999999 do    if i mod 100 = 0 then      UpdateProgressBar;  FRunTime := GetTickCount64 - FStartTime;end; constructor TIncrementThread.Create(AProgressBar: TProgressBar);begin  inherited Create(True);  FProgressBar := AProgressBar;  FStartTime := GetTickCount64;  FProgressState := 0;end;[...]function TForm1.RunTest(numThreads: Integer): QWord;var  Threads: array of TIncrementThread;  i: Integer;  AllFinished: Boolean;begin  SetLength(Threads, numThreads);  for i:=0 to numThreads - 1 do    Threads[i] := TIncrementThread.Create(CreateProgressBar(i));  for i:=0 to numThreads - 1 do    Threads[i].Start;  repeat    Application.ProcessMessages;  AllFinished := True;    for i:=0 to numThreads - 1 do    begin      AllFinished := AllFinished and Threads[i].Finished;      Threads[i].FProgressBar.Position := Threads[i].FProgressState;    end;  until AllFinished;  for i:=0 to numThreads -1 do  begin    Result := 0;    if Threads[i].FRunTime > Result then      Result := Threads[i].FRunTime;    Threads[i].FProgressBar.Free;    Threads[i].Free;  end;end;Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 1 Bars: 0ms2 Bars: 0ms3 Bars: 0ms4 Bars: 0ms5 Bars: 16ms6 Bars: 0ms7 Bars: 0ms8 Bars: 0ms9 Bars: 16ms10 Bars: 0msIt's basically instantly finished, because now there is no waiting involved at all. So long story short, if you are working with threads, you should think about different synchronization mechanisms, and use synchronize only if you require your thread to actually wait (and potentially be blocked by other threads) for the mainthread action to finish. This is usually only the case when the worker thread requires feedback gathered from the action on the mainthread. If you just want to update the mainthread, there is no reason for your thread to wait. Just queue the update on the mainthread and let your worker continue. Or regularly update your UI using polling and thereby read the current state from the threads (this is what for examples games usually do to archive high framerates when using threads for computing something). To put it simply: To send data to the main thread (e.g. to say "Hey I computed new data please display it in mainthread"), use queue, if you want to request and receive data from the main thread, use synchronize. I generally find that the former situation is much more common than the latter, yet Synchronize is much more often recommendet than queue ---------------------------------------------------------------------- MarkMLl · 2022-10-27 19:49 Quote from: r.lukasiak on October 27, 2022, 04:33:17 amohh yes, I've already corrected the post. I was copy-pasting the code in pieces, I just mis-pasted it. In my code, shader and effect are freed beyond the loop. Which is one reason why posting an incomplete program when asking for help is frowned upon. Quote from: Warfley on October 27, 2022, 09:18:30 pmQueue basically does the same as Synchronize, but that the thread does not wait for the function to be executed but just says for the mainthread to execute that function and then the worker thread continues whatever it is doing: But OP makes if clear that he's quite a long way down the learning curve. In that context, I think it's better to emphasise techniques which are unconditionally safe, even if they do merit an efficiency warning. While I am most definitely not an apologist for the bloat attendant on most modern development techniques and tools, sorting out reentrancy and deadlock issues can be tricky for even an experienced developer not yet familiar with the infrastructure he's using. MarkMLl ---------------------------------------------------------------------- Warfley · 2022-10-27 21:09 Quote from: MarkMLl on October 27, 2022, 09:49:05 pmBut OP makes if clear that he's quite a long way down the learning curve. In that context, I think it's better to emphasise techniques which are unconditionally safe, even if they do merit an efficiency warning. But this is the problem with synchronize, it seemingly does work easiely, but if you don't know what you are doing, you basically fully negate the effects of multithreading. The problem with synchronisation is that there is no universal antidote for it, so there is no such thing as a simple solution, and I think that should not be suggested. It is essential for someone learning this issue to make one thing abandonly clear, this is hard, probably one of the hardest problems in computer science. Just on how hard this topic actually is, this year alone there where hundreds if not thousands of scientific papers published about new approaches to solving these problems. So this is a problem where thousands of computer scientists around the world, spend their time researching on how to solve it. And I think the best way for someone to learn such things is to not and try to hide how hard it is, but to state clearly from the beginning how hard it is. Such seemingly "easy" solutions as synchronize basically just hide the problems without solving them, it just postpones them until they are coming to bite you anyway. Bad synchronizing is worse than using no threads at all. Especially considering that many learning resources (as the multithreaded application tutorial linked by the thread author above), but also the Delphi book I learned delphi with, simply do not tell you this. They don't say that it is hard, not even have an explaination of what a deadlock is. And you can't learn how to correctly use something, without knowing the pitfalls and dangers of that thing ---------------------------------------------------------------------- SymbolicFrank · 2022-10-28 06:33 Everything that depends on the message handler to execute updates isn't a good idea, either. Your best bet is a stand-alone queue and a timer, to poll it periodically. Yes, that sounds very primitive and inefficient, but it keeps al your threads free-running. ---------------------------------------------------------------------- MarkMLl · 2022-10-28 07:43 Quote from: Warfley on October 27, 2022, 11:09:21 pmJust on how hard this topic actually is, this year alone there where hundreds if not thousands of scientific papers published about new approaches to solving these problems. So this is a problem where thousands of computer scientists around the world, spend their time researching on how to solve it. But we're not in the business of writing papers here, we're in the business of writing- and helping other people write- applications that don't fall over at random. As such, we are stuck- and I use the word without apology- with underlying widget sets (i.e. gtk etc.) that don't play nicely with multithreading, and with the obligation to accommodate that at the application level. My position on this is that our first obligation- to other members of the community, to potential customers where appropriate, and to ourselves and non-paying users- is to produce something that behaves predictably. And if that predictable behavior doesn't include adequate throughput, that is the point at which we should review our expectations and methodology. Over the last 30 years there has been a vast increase in computing power, including the introduction of SMP/NUMA systems. There has also been a vast increase in software complexity, and to a great extent that has been caused rather than avoided by trendy ideas in computer science and popular development. The net result has been a decrease in software capability, compared with the computational resources needed to run that software, and a decrease in reliability largely caused by inexperienced developers overreaching themselves and not being protected by their development and runtime environments. So while I don't disagree that it's important to understand the tools one is using and to plan software carefully, I think it's also important to focus on what we're doing here. *  Object Pascal was possibly the first language to encapsulate threads with any degree of rigour, and once a program has been partitioned into threads with limited interaction there are various ways that that interaction can be tuned for improved performance. *  Lazarus, preceded by Delphi, is one of the best IDEs and one that still has a great deal to offer the relatively inexperienced programmer. *  Division of software into threads can substantially improve performance, particularly where UI interaction is minimised. *  As a way of compartmentalising UI interaction, Synchronize() is fairly safe because it blocks. Queue(), and other non-blocking equivalents, are somewhat less safe because it's still possible for poorly written code to attempt reentrancy. *  So the way to do things is to first compartmentalise into threads with no unprotected shared variables etc., and then once that's reliable minimise blocking by replacing Synchronize() where possible. MarkMLl ---------------------------------------------------------------------- SymbolicFrank · 2022-10-28 08:45 @MarkMLI: I agree. ---------------------------------------------------------------------- Warfley · 2022-10-28 13:16 Quote from: MarkMLl on October 28, 2022, 09:43:08 am*  So the way to do things is to first compartmentalise into threads with no unprotected shared variables etc., and then once that's reliable minimise blocking by replacing Synchronize() where possible. Making a bad but working version first and improving it afterwards never works. This is why we now have concepts like security by design, privacy by design or ux by design, because history has shown that if you write an unsafe, privacy leaking or badly ux designed program fixing it afterwards is extremely hard, and those considerations have to be made from the start. The same is for thread safety, the program must be designed to be thread safe, this means to reduce shared state as much as possible and think about the interfaces between those threads (and how to secure them) in every step of the development. And in order to do this, the programmer needs to know the problems they will encounter. This topic is kinda close to my heart because I remember when I started programming with Delphi and my book told me about synchronize and critical sections but, like the tutorial in the wiki, did not mention anything about deadlocks, and only extremely superficially explained the problems that these two mechanisms (CRs and synchronize) solve. And when I then wrote my first multithreaded application it was slow and froze regularly and I didn't knew why. Turns out that it had a few deadlocks in there, which I didn't even know existed because the term deadlock (similar to the tutorial in the wiki) did not occur once in the book. Basically I did not manage to find out what the problem was and something like 50 hours of work where basically pointless because I had to throw the whole thing away. It took me going to university and having a few lectures about this where we where the first thing we where tought was all the ways things can go wrong, with examples so we can see it for our selves, and then the different solutions, how they work and the problems they provide. Hiding complexity is not the same as managing complexity, and not telling someone how hard something really is but pretending that it is easy does not help anyone ---------------------------------------------------------------------- MarkMLl · 2022-10-28 14:12 Quote from: Warfley on October 28, 2022, 03:16:22 pmMaking a bad but working version first and improving it afterwards never works. Worked for Microsoft (DOS v1, Windows v1), Borland (Delphi v1 and 2) and many others... QuoteThis topic is kinda close to my heart because I remember when I started programming with Delphi and my book told me about synchronize and critical sections but, like the tutorial in the wiki, did not mention anything about deadlocks, and only extremely superficially explained the problems that these two mechanisms (CRs and synchronize) solve. (etc.). I sympathise, but if Computer Studies of the last 30 years had really contributed anything useful our tools would now be good enough to prevent the user breaking things until he /explicitly/ did so. Which is, after all, the "Pascal Way": strong typing etc. plus a circumvention mechanism. MarkMLl ---------------------------------------------------------------------- SymbolicFrank · 2022-10-28 14:31 @Warfley: I agree... kinda. The problem is twofold: as Mark already said, most people find this really hard to grasp, and the set of tools you get to tackle the program (IDE + programming language, mostly) are all build to help you write straightforward applications, where the user activates a control and you do a small action to update the state. Alternatively, if you just want to calculate something, single-threaded command-line apps are popular. And applications that really use all your cores, will bring the rest of your applications to a near-standstill and make the computer very unresponsive. Multi-threading isn't the norm. For starters, on Windows, you are expected to use shared memory for inter-thread communication, or at best the system-wide message queue. There is no good mechanism to check queues or activate threads with new data when it is time for them to do something. Multi-tasking is tacked-on, in many ways. It was, and still is, a stopgap for the single-threaded, 100+ GHz CPU. That would fit the current programming model perfectly. And yes, that results in most multi-tasking applications actually running slower than when you simply did it all sequentially, on a single core. It's hard. Sure, there are some basic rules: 1. No shared memory. 2. No waiting for threads. 3. Use single-use, stand-alone threads. 4. No polling without waiting in between, use blocking reads. Etc. But even the first one: if you create a variable, where is it stored? How do you access shared resources? How do you know? How can you change that? And if you Google that, you hear about mutexes and memory barriers. That still doesn't answer the question how you get the data to the thread, and the result back to the GUI. Or, the advice to use thread-pools, because threads are expensive to create, and to split your calculation into multiple threads, that all execute the same code on a part of your data. Good luck. So, TLDR, yes, it would be best if you designed everything up front the right way. That makes it much, MUCH easier to get it right. But you need to be a thread-guru before you can do that. It's hard. ---------------------------------------------------------------------- MarkMLl · 2022-10-28 14:56 Quote from: SymbolicFrank on October 28, 2022, 04:31:53 pmFor starters, on Windows, you are expected to use shared memory for inter- thread communication, or at best the system-wide message queue. There is no good mechanism to check queues or activate threads with new data when it is time for them to do something. Part of the problem there is that on PCs, in effect starting off with OS/2 and progressing through LAN Manager and Windows, there never was a single interthread or interprocess communication mechanism which was intended to be used for moving structured data between different parts of the same program. OK, there were Named Pipes... which were intended to compete with TCP and perhaps with something even older in the SNA (etc.) architecture. And there were mailslots... which like UDP were grievously underused. But instead of sorting out a decent and universally-used API with semantics that everybody was happy with, MS et al. jumped straight into (graphical) component embedding etc. which they might or might not have managed to keep as a closed shop: it's certainly made enough money for them as one of the underpinnings of MS Office. That, arguably, was one of the good things about the multitasking DR OSes: they had a designed-in datagram mechanism which was used heavily by the more advanced developers to pass data between threads, and that included a variant intended for mutual exclusion: which like everything else also worked robustly across a LAN. So, I don't think that anybody can accuse me of not appreciating the importance of a good underlying architecture. But the best architecture in the World won't get much love if it doesn't have competent- by the standards of the day- development tools. And while a competent development tool can go a very long way to protecting a user from himself (or herself as the case may be), the bottom line remains the same: programming's difficult. MarkMLl ---------------------------------------------------------------------- Warfley · 2022-10-28 16:36 Quote from: MarkMLl on October 28, 2022, 04:12:13 pmI sympathise, but if Computer Studies of the last 30 years had really contributed anything useful our tools would now be good enough to prevent the user breaking things until he /explicitly/ did so. Which is, after all, the "Pascal Way": strong typing etc. plus a circumvention mechanism. MarkMLl There has actually been some progress in the tooling, most importantly language design, wich is to discourage threading but rather have co- routines, by splitting up the code into small tasks. Those are a very restrictive form of parallelism, where tasks need to be extremely small, and are then scheduled by the runtime to different threads depending on their requirements and dependencies. By restricting the capabilities of the programmer using these tasks it is much easier to manage. And if the user requires true threads, they still can, but everything thats possible using these tasks or co-routines is encouraged to do it with those. For example Python, C#, C++, Swift, etc. all these languages had threading long before those co-routines, but because this simplified way of asynchronous programming allows for much easier handling of the problems that arise from this. Still you can use threading in all of those where it is needed. For example, the problem that you can't access the GUI from different threads, simple just have all the tasks interacting with the gui be scheduled on the main thread. Or that the main thread should not wait for other threads, simple break up the mainthread message queue into small tasks which can be scheduled while others are waiting, so the processing of messages never stops. This is also the reason why I build my STAX co-routine implementation because I've found that threads are just really complicated for most of the things where asynchronous programming is required (e.g. spinning up a thread and having to coordinate it for just downloading something from the internet is way to complicated and introducdes way to many pitfalls) But this isn't really the issue I have here, the issue I have is that in my oppinion most resources for beginners do not accurately portray the complexity of the problem that is threading, and this leads to people running into problems they don't understand and are incapable of solving on their own, as me all those years ago, and any solution would require a complete rewriting of their threading code. I think the way I got tought those problems in University was the right approach, begin with a short explaination what threads are and then show where it can go wrong, with read-write and write-write problems (classical example, to threads increment a variable n times and in the end the variable is < n*2 because of race conditions), then the solution using atomic operations and locks, and then the problems with deadlocks and performance issues when using locks. Then after that the more advanced synchronization techniques like message queues (what synchronize and TThread.Queue are doing). The fact that most of those resources, like the tutorial in the wiki, don't explain the problems properly (just says that always lock shared state), and talks about synchronize before talking about locks and waiting in more general, and that the problem of deadlocks isn't even mentioned at all, is just a recipe for desaster for anyone who tries to learn with those resources ---------------------------------------------------------------------- MarkMLl · 2022-10-28 16:49 Quote from: Warfley on October 28, 2022, 06:36:09 pmThere has actually been some progress in the tooling, most importantly language design, wich is to discourage threading but rather have co-routines, Yes, as introduced by Wirth in Modula-2 as part of his attempt to undo some of the bad design decisions that went into Pascal (dangling else, implicit type conversions and so on). And 40 years on, this community is still full of people who insist that Pascal is the paragon of good design and that people who criticise it are misguided. MarkMLl ---------------------------------------------------------------------- Lulu · 2022-10-28 18:35 Very interesting topic, I didn't know the concept of deadlock about threads, thanks to all the participants ! ---------------------------------------------------------------------- MarkMLl · 2022-10-28 19:36 Quote from: Lulu on October 28, 2022, 08:35:47 pmVery interesting topic, I didn't know the concept of deadlock about threads, thanks to all the participants ! A word of background here. When Dijkstra and others started studying the problems related to simultaneously-executing jobs/processes/threads, they assumed that communication between them would be handled by queues and semaphores mediated by the operating system, and that by and large operations would be blocking: there was no provision for polling whether a queue contained a message or a semaphore was set. So discussion of deadlock really applies to where (in current terms) a thread tries to do something where it is likely to be blocked by the kernel until successful, i.e. * Tries to write a message to a queue which is full. * Tries to set an already-set semaphore. * Tries to read an empty queue. * Tries to unset a semaphore which is not set. ...and so on. However, the situation with threads as supported by modern OSes is far less clear-cut, since they can share variables with no protection mechanism, and since multiple threads can potentially call a routine simultaneously in an unsafe way. The Synchronize() and Queue() facilities provided by TThread are an attempt to improve robustness, since by and large the code that handles an application's GUI is not thread-safe: it assumes that it is being run by the application's "main thread" and has no protection against interference by a background thread. As Warfley and others have pointed out, Synchronize() etc. comes with its own problems. But the alternative- uncontrolled access to shared variables- is far, far worse. ----- As a word of background, computing semaphores are, of course, named after the semaphore signals you get on railway lines and the robust block system of working. And that was designed after a particularly nasty crash not far from where I am now, where a couple of hundred casualties were the direct result of people not understanding how to interlock independently-executing activities. And I'm afraid that I'd put a lot of money on a bet that more than that number of people has been killed by uncontrolled thread interaction of safety-critical computer systems. MarkMLl ---------------------------------------------------------------------- SymbolicFrank · 2022-10-28 19:53 Simplified threads (no TThread ancestor!) with a good OS-agnostic inter-thread communication is the improvement I would want most in FPC, because it's what I would use most. It has been on my TODO list to make for a long time. And, yes, that would require a lot of low- level changes. But it's a lot of time, there are always more important things to do. Coroutines don't solve the waiting for threads or easy data distribution problems. I expect it to be hard to make things faster by using them. You're extracting the calculation from your function, run it in parallel and wait for the result. I agree, that thinking in tasks is the way to go. Don't try to parallelize an existing function, split it up. But the main thing is not to think serial. If something has to happen, execute it as a task. If it takes too long, break it up into smaller ones. Don't think: "well, first we have to do this, then we have to do that". Make an action-collector, that executes all incoming actions as task. As for an example: in the past, having a high frame rate in your competitive FPS gave you an edge over your competition: you saw them earlier and could react faster. The same with your ping rate. But that has changed. The game logic nowadays runs at a fixed rate for everyone, and the communication intervals are standardized. Ok, if your FPS is really low, you will still see your enemies later. But in general, if it is lower than the logic clock, frames are dropped, to keep you up to date. And if it is faster, no new information is displayed. There are frames inserted in between the updated ones. Increasing the display quality, means more items on the screen, which takes more time to transform them in between frames. If you want to speed that up, are you going to spawn more threads to do those transformations? How are you going to segment that data? That is quite a hard problem. No, you send it all to the GPU and have it distribute the actions over its thousands of cores. Ah! You split it up into minuscule threads! Coroutines! Yes/no. The main bottleneck for a GPU is the latency: making sure the data needed arrives at the right core at the right time. It has a huge list of actions that have to be executed, but everything else is logistics. "If I schedule this action now on core 4123, it will be ready for execution in 317 cycles, by which time the needed data has arrived." Of course, many of the actions executed on a GPU are the same and only occupy a single pixel, so you can run them in bulk. Select the data that has to be moved form memory to the cores, and schedule the right program to execute at that time. And this only works, because they go through great pains to decouple everything as much as possible. If the data is changed or late: just throw the task away and reschedule it. That's also why GPU's have such massive memory bandwidths. And that is something your CPU doesn't have. That's why caches keep on growing and in general occupy more die-area than the cores themselves. The best you have is a few DMA channels. But look at the older game consoles: they prioritize DMA (or equivalents) above most other things. (Although there are some that prefer a small bit of really fast general memory on-chip, but that almost always turns out to be much too small to be generally usable.) And the best example of this model is the Cell processor, from the PS3: no shared memory, DMA everything, lots of local storage. But the reception: Gabe Newell saw it as an affront: "Learning how to use it is a colossal waste of time!" And he was only the most outspoken one, the vast majority of developers agreed: it was too different. And look where we are today... ---------------------------------------------------------------------- MarkMLl · 2022-10-28 20:24 Quote from: SymbolicFrank on October 28, 2022, 09:53:02 pmSimplified threads (no TThread ancestor!) with a good OS-agnostic inter-thread communication is the improvement I would want most in FPC, because it's what I would use most. It has been on my TODO list to make for a long time. And, yes, that would require a lot of low-level changes. But it's a lot of time, there are always more important things to do. Coroutines don't solve the waiting for threads or easy data distribution problems. I expect it to be hard to make things faster by using them. You're extracting the calculation from your function, run it in parallel and wait for the result. I agree, that thinking in tasks is the way to go. Don't try to parallelize an existing function, split it up. But the main thing is not to think serial. If something has to happen, execute it as a task. If it takes too long, break it up into smaller ones. Don't think: "well, first we have to do this, then we have to do that". Make an action-collector, that executes all incoming actions as task. Pascal, "as she is spoke", has far more serious problems that that IMO: there's far too much computersciencecrap gone into it over the last few years, while sweeping too many more worthy problems- like parallelisation, which Cray cracked in the 1970/80s- under the carpet. Coroutines are simple enough, but (before Warfley has apoplexy) come with significant problems when exceptions and debugging are considered. If considering threads one has to take into account programming paradigms (PThreads vs NPTL) and expectations. And once one gets to processes one is very much at the mercy of the OS. The first fundamental problem is that threads/coroutines share memory with one another. But if they didn't they'd be processes. The second is that there is no agreed way of passing (blocking or non- blocking) binary messages between threads/coroutines in the same program, i.e. where there is a reasonable guarantee that all units are compiled against the same type definition. Interestingly, the second point also cropped up in a discussion of (Lazarus-style) GUI frames a few days ago. MarkMLl ---------------------------------------------------------------------- SymbolicFrank · 2022-10-28 20:53 Quote from: MarkMLl on October 28, 2022, 10:24:54 pmThe first fundamental problem is that threads/coroutines share memory with one another. But if they didn't they'd be processes. Yes, this is the root of the problem. Starting a process on Windows is relatively expensive, so Microsoft came up with something that does away with the expensive stuff. At that time, it probably seemed a great idea. Now, nobody cares. The default stack space is 1 MB. Which was more than your total memory at the start. Today, it's peanuts, even for your phone. And loading an executable from your hard disk: it's an SSD and it is cached. Nobody is going to notice. And yes, cache coherence is the second hardest problem to tackle for Intel and AMD (after branch prediction), because of unified memory. NUMA is just the beginning of the solution. Tasks and latency. The main steering mechanism for the distribution of code and data is dependency. If your task needs the output from one or more different tasks to run, you have to store all of it until it is ready. And depending on the time that takes, some of it might be on slow storage by that time. That also shows, that cache memory isn't going to solve the problem. It helps, but you cannot store everything on it. Which leaves your task with data that takes different times to become available. Logistics. NUMA comes from supercomputers with distributed memory. Because having a shared main memory is far too slow: not only for the bandwidth required, but also the distance traveled and the amount of times it is routed. And if you keep increasing and unifying your cache memory, you run into the exact same problems: it becomes the new, shared, main memory. You can give your task scheduler hints: "in about 58 milliseconds, task x will be ready", and "it will take roughly 117 milliseconds to collect that data'. So it knows, when is the best time to schedule its tasks. Do it like a supercomputer ---------------------------------------------------------------------- r.lukasiak · 2022-10-29 00:27 PascalDragon, yes, I see it. It was just a leftover after my desperate improvisation. I made some changes. I create a separate TImage for each thread and free it in .ShowStatus after the last iteration in .Execute, it somehow solves the problem but not completely... I noticed my problem is not the thread itself, it's what's in .Execute. If I use TBGRATextEffectFontRenderer and/or TPhongShading everything crashes, I'm able to fire max 1 thread. If I use just simple .TextOut, it works perfectly, I can fire tens of threads at once and everything works flawlessly. It'd be cool to use these effects, I wanted my "animation" to be semi-transparent. Why would TBGRATextEffectFontRenderer or TPhongShading fail in a thread? Any idea? I tried just transparency, with just bmp.CanvasBGRA but it also crashes  @Warfley, thanks a lot for your explanation, i tested all your options and it looks like only Synchronization works correctly in my case. Queue gives me SIGSEV immediately and calling .ShowStatus directly freezes everything after firing 3-4 threads at once. what I have now: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TAnimText = class(TThread)  private    bmp:    TBGRABitmap;    effect: TBGRATextEffectFontRenderer;    shader: TPhongShading;    procedure ShowStatus;  protected    procedure Execute; override;  public    img:       TImage;    imgidx:    integer;    FIter:     integer;    FColorR:   byte;    FColorG:   byte;    FColorB:   byte;    FInitX:    word;    FInitY:    word;    FText:     string;    Done: boolean;    Constructor Create(CreateSuspended : boolean); end; constructor TAnimText.Create(CreateSuspended : boolean);begin  inherited Create(CreateSuspended);  FreeOnTerminate := True;end; procedure TAnimText.ShowStatus;begin  thrimglst[imgidx].Left:=FInitX-(bmp.Width div 2);  thrimglst[imgidx].Top:=FInitX-(bmp.Height div 2);  thrimglst[imgidx].Width:=bmp.Width;  thrimglst[imgidx].Height:=bmp.Height;  thrimglst[imgidx].Picture.Assign(bmp);   if Done then thrimglst[imgidx].Free;end; procedure TAnimText.Execute;var  i,tw,th: integer;begin  Done:=false;  for i:=0 to FIter-1 do    begin      {shader:=TPhongShading.Create;      effect:=TBGRATextEffectFontRenderer.Create(shader,true);      effect.FontEmHeight:=i;      bmp:=TBGRABitmap.Create(effect.TextSize(F Text).cx,effect.TextSize(FText).cy,BGRA(0,0,0,0));      bmp.FontQuality:= fqFineAntialiasing;      effect.TextOut(bmp,0,0,FText,                     BGRA(FColorR,FColorG ,FColorB,Round((255/FIter)*(i-(BoolTo01((i+1)>(FIter div 2))*((i-(FIter div 2))*2))))),                     taLeftJustify);}     {bmp:=TBGRABitmap.Create;      bmp.Canvas.Font.Height:=i;      bmp. SetSize(bmp.Canvas.TextWidth(FText),bmp.Canvas.TextHeight(FText));    bmp.FillTransparent;      bmp.Canvas.Brush.Style:=bsClear;;      bmp.Canvas.Font.Color:=clRed;      bmp.Canvas.TextOut(0,0,FText);}     bmp:=TBGRABitmap.Create(300,200,BGRA(0,0,0,0));      bmp.Canvas.Font.Height:=i;      bmp.Canvas.Brush.Style:=bsClear;;    bmp.Canvas.Font.Color:=clRed;      bmp.Canvas.TextOut(0,0,FText);     if i=FIter-1 then Done:=true;      Synchronize(@ShowStatus);      //Sleep(20);      bmp.Free;       if Terminated then Break;    end;   {effect.Free;  shader.Free;}end; the commented out parts in .Execute are different versions I tried, none of them was possible to do. The only way to have it work is to set a fixed size of bmp. This way I can fire as many threads I want. If I even try to resize the bmp, it crashes. I ran the app from terminal and I'm getting things like this (if use one of the first versions): QuoteQObject::startTimer: Timers can only be used with threads started with QThread QPainter::end: Painter ended with 2 saved states QWidget::repaint: Recursive repaint detected QWidget::repaint: Recursive repaint detected [FORMS.PP] ExceptionOccurred   Sender=EAccessViolation   Exception=Access violation   Stack trace:   $00000000006BBC0C   $00000000006BB9C1   $00000000006BB5C9   $00000000005EBDCB   $0000000000576929   $00000000006CFA2F   $00000000006CC909   $00000000006EF692   $00000000006D604A   $00007FD2B4DD7E0B ---------------------------------------------------------------------- r.lukasiak · 2022-10-29 00:50 now I tried Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i bmp.TextOut(0,0,FText,128);instead of bmp.Canvas.TextOut(0,0,FText) and it also fails, it gives: QuoteQObject::startTimer: Timers can only be used with threads started with QThread free(): invalid pointer Aborted ---------------------------------------------------------------------- Warfley · 2022-10-29 09:33 A different question, why do you need threading here at all? BGRA bitmaps should we hardware accelarated (i.e. painted via the GPU), so it should easiely be possible to have hundreds to thousands (if not more) of drawing operations per frame at still 30 frames per second. As I already stated here, the best way to deal with threading is to avoid it whenever possible. Just loop, either using a timer with a low interval, or using the Application Idle event, and iterate through the images and create them single threaded. This should be more than fast enough (at least when optmising your code a bit), and is much easier than involving threads. Alternatively, predraw all the images and then in the animation just cycle through them, this is how 2D games usually work using sprites, they load one texture that contains all the different textures at once and just display different parts of it depending on the state of the animation. But to answer your problem about Queue, the reason is because you call Free after calling queue, so after the queuing of the function your thread continues to free the data and then when the queued function accesses it it has a use-after-free. You need to pass the data to the function and let the function that runs in Mainthread free it. E.g. via Application.QueueAsyncCall: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TAnimText.ShowStatus(AData: PtrInt); // QueueAsyncCall allows giving data to the function calledvar  bmp: TBGRABitmap;begin  bmp := TBGRABitmap(Data); // Get bitmap from data  thrimglst[imgidx].Left:=FInitX-(bmp.Width div 2);  thrimglst[imgidx].Top:=FInitX-(bmp.Height div 2);  thrimglst[imgidx].Width:=bmp.Width;  thrimglst[imgidx].Height:=bmp.Height;  thrimglst[imgidx].Picture.Assign(bmp);  bmp.Free; // Free bitmap after use   if Done then thrimglst[imgidx].Free;end; [...]       Application.QueueAsyncCall(@ShowStatus, PtrInt(bmp)); // Call with bmp as argument for the function      //Sleep(20);      //bmp.Free; // Freed in ShowStatus after being used This is btw IMHO the "easiest" way to avoid synchronization issues, because it minimizes shared state. By basically creating a copy of the data (the BMP that is created in the thread and never used after the queuing), you don't have the main thread and the worker thread operate on the same data, but on their own copies of the data, such that you don't need to care about locks or synchronization But your method is btw not very robust, as it is not really capable of providing a reliable framerate, as when you call synchronize (or queue) adds an unpredictable delay between the call and when it is actually executed. So if you want to use threads, you should create the textures in the thread, but than have a main loop on the main thread being executed every n milliseconds (each frame) that than takes the results of your threads and draws them (polling basically polling, this is also how games work) ---------------------------------------------------------------------- PascalDragon · 2022-10-29 13:31 Quote from: SymbolicFrank on October 28, 2022, 09:53:02 pmSimplified threads (no TThread ancestor!) with a good OS-agnostic inter-thread communication is the improvement I would want most in FPC, because it's what I would use most. It has been on my TODO list to make for a long time. And, yes, that would require a lot of low-level changes. But it's a lot of time, there are always more important things to do. Nobody stops you from using BeginThread which is the underpinning of TThread directly and building some inter-thread communication mechanism. No changes to existing code needed...