Help a practitioner with atomic access

A

Ark

Consider

static T foo;
..............
T get_foo(void) { return foo; }

In a multi-threaded (or -tasked) environment, I need to ensure that I
get_foo() grabs T atomically (e.g. is not preempted while accessing foo).

Are there types T for which atomic fetch /guaranteed/ portably? Does the
standard say anything on this? (I'd guess not; concurrency is not a
subject there.)

A related issue: if I modify get_foo() to
T get_foo(void)
{
T temp;
begin_critical(); //whatever it means
temp = foo;
end_critical();
return temp;
}

- and foo is /not/ a volatile, is there a guarantee that temp will be
actually created, filled and returned as written? My fear is that if a
compiler is smart enough to figure out that begin_critical() and
end_critical() do not modify foo, it may optimize the code into

T get_foo(void)
{
begin_critical();
end_critical();
return foo;
}

That I don't want to happen. On the other hand, I'd rather not make foo
volatile because while I am massaging it (within a critical section) I
don't want to turn off optimizations on it.

Any advice to a cornered practitioner?
Thanks,
- Ark
 
E

Eric Sosman

Ark said:
Consider

static T foo;
.............
T get_foo(void) { return foo; }

In a multi-threaded (or -tasked) environment, I need to ensure that I
get_foo() grabs T atomically (e.g. is not preempted while accessing foo).

Are there types T for which atomic fetch /guaranteed/ portably? Does the
standard say anything on this? (I'd guess not; concurrency is not a
subject there.)

The closest you can come is `volatile sig_atomic_t', which
is guaranteed to work as expected w.r.t. signals. But signals
themselves are all set about with implementation-defined fever
trees, and there is no surety -- in the C language itself --
that signal-safety implies thread-safety.
A related issue: if I modify get_foo() to
T get_foo(void)
{
T temp;
begin_critical(); //whatever it means
temp = foo;
end_critical();
return temp;
}

- and foo is /not/ a volatile, is there a guarantee that temp will be
actually created, filled and returned as written? My fear is that if a
compiler is smart enough to figure out that begin_critical() and
end_critical() do not modify foo, it may optimize the code into

T get_foo(void)
{
begin_critical();
end_critical();
return foo;
}

That I don't want to happen. On the other hand, I'd rather not make foo
volatile because while I am massaging it (within a critical section) I
don't want to turn off optimizations on it.

Any advice to a cornered practitioner?

The magic is all in "whatever it means." All C gives you is
the notion of "sequence points," and then it whisks most of the
gift away under the provisions of the "as-if rule."

Other standards extend the C Standard and provide additional
guarantees not found in the C language per se. For example, the
POSIX "Pthreads" standard (I forget the exact number) guarantees
that pthread_mutex_lock() and pthread_mutex_unlock() will behave
as you desire w.r.t. the code executed in between them. It is
the job of the POSIX implementation to make sure this happens the
way you want; that may or may not require constraining some of the
optimizations a non-POSIX C compiler might indulge in.

See comp.programming.threads for much more on these topics;
they're really not about C as such. There you will learn (among
other things) that `volatile' is neither necessary nor sufficient
for what you want to do. Followups set.
 
C

Christopher Layne

Ark said:
Are there types T for which atomic fetch guaranteed portably? Does the
standard say anything on this? (I'd guess not; concurrency is not a
subject there.)

A related issue: if I modify get_foo() to
T get_foo(void)
{
T temp;
begin_critical(); //whatever it means
temp = foo;
end_critical();
return temp;
}

Standard knows absolutely nothing about threads.

However, in your particular example, which I'm presuming to just be a
contrived example, I can't see how "T temp" is even suspect to being
clobbered by a concurrent action (presuming begin_critical() doesn't create
new threads and pass temp to it, etc.). I'm also figuring that you're
dyn-allocing for temp, otherwise you wouldn't be returning it either.
 
J

Jack Klein

Consider

static T foo;
.............
T get_foo(void) { return foo; }

In a multi-threaded (or -tasked) environment, I need to ensure that I
get_foo() grabs T atomically (e.g. is not preempted while accessing foo).

