Thread safe memory allocation

Hi!

If i want to allocate/release memory during the execution of a thread, will that automatically be thread safe?

If not, what ways could be used to make it safe in the easiest possible way?

hereunder:
TObject.Create / destroy
TComponent.Create / destroy
Setlength / refcount = 0
GetMem / ReallocMem / FreeMem


This could perhaps become a discussion rather than a question with exact answers.
In that case the best contributors will share points.

Feel free to participate :-)

classmate
LVL 2
classmateAsked:
Who is Participating?
 
Wim ten BrinkConnect With a Mentor Self-employed developerCommented:
Well, the rest is interesting for those who don't know this yet. :-)
About the documentation... In the Delphi 5 source folder you will find a file called GETMEM.INC which is probably merged with higher Delphi versions. Three functions in this are important: SysGetMem, SysFreeMem and SysReallocMem.
If you look at this source, you will see this line of code:
  if IsMultiThread then EnterCriticalSection(heapLock);

Now, what does this mean? Simple. If you create a new thread by using the TThread class or Delphi's BeginThread function then IsMultiThread will be set to true and the system will protect this code with a critical section. (You could set IsMultiThread to true yourself, btw.) The drawback of course is that this will slow down your code slightly, which is why Delphi doesn't use a critical section if you don't have multiple threads.
What does this mean? You can create memory errors if you create a multi-threaded application within Delphi if you use the Windows API directly from your code without setting this flag to true. Thus, when you use CreateThread instead of BeginThread.

Btw, Delphi just takes a block of memory (the Delphi heap) and the memory manager will divide this block internally, letting it grow whenever the system needs more memory and reallocating and releasing blocks when they're not required anymore. Thus, Delphi reserves a large block of memory and allocates the little bits of memory you need within this block. Often blocks of (multiple of) 4 KB.
0
 
tobjectpascalCommented:
I'm not quite sure what you're getting at. Why not let the thread deal with memory allocation?
0
 
classmateAuthor Commented:
hi tobjectpascal!

If two threads try to allocate memory, say through windows API, at the same time, is there a chance that the program will crash?

And then, what if  two threads call TObject.Create at the same time?

and so on.



0
Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
tobjectpascalConnect With a Mentor Commented:
allocating memory to a global var, then you should use Synchronize it, so that only 1 thread at a time can can allocate/deallocate memory. essentially, the thread goes in, you do something like EnterCriticalSection(); GetMem, whatever LeaveCriticalSection();

so basically any other thread that enters that section will be put to sleep, when it leaves the other threads will wake up, first one in and so on...


As for calling Tobject.Create,  i don't see any problems there as each instance is a pointer in memory to a new object, it's not going to end up trying to access the same part of memory at the same time.. unless you were going to keep track of the objects, then you would use Synchronize to stop other threads from accessing it at the same time. i've done quite a bit of work with threads and as long as you know when to Synchronize you should not have any problems, appart from when you access the VCL :P
0
 
andrewjbCommented:
I've never had problems, either, but is it actually guaranteed that .Create is safe?

The issue isn't with the new object that you're creating - it's whether the undelying memory manager that gives out the new memory you need is thread safe. Probably is, but where does it say that?
0
 
tobjectpascalCommented:
lol, you have a point...
0
 
classmateAuthor Commented:
Hi again, Objectpascal!
One problem with synchronize is that it depends on the main thread to process a message that in turn calls the synchronized method. Sometimes the message already being processed in the main vcl thread at the moment kan take so much time to finish, that waiting for it can be highly inefficient. Therfore i need another solution than Synchronize.

Also:
Given two simultanous calls to getmem that assigns to completely different variables. Will windows be able to do this safely, or do i have to make critical sections around all getmem calls within my program if i use threads?
0
 
classmateAuthor Commented:
hi andrewjb, that's exactly my point :-)

If TComponents are created, then there are TList objects involved also. This cannot be safe, since there also exists a TThreadList.

This code is taken from the Classes unit, Delphi 7

constructor TComponent.Create(AOwner: TComponent);
begin
  FComponentStyle := [csInheritable];
  if AOwner <> nil then AOwner.InsertComponent(Self);
end;

procedure TComponent.Insert(AComponent: TComponent);
begin
  if FComponents = nil then FComponents := TList.Create;
  FComponents.Add(AComponent);
  AComponent.FOwner := Self;
end;
0
 
classmateAuthor Commented:
So the my point is: Exactly what is thread safe, and what is not.
0
 
andrewjbConnect With a Mentor Commented:
In general, the VCL components are NOT thread-safe. So you shouldn't access, say, a TList from two threads unless you're sure you won't access it at the same time... (Else use critical sections, or Synchronise()..)

I'm sure that .create and GetMem and so on _are_ thread-safe, but I can't point you to a definitive statement that that is true. i.e. the underlying memory manager is OK, but I don't know where it tells you that..
0
 
classmateAuthor Commented:
What about Strings, dynamic arrays (setlength), and pointer types (new/dispose)
0
 
andrewjbCommented:
Again, I'd be happy to use them withouth problems, but can't give you a document to prove it...

Obviously, with all these, you have to protect each specific variable/array/pointer against simultaneous access if you're changing it from two thread, but you've already covered that..
0
 
