Re: Critical Section
On 26 =D7 =D7=95=D7=91=D7=9E=D7=91=D7=A8, 06:39, Joseph M. Newcomer <newc=
On Wed, 24 Nov 2010 21:19:34 -0800 (PST), Goran <goran.pu...@gmail.com> w=
On Nov 24, 9:50 pm, Joseph M. Newcomer <newco...@flounder.com> wrote:
Why is it so wrong to call Lock/Unlock? Well, Lock is not so bad, but
Unlock is catastrophic for exception-safety. Ask yourself this: are
you sure that there will be no exceptions between calls to Lock and
Unlock? And what will happen if there is? Answers are: no, you are
not. Even in simplest code like yours, there can be e.g.
CMemoryException (in InsertBusyThreadId). If there indeed is an
exception, your Unlock will not be called and your critical section
will stay locked, possibly forever.
Actually, this is a Good Thing, and the point I was making is that it =
extremely important that the critical section stay locked forever. Why=
? Because if you
throw an exception, YOU HAVE NO IDEA IF THE DATA BEING PROTECTED IS CO=
Fair enough, but that's just another thing to consider: code inside a
lock needs to have "strong" exception guarantee. E.g.
CSingleLock l(&syncObject, TRUE);
ScopeGuard g1 = MakeObjGuard(container1, &c1type::pop_back);
ScopeGuard g2 = MakeObjGuard(container2, &c2type::pop_back);
// All fine, keep all data in
But here, you are assuming that the state of the object is guaranteed to =
be correct if
push_back throws an exception. But what if the exception is thrown =
partway through the
modification of a structure? push_back is not an atomic operation, =
and there is no
guarantee that if an exception is thrown in the middle of push_back that =
structure is intact, so the state cannot be determined (in fact, throwing=
generally indicates the state of the object is undefined). Overall,=
the requirement of a
"strong" guarantee of correctness is hard to achieve, and I'm not sure th=
e above code
really accomplishes it. Supposed, for example, that my requirement =
was that if any
exception is thrown, NO elements have been added to data1, data2 or data3=
.. I was using
the concept of "ScopeGuard" in 1967 (in fact, it was put in at my request=
in the project
we were working on, by the guy who invented our exception mechanism), and=
even back then
we knew that it didn't guarantee integrity.
Of course, I presume here that any cleanup operations have no-throw
exception guarantee, which is reasonable and true relatively often.
(Ground rule of the scope guard is that the function it calls is a no-
throw one. It's really the same thing as the "destructors shalt not
throw" rule of C++).
One thing Java got right was the insistence on two semantics being handle=
d correctly; in
fact, it is an error to call a function that can throw without putting a =
handler in or
declaring the function that is doing the calling declares the exception a=
s one it throws.
C# got this completely wrong, as does C++. I have programmed in Jav=
a, C#, and of course,
C++, and I much prefer the Java semantics. It is sometimes a pain t=
o deal with, but it
means there are no surprises and the code is more robust. I don't e=
ven have the option of
requesting this behavior in C++, which I think is a real pain.
"feature" guarantees the lock is unlocked without actually guaranteein=
g that the data
integrity is preserved, which is really, really dangerous. Not to ment=
ion stupid. I'd
rather have the data stay locked forever than to be unlocked and broke=
n, which is what the
CSingleLock mechanism promises.
Using lock/unlock guarantees that data is locked/unlocked without a
guarantee that data integrity is preserved just the same.
No, the explicit lock/unlock does have the side effect that if an excepti=
on is thrown, the
lock is NOT unlocked, leaving the corrupted data inaccessible/ I'd =
rather if fail by
leaving the data inaccessible than fail by allowing corrupted data to bec=
I'd rather deal with a program hang than a program that can produce erron=
without warning. Or one which turns an error into an access fault o=
r other obnoxious
In the example above, if you use explicit lock/unlock and no scope
guard (or try/catch), and container2.push_back fails, you have utterly
horrible bug: the lock is there forever for any thread except the one
where container2.push_back failed, data is not consistent even in that
thread, and you can touch it later. In other words, CSingleLock or
explicit lock/unlock, you, the programmer, have to insure data
integrity. Both approaches are a 100% orthogonal to shared data
But having the lock there forever is GOOD! It means that potentiall=
y corrupted data is
inaccessible! RAII guarantees that potentially corrupted data becom=
Sure, RAII idiom, like all similar things of the sort, can lull you
into a false sense of security. But when used properly (e.g like
above), it's a good code-clarity help (in the example, the alternative
are repetitive try/catch-es, meh).
Since the above example does not convince me that (in general) data integ=
rity can be
maintained, I'm suspicious that this ispossible. Years of programmi=
ng multiprocessors and
operating systems taught me that anything which gives the illusion of cor=
of providing correctness ends up creating code which is unmaintainable an=
d hard to debug.
I don't believe that the release of the scope guard can be guaranteed to =
exceptions; in fact, this contradicts years of experience.
By the way, one of the most highly-regarded C++ libraries, boost, also
uses a variant of CSingleLock (they call it scoped_lock), and they
discourage explicit use of lock/unlock.
I believe this is the consequence of C++ propaganda, and not a consequenc=
e of actually
worrying about correctness. I do not judge code by how highly-regar=
ded it might be; all
C++ fantatics seem to share the same, consistent,blinders, so the opinion=
s of people who
follow the same set of religioius principles without factual backup don't=
matter very much
I spent a year making a large system completely bulletproof. RAII w=
ould not have helped
at all. Also, it was entirely possible that an attempt to recover w=
ould throw another
exception, so we had no guarantees about the "no destructor exception" ru=
le (it was not
done in C++ in 1975, but this wouldn't have helped anyway; the no destruc=
rule is naive and not really a reliable mechanism; for example, what happ=
ens if the heap
has been trashed and the destructor throws an exception because of the ba=
d heap? I need
reliability and robustness everywhere when I'm engineering a large system=
, and I really
can't rely on the *assumption* of a condition that cannot be rigorously e=
Joseph M. Newcomer [MVP]
Actually my task was to build a multithreaded socket server which is
supposed to receive HTTP messages and to treat them in some manner.
I based my work on your excellent "MFC Asynchronous Socket" Example on
But of course there were some changes between the requirements:
1. My client is a 3rd party client which sends HTTP requests and does
not send the number of bytes in the start of the message as your
2. The HTTP message could be truncated into 2 or more parts. Maunly it
is truncated into HTTP header on first packet and the body on the next
3. Point 2 causes that analyzing the recieved packets can take some
time and I need to know what to to with new packets that come while I
analyze the former ones.
Unfortunatly I got into serious troubles trying to turn your example
to be relevant for me. I guess it is a thread sync issue.
I gave up using mfc sync objects as you recommended and started to
base only on messaging, but still got problem.
I added a local Q for every server thread to keep new packets that
arrive while I am dealing with the former ones, and still I get
dangling pointer when trying to access the Q after thousands of
packets that arrived safely...
I would appreciate any help here.