15

I am kind of new to programming in general (about 8 months with on and off in Delphi and a little Python here and there) and I am in the process of buying some books.

I am interested in learning about concurrent programming and building multi-threaded apps using Delphi. Whenever I do a search for "multithreading Delphi" or "Delphi multithreading tutorial" I seem to get conflicting results as some of the stuff is about using certain libraries (Omnithread library) and other stuff seems to be more geared towards programmers with more experience.

I have studied quite a few books on Delphi and for the most part they seem to kind of skim the surface and not really go into depth on the subject. I have a friend who is a programmer (he uses c++) who recommends I learn what is actually going on with the underlying system when using threads as opposed to jumping into how to actually implement them in my programs first.

On Amazon.com there are quite a few books on concurrent programming but none of them seem to be made with Delphi in mind.

Basically I need to know what are the main things I should be focused on learning before jumping into using threads, if I can/should attempt to learn them using books that are not specifically aimed at Delphi developers (don't want to confuse myself reading books with a bunch of code examples in other languages right now) and if there are any reliable resources/books on the subject that anyone here could recommend.

Johan
  • 74,508
  • 24
  • 191
  • 319
Gary Becks
  • 887
  • 2
  • 13
  • 21
  • 4
    You won't find a modern take on concurrent programming using examples in Delphi. Accordingly you should get used to reading examples in other languages. I personally regard Joe Duffy's book Concurrent Programming in Windows to be a good introduction. Most of the examples there are in C# which is very easy to read across into Delphi. – David Heffernan Nov 17 '11 at 07:48
  • 1
    There's a good tutorial linked in [this answer](http://stackoverflow.com/questions/7725813/what-is-new-in-multithreading-in-delphi-xe/7729097#7729097) (and comments). – Ondrej Kelle Nov 17 '11 at 08:08
  • @David, TOndrej: you should make this an answer. – jpfollenius Nov 17 '11 at 09:12
  • 7
    Just to reiterate, "Multithreading - The Delphi Way" is by far the best overall resource. Strangely enough I have spent the last 8 years building a threading framework just to enable me to develop application code without having to use any knowledge of threading or synchronisation primitives. If all your threads process data internally, and the only way to communicate between threads is via "posted" messages (generic, not Windows), then you can basically ignore any need for thread-specific stuff and all your code is by design multi-threaded. – Misha Nov 17 '11 at 12:42

4 Answers4

20

Short answer
Go to OmnyThreadLibrary install it and read everything on the site.

Longer answer
You asked for some info so here goes:
Here's some stuff to read:

http://delphi.about.com/od/kbthread/Threading_in_Delphi.htm
I personally like: Multithreading - The Delphi Way.
(It's old, but the basics still apply)

Basic principles:
Your basic VCL application is single threaded.
The VCL was not build with multi-threading in mind, rather thread-support is bolted on so that most VCL components are not thread-safe.
The way in which this is done is by making the CPU wait, so if you want a fast application be careful when and how to communicate with the VCL.

Communicating with the VCL
Your basic thread is a decendent of TThread with its own members.
These are per thread variables. As long as you use these you don't have any problems.
My favorite way of communicating with the main window is by using custom windows Messages and postmessage to communicate asynchronically.
If you want to communicate synchronically you will need to use a critical section or a synchonize method.
See this article for example: http://edn.embarcadero.com/article/22411

Communicating between threads
This is where things get tricky, because you can run into all sorts of hard to debug synchonization issues.
My advice: use OmnithreadLibrary, also see this question: Cross thread communication in Delphi
Some people will tell you that reading and writing integers is atomic on x86, but this is not 100% true, so don't use those in a naive way, because you'll most likely get subtle issues wrong and end up with hard to debug code.

Starting and stopping threads
In old Delphi versions Thread.suspend and Thread.resume were used, however these are no longer recommended and should be avoided (in the context of thread synchronization).
See this question: With what delphi Code should I replace my calls to deprecated TThread method Suspend?
Also have a look at this question although the answers are more vague: TThread.resume is deprecated in Delphi-2010 what should be used in place?
You can use suspend and resume to pause and restart threads, just don't use them for thread synchronization.

Performance issues
Putting wait_for... , synchonize etc code in your thread effectively stops your thread until the action it's waiting for has occured.
In my opinion this defeats a big purpose of threads: speed
So if you want to be fast you'll have to get creative.

A long time ago I wrote an application called Life32.
Its a display program for conways game of life. That can generate patterns very fast (millions of generations per second on small patterns).
It used a separate thread for calculation and a separate thread for display.
Displaying is a very slow operation that does not need to be done every generation.
The generation thread included display code that removes stuff from the display (when in view) and the display thread simply sets a boolean that tells the generation thread to also display the added stuff.
The generation code writes directly to the video memory using DirectX, no VCL or Windows calls required and no synchronization of any kind.
If you move the main window the application will keep on displaying on the old location until you pause the generation, thereby stopping the generation thread, at which point it's safe to update the thread variables.
If the threads are not 100% synchronized the display happens a generation too late, no big deal.
It also features a custom memory manager that avoids the thread-safe slowness that's in the standard memory manager. By avoiding any and all forms of thread synchronization I was able to eliminate the overhead from 90%+ (on smallish patterns) to 0.

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
Johan
  • 74,508
  • 24
  • 191
  • 319
  • Mostly agree. It's very difficult to get developers out of the 'start thread, wait for result in an event-handler' call-return mindset. Newer languages have not helped - "I'm a Java guy and I can do threads! I just start them and join to wait for the results". – Martin James Nov 17 '11 at 13:06
  • 1
    Thanks a lot for the thorough and detailed answer Johan. – Gary Becks Nov 17 '11 at 20:30
  • In just a line explains the meaning behind 'synchronize'. Top answer! – user30478 May 22 '18 at 19:06
15

You really shouldn't get me started on this, but anyway, my suggestions:

  • Try hard to not use the following:

    • TThread.Synchronize
    • TThread.WaitFor
    • TThread.OnTerminate
    • TThread.Suspend
    • TThread.Resume, (except at the end of constructors in some Delphi versions)
    • TApplication.ProcessMessages
  • Use the PostMessage API to communicate to the main thread - post objects in lParam, say.

  • Use a producer-consumer queue to communicate to secondary threads, (not a Windows message queue - only one thread can wait on a WMQ, making thread pooling impossible).
  • Do not write directly from one thread to fields in another - use message-passing.
  • Try very hard indeed to create threads at application startup and to not explicitly terminate them at all.
  • Do use object pools instead of continually creating and freeing objects for inter-thread communication.

The result will be an app that performs well, does not leak, does not deadlock and shuts down immediately when you close the main form.

What Delphi should have had built-in:

  1. TWinControl.PostObject(anObject:TObject) and TWinControl.OnObjectRx(anObject:TObject) - methods to post objects from a secondary thread and fire a main-thread event with them. A trivial PostMessage wrap to replace the poor performing, deadlock-generating, continually-rewritten TThread.Synchronize.

  2. A simple, unbounded producer-consumer class that actually works for multiple producers/consumers. This is, like, 20 lines of TObjectQueue descendant but Borland/Embarcadero could not manage it. If you have object pools, there is no need for complex bounded queues.

  3. A simple thread-safe, blocking, object pool class - again, really simple with Delphi since it has class variables and virtual constructors, eg. creating a lot of buffer objects:
    myPool:=TobjectPool.create(1024,TmyBuffer);

mghie
  • 32,028
  • 6
  • 87
  • 129
Martin James
  • 24,453
  • 3
  • 36
  • 60
  • OK, great! Why has it taken 30 years only to then find that the useful stuff is supplied by a 3rd-party? I will have look at this library, (though I have stuff of my own since D3). – Martin James Nov 17 '11 at 12:17
  • Basically I have implemented your entire arhitecture within my distributed application framework, and added inter-process communication that shares exactly the same interface. – Misha Nov 17 '11 at 12:45
  • @Misha - so that's two of us now :) I don't do much inter-process comms but, if you can keep the interface basically the same, fine! – Martin James Nov 17 '11 at 14:03
  • 1
    @Martin - Misha's stuff is great - in case you use inter-process communication, you should definitely look into it. – gabr Nov 17 '11 at 14:43
  • @Martin - And your point 3) has been added on my to-do list for one of the future OmniThreadLibrary releases. – gabr Nov 17 '11 at 14:44
  • @Misha - once you have a blocking queue, an object pool is fairly trivial, (in Delphi anyway - lack of class types and no virtual constructors makes C++ etc. less than useful for this :). If I need inter-process comms, I will certainly use your stuff. – Martin James Nov 17 '11 at 16:23
5

I thought it might be useful to actually try to compile a list of things that one should know about multithreading.

  • Synchronization primitives: mutexes, semaphores, monitors
  • Delphi implementations of synchronization primitives: TCriticalSection, TMREWSync, TEvent
  • Atomic operations: some knowledge about what operations are atomic and what not (discussed in this question)
  • Windows API multithreading capabilities: InterlockedIncrement, InterlockedExchange, ...
  • OmniThreadLibrary

Of course this is far from complete. I made this community wiki so that everyone can edit.

Community
  • 1
  • 1
jpfollenius
  • 16,456
  • 10
  • 90
  • 156
2

Appending to all the other answers I strongly suggest reading a book like: "Modern Operating Systems" or any other one going into multithreading details.

This seems to be an overkill but it would make you a better programmer and you defenitely get a very good insight into threading/processes in an abstract way - so you learn why and how to use critical section or semaphores on examples (like the dining philosophers problem or the sleeping barber problem)

mrabat
  • 802
  • 7
  • 15
  • 1
    Thanks, I was looking for something like this. I know that the libraries available take care of most of the grunt work for you, but I just want to know what's actually going on behind the scenes. – Gary Becks Nov 17 '11 at 21:01