Wim ten BrinkSelf-employed developerCommented:
Delphi has a thread-safe memory manager that will allocate and free memory for you. Any code that uses the Delphi functions to get and free memory will be using this memory manager and thus it will be thread-safe. However, if you load a DLL in your project, your DLL will be using a different memory manager, which could result in problems when e.g. the EXE creates an object and the DLL frees it again. When you do this, the EXE memory manager still doesn't know that the memory has been freed. However, the DLL memory manager never knew it was allocated. This is basically the biggest issue with memory allocation within Delphi.

Strings (huge strings, not the short strings) will cause the memory managers to get confused if you share them between EXE and DLL. So do objects and other pointer variables. But if you're not using any separate DLL's then there should be no problem.

Borland has been working on making more and more VCL components threadsafe and is doing a good job at it.

Making data threadsafe has nothing to do with getting and freeing memory, though. The problem is that two threads might be accessing the same variable. And if one thread is reading and the other is writing, it could lead to confusion. Look at the following pseudocode:

var AValue: string = 'Hello';

procedure Thread1;
begin
  AValue := 'Goodbye';
end;

procedure Thread2;
begin
  ShowMessage(AValue);
end;

Now, what could happen is that Thread1 is running and it changes the value. But halfway the change, the processor switches threads. Thus, Thread2 could be displaying something like: 'Goolo&$'. The length of the string has been set to 7 thus the extra 2 (random) characters and the first 3 characters have been replaced by the new value while the other two are still from the old value.
Now, this would be something that you would have to protect against. You have to prevent that multiple threads are doing something with the same data. Just compare it with giving a single small notebook to a class full of students and require them all to write notes in it. Those students will just tear it apart, each and everyone fighting for his own piece of notebook. It would be nicer to pass along the notebook from one student to the other, giving each his turn to write notes but it also means all other students will just have to wait their turn.
Would you like your students (threads) to fight over the data or just let them wait? :-)
0
 
classmateAuthor Commented:
hi Workshop_Alex!

>Delphi has a thread-safe memory manager that will allocate and free memory for you. Any code that uses the Delphi functions to get and free memory will be using this memory manager and thus it will be thread-safe.

This is the only interesting part of your comment (the rest i know about), but it is vital. Is there any documentation of this any place that you know of?
0
 
Russell LibbyConnect With a Mentor Software Engineer, Advisory Commented:

Actually, the above statement is only partially true; Delphi's memory manager can be thread safe, but only so long as you adhere to the following:

1.) Setting of IsMultiThread to True

Help for IsMultiThread indicates the following:

Indicates whether the application spawned additional threads using BeginThread or TThread objects.

Unit
   System

Category
  thread management routines

var IsMultiThread: Boolean;

Description

IsMultiThread is set to True by BeginThread in order to allow the memory manager to execute safely if a VCL application has more than one thread. If a second thread attempts to allocate memory, it is blocked until the first thread exits the memory manager.

---

So, for example, if one were to call CreateThread directly, and have it execute a function address in the project, then any memory allocated in this thread would NOT be thread safe, as Delphi would not have any idea that it needed multi threaded protection. (assuming that BeginThread or TThread.Create had not been called yet).

Also if you take a look at the routines in getmem.inc (if you have source), you will see that they use a critical section for blocking when IsMultiThread is true. Now, if you implemented a custom memory manager, and decided that you didn't want to call SysGetMem, etc under the covers, then your memory manager would need to be aware of the fact that it needs to handle multithreading as well.

Just some things to be aware of,
Russell






0
 
Russell LibbySoftware Engineer, Advisory Commented:
Too slow this morning.... <g>

Russell

0
 
andrewjbCommented:
Hmmm.. That's scarey. I'd better check in the C++ world of Builder, since I currently create threads using CreateThread etc....

:-(
0
 
Wim ten BrinkSelf-employed developerCommented:
LOL@Russell

You are right by repeating me, though. People tend to ignore the IsMultiThread variable, yet they use CreateThread instead of BeginThread. Of course it is better to immediately set IsMultiThread to true when you know you're going to work on a multi-threaded application. (Or use BeginThread.)

I do wonder if many people use an alternative memory manager, though. I know there are a few alternative memory managers for Delphi but I think Delphi's solution is fast enough in most cases anyway. IsMultiThread is only used by the memory manager, though. And if you're 200% sure that your threads won't be allocating any memory then theoretically, you could set it to false again. It would speed up memory allocations in your main thread again a bit, but not really noticeable, I think.

@andrewjb, in C++ Builder you probably have the same issue. But if your thread doesn't allocate any memory then you're still safe. If you synchronise with the main thread and let the main thread allocate memory for you then you're safe again. If you use the Windows API to allocate memory blocks then this will probably be safe too. (Because Delphi just allocates it's own heap and the memory manager takes bytes from this heap.) And of course the chance of memory allocations colliding with one another aren't that big, unless you do a huge number of allocations. (Creating and freeing lots of objects in all threads.) Chances are that this "minor bug" in your code will cause an unexpected error only once in a while.
Compare it with a crossing in the road where all cars drive at the same speed, never look at the other traffic and never stop. (Every car is some memory manager call.) If you have 10 cars passing the crossing per minute from all directions, the chance of a collision still isn't very high. (If no car stays at the crossing more more than half a second.) Using IsMultiThread means that you put a traffic light at the crossing, thus forcing cars to stop when another car is going to cross.
0
 
andrewjbCommented:
I bet it's more common than you think...

Anyway, there's a compiler flag you should set in Builder, and need to link to a different library.. It's in the help somewhere.
0
All Courses

From novice to tech pro — start learning today.