Re: Critical Section
On Nov 27, 5:44 pm, dushkin <talt...@gmail.com> wrote:
On 26 =D7 =D7=95=D7=91=D7=9E=D7=91=D7=A8, 06:39, Joseph M. Newcomer <ne=
wco...@flounder.com> wrote:
See below...
On Wed, 24 Nov 2010 21:19:34 -0800 (PST), Goran <goran.pu...@gmail.com>=
wrote:
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, b=
ut
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 i=
t is actually
extremely important that the critical section stay locked forever. W=
hy? Because if you
throw an exception, YOU HAVE NO IDEA IF THE DATA BEING PROTECTED IS =
CORRECT!
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);
container1.push_back(data1);
ScopeGuard g1 = MakeObjGuard(container1, &c1type::pop_back);
container2.push_back(data2);
ScopeGuard g2 = MakeObjGuard(container2, &c2type::pop_back);
container3.push_back(data3);
// All fine, keep all data in
g2.Dismiss();
g1.Dismiss();
}
*****
But here, you are assuming that the state of the object is guaranteed t=
o be correct if
push_back throws an exception. But what if the exception is throw=
n 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 tha=
t the data
structure is intact, so the state cannot be determined (in fact, throwi=
ng an exception
generally indicates the state of the object is undefined). Overal=
l, the requirement of a
"strong" guarantee of correctness is hard to achieve, and I'm not sure =
the above code
really accomplishes it. Supposed, for example, that my requiremen=
t was that if any
exception is thrown, NO elements have been added to data1, data2 or dat=
a3. I was using
the concept of "ScopeGuard" in 1967 (in fact, it was put in at my reque=
st in the project
we were working on, by the guy who invented our exception mechanism), a=
nd 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 hand=
led 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=
as one it throws.
C# got this completely wrong, as does C++. I have programmed in J=
ava, C#, and of course,
C++, and I much prefer the Java semantics. It is sometimes a pain=
to deal with, but it
means there are no surprises and the code is more robust. I don't=
even have the option of
requesting this behavior in C++, which I think is a real pain.
*****
The RAII
"feature" guarantees the lock is unlocked without actually guarantee=
ing that the data
integrity is preserved, which is really, really dangerous. Not to me=
ntion stupid. I'd
rather have the data stay locked forever than to be unlocked and bro=
ken, 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 excep=
tion 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 b=
ecome accessible.
I'd rather deal with a program hang than a program that can produce err=
oneous results
without warning. Or one which turns an error into an access fault=
or other obnoxious
crash.
****
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
integrity.
****
But having the lock there forever is GOOD! It means that potentia=
lly corrupted data is
inaccessible! RAII guarantees that potentially corrupted data bec=
omes accessible.
****
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 int=
egrity can be
maintained, I'm suspicious that this ispossible. Years of program=
ming multiprocessors and
operating systems taught me that anything which gives the illusion of c=
orrectness instead
of providing correctness ends up creating code which is unmaintainable =
and hard to debug.
I don't believe that the release of the scope guard can be guaranteed t=
o not throw
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 conseque=
nce of actually
worrying about correctness. I do not judge code by how highly-reg=
arded it might be; all
C++ fantatics seem to share the same, consistent,blinders, so the opini=
ons of people who
follow the same set of religioius principles without factual backup don=
't matter very much
to me.
I spent a year making a large system completely bulletproof. RAII=
would not have helped
at all. Also, it was entirely possible that an attempt to recover=
would throw another
exception, so we had no guarantees about the "no destructor exception" =
rule (it was not
done in C++ in 1975, but this wouldn't have helped anyway; the no destr=
uctor exception
rule is naive and not really a reliable mechanism; for example, what ha=
ppens if the heap
has been trashed and the destructor throws an exception because of the =
bad heap? I need
reliability and robustness everywhere when I'm engineering a large syst=
em, and I really
can't rely on the *assumption* of a condition that cannot be rigorously=
enforced.
joe
****
Goran.
Joseph M. Newcomer [MVP]
email: newco...@flounder.com
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
Joe,
Actually my task was to build a multithreaded socket server which i=
s
supposed to receive HTTP messages and to treat them in some manner.
You need an HTTP server!?!? You should not be writing your own, you
have ready-made ones that you can use. I don't know if there's a free-
as-in-beer one around (would be surprised if there weren't). It's
infinitely less costly to get one and use it than to write your own.
Is this for a homework or some other form of study? Because, if it
isn't, I have this to say: whoever decided to write an HTTP server
from scratch ( I hope it's not you :-) ) is a bloody idiot.
Goran.