If you have questions about multitasking, you really need to take them
to a group that supports your particular platform. The C standard
does not define any support at all for this.
Are there types T for which atomic fetch /guaranteed/ portably? Does the
standard say anything on this? (I'd guess not; concurrency is not a
subject there.)

The closest C comes to defining anything relating to multitasking is
the type sig_atomic_t, defined in <signal.h>, which is an integer type
that can be accessed atomically even in the presence of asynchronous
interrupts. It may need to be volatile for this guarantee, and the C
standard says nothing about whether your particular multitasking
environment is equivalent.
A related issue: if I modify get_foo() to
T get_foo(void)
{
T temp;
begin_critical(); //whatever it means

I have no idea what that means either, ask in platform specific group.
temp = foo;
end_critical();
return temp;
}

- and foo is /not/ a volatile, is there a guarantee that temp will be
actually created, filled and returned as written? My fear is that if a
compiler is smart enough to figure out that begin_critical() and
end_critical() do not modify foo, it may optimize the code into

T get_foo(void)
{
begin_critical();
end_critical();
return foo;
}

That I don't want to happen. On the other hand, I'd rather not make foo
volatile because while I am massaging it (within a critical section) I
don't want to turn off optimizations on it.

Just what terrible effect do you think making it volatile will have on
your program? As to what is or is not possible really depends on your
platform and its implementation of threads. Ask there.
 
A

Ark

Jack said:
If you have questions about multitasking, you really need to take them
to a group that supports your particular platform. The C standard
does not define any support at all for this.


The closest C comes to defining anything relating to multitasking is
the type sig_atomic_t, defined in <signal.h>, which is an integer type
that can be accessed atomically even in the presence of asynchronous
interrupts. It may need to be volatile for this guarantee, and the C
standard says nothing about whether your particular multitasking
environment is equivalent.


I have no idea what that means either, ask in platform specific group.


Just what terrible effect do you think making it volatile will have on
your program? As to what is or is not possible really depends on your
platform and its implementation of threads. Ask there.

Gee... Yes it's easy to shove me to platforms or threads NG but... I
have no threads!
What I have is a small embedded system, a C compiler (IAR EWARM 4.40),
and a smallish RTOS (uC-OS/II) with a in-house-made port to the
platform. The compiler is unaware of this (or any) RTOS (yeah, the
debugger is). I know the habits of the compiler but I'd love my code to
be able to compile and run with a different compiler and/or on a
different target. You know, this portability thingy.
I wonder how much can be achieved in this direction. Turns out, not much...
Thank anyway.
- Ark
 
C

Chris Torek

Indeed.

Gee... Yes it's easy to shove me to platforms or threads NG but... I
have no threads!

In that case, you are stuck with platform- and/or compiler-specific.
What I have is a small embedded system, a C compiler (IAR EWARM 4.40),
and a smallish RTOS (uC-OS/II) with a in-house-made port to the
platform. The compiler is unaware of this (or any) RTOS (yeah, the
debugger is). I know the habits of the compiler but I'd love my code to
be able to compile and run with a different compiler and/or on a
different target. You know, this portability thingy.
I wonder how much can be achieved in this direction. Turns out, not much...

Indeed, essentially none at all.

Or rather: you can (and apparently already have) define(d) your
own "start critical section" and "end critical section" macros
and/or functions, and write ones that work on your particular
compiler-plus-platform combination. Then, if you change one or
both of those, you need only rewrite those macros/functions.

(It turns out that some compilers, e.g., gcc, have general
"compiler-wide" mechanisms for preventing the compiler from moving
instructions across critical-section barriers. Combining these
with platform-specific barriers, such as the "memory barrier"
instruction on SPARCv9 or "eieio" instruction on PowerPC, does the
job. But note that *both* the gcc-specific trick *and* the
platform-specific trick are required here: if you use some other
compiler, even on the same CPU, you may need some other technique.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,263
Messages
2,571,062
Members
48,769
Latest member
Clifft

Latest Threads

Top