views:

165

answers:

2

I want to have two procedures which can call each other, or be called from whatever threads are running, but only have one running at a time. How can I do this? Will this work correctly?

var
  cs: TCriticalSection;

procedure a;
begin
  cs.Acquire;
  try
    // Execute single threaded here. 
  finally
    cs.Release;
  end;
end;

procedure b;
begin
  cs.Acquire;
  try
    // Execute single threaded here. Maybe with calls to procedure a.
  finally
    cs.Release;
  end;
end;
+2  A: 

Yes, that will work. Procedure A can call B and vice versa within the same thread and while Thread A is using procedure A or B, Thread B has to wait when it wants to use those procedures.

See the MSDN documentation about Critical Sections: http://msdn.microsoft.com/en-us/library/ms682530%28VS.85%29.aspx

Critical sections can be nested, but for every call to Acquire you must have a call to Release. Because you have your Release call in a try .. finally clause you ensure that this happens, so your code is fine.

The_Fox
+1  A: 

While it is possible on Windows to acquire a critical section multiple times, it is not possible on all platforms, some of them will block on the attempt to re-acquire a synchronization object.

There is not really a need to allow for "nesting" here. If you design your classes properly, in a way that the public interface acquires and releases the critical section, and the implementation methods don't, and if you make sure that implementation methods never call interface methods, then you don't need that particular feature.

See also the Stack Overflow question "Recursive Lock (Mutex) vs Non-Recursive Lock (Mutex)" for some details on the bad sides of recursive mutex / critical section acquisition.

mghie
Thank you. I agree there is no need for nesting so long as the class hierarchy coincides with the things to be locked, which would normally be the case with a good design, but think it would be possible to construct a case where this does not happen. (My program is not one of them, I am just cheating.)
soid
@soid: But if you implement locking via thread-local singletons, then there wouldn't be any way to re-acquire the same critical section from the same thread. And every use case could still be implemented. IMO there's just no real (as in "there is no other way") need for recursive critical sections.
mghie
@mghie: That sounds like a neat solution. Thank you.
soid
@mghie: I am having a cold and my brain can't get itself around using a thread local singleton for locking. Would appreciate it very much if you could provide a link to an example and explanation of this approach.
Marjan Venema
@Marjan: What I mean is to have an object that calls `Acquire()` in its constructor and `Release()` in its destructor - RAII. If that's a singleton per thread, either in TLS or in a map with the thread id as the key, then the critical section can only be acquired once. I don't propose that as an alternative, because the problem described in the linked-to answers is just the same. I don't see the use case where recursive acquisition would be necessary, but if there's one it can always be implemented with non-recursive acquisition and thread-local data or an additional parameter.
mghie
@mghie: Thanks. The acronym which helped me google. I get RAII for resource protection wrt making sure that resources are always de-allocated. Am I right in thinking that it is not going to help in protecting resources from race conditions between multiple threads? If you have a lock per thread, especially one in TLS, each thread is going to have its own, and access to the protected resource won't be serialized/synchronized? And that is why you didn't propose it as an alternative? But did propose proper class design (as you described) to negate the need for recursiveness?
Marjan Venema
@Marjan: Indeed, yes to all of those. Changing the design in a way that synch objects are never re-acquired is IMO the best option, and I can't really come up with a use case where that wouldn't be possible. And the RAII was not meant to be used for the synch objects themselves, but for helper objects that act on the synch objects. But after more thinking I'd say it wasn't such a good idea to begin with. Proper design should IMO be enough in all cases.
mghie