Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are some of the core principles needed to master multi-threading using Delphi?

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.

like image 245
Gary Becks Avatar asked Nov 17 '11 07:11

Gary Becks


4 Answers

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.

like image 84
Johan Avatar answered Nov 20 '22 05:11

Johan


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);

like image 21
Martin James Avatar answered Nov 20 '22 07:11

Martin James


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.

like image 5
2 revs Avatar answered Nov 20 '22 05:11

2 revs


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)

like image 2
mrabat Avatar answered Nov 20 '22 06:11

mrabat