Re: Named shared memory without synchronization
Thank you for your reply.
"Igor Tandetnik" wrote:
Any reasoning about the behavior of a program in the presence of
multiple threads and such is necessarily specific to a particular
implementation, not the C language in general.
Nevertheless, assuming an x64/x86 architecture (portability being a
secondary issue for me, at least for the time being) implies atomic
operations on the int level, does it not?
Some processors only support loading a register from a word-aligned
memory address with a single machine instruction. Reading an unaligned
word then requires several instructions - reading two aligned words that
the word of interest overlaps, then doing some shifts and bitwise
operations to bring two halves together in the register. The CPU can of
course be interrupted after reading one half and before reading the
other, which could change right under it.
Again, if I assume an x86/x64 architecture I think i can get away with this,
since integers will be aligned by default. I'm not aware of an instance where
this is not true (unless using #pragma pack). Again, correct me if I'm wrong.
So again, as of VC8, any read or write to volatile variable issues a
memory barrier, which allows one to avoid certain "interesting" effects
on some SMP architectures. Earlier compiler versions (and many non-MS
compilers) don't do that, so volatile does not help any for mulithreaded
synchronization.
This is good news to me, since portability is currently a secondary issue
for my app.
If I understand you correctly, using the volatile keyword will ensure (at
least with VC8) that
the solution I suggest (see details below) will also work with dual core
processors (which is not mandatory right now, but nice to have).
Of course the OS-provided synchronization functions (WaitForSingleObject
et al) and Interlocked* functions issue all the necessary memory
barriers, so one doesn't need to worry about such arcane details when
using them.
I'm not trying to achieve synchronization as such (again, see details below).
The integer seems to
be fetched again for every access.
That wouldn't be the case if you were to, say, read it in a tight loop
without any intervening function calls.
The only intervening function call was a delay call (see below).
In general, after substantial
testing, I haven't found drawbacks to this no-lock strategy.
That's the problem with multithreaded programming: the bugs have the
tendency not to come up during testing. We are talking extremely subtle
effects here: consider the task of debugging a system that only fails
seemingly randomly, approximately once a week, at the customer's site
when running on a 4-way server under heavy load. This task will be yours
if you don't take the time to understand the issues (or else, switch to
a safer implementation utilizing locks or interlocked instructions).
:)
This is exactly what I'm trying to avoid, hence my post here.
My question(s):
Is this really a stable solution?
This depends on exact details of what you are doing. From what little
you disclosed, I have my doubts.
If not, does this mean that the same strategy would work for thread
synchronization of an integral global variable?
What strategy? What are you synchonizing? Show some code.
I don't have the actual "proof-of-concept" code with me. I will post it
later if necessary.
However, I think I can describe it well enough for our purpose:
What I'm trying to achieve is a sort of watchdog behavior, where one process
(the server) will
terminate another (the client) if it does not send an "I'm alive" message
within a predefined
timeout. My suggested solution works as follows:
1. Server names shared memory after the client's executable name.
2. Server defines a pointer (we'll call it x) to an integer in the shared
memory block.
3. Server runs client (createprocess etc...).
4. Client defines a pointer (x) to an integer in the shared memory block
named after its own name
5. Client does his work. Assuming the duration of the client's "main loop"
is at most t seconds,
client resets x (e.g *x=t) every t seconds.
6. Server wakes up every second and does:
if (!((*x)--)) {
terminateprocess(client);
createprocess(client);
}
(note that in reality, the server actually provides watchdog services for
multiple clients, but I don't think it's relevant in the context of this
discussion)
To the best of my understanding, the only two concerns would be:
1. That the integer pointed at by x isn't somehow corrupted by a combined
read/write.
2. That the integer pointed to by x is properly updated when the server
reads it.
From what I gathered from your, as well as Alex's post, if I stick to
primitive types and declare them volatile I'm quite safe.
Given my description, do you share my opinion?
Thanks,
Dan