Jens Gustedt's Blog

September 8, 2010

Why sem_t is not suited as an atomic counter

Filed under: lock structures, POSIX — Jens Gustedt @ 12:23

C has itself no primitive for atomic access to variables. Whenever we want to deal with some sort of counter in a threaded environment (e.g for reference counting) we have thus to be careful not to erase data that other threads might have written or be just in the course of reading.

sem_t as a counter

At a first glance, sem_t seems to fit into that need. It has an increment function sem_post, a decrement sem_trywait and a value test with sem_getvalue. If we are careful about interrupt handling (see this post) this should give us access to a “good” counting device, one might think.

First if we would like to do this an implementation of a reference counter would be relatively tedious. sem_t is meant to be waited for if the counter is 0, but here we would need it the other way round: we would need to wait until the counter falls to 0. Nevertheless this is doable, not with one semaphore but with several.

Side effects

What is more annoying is a another defect of sem_t its ill-conceived API, namely its tendency to have side effects to the system call. The sem_trywait call can be used to decrement a counter without waiting, and in particular to know once the counter has reached 0. If the counter has been decremented, so it was not 0 before, sem_trywait returns 0, otherwise it returns -1.

But the later case has a side effect: errno is set to an error code. So in that case, the caller has not only to do what he has to do but also has to do repair work, namely reset errno to 0. This may sound harmless, but it isn’t: errno is not a simple variable but an

expression which expands to a modifiable lvalue that has type int

This must be so, since errno is guaranteed to be different per thread, one thread’s errno wouldn’t influence the one of another thread. C has no concept of thread-only variables, therefore the system usually as to deploy quite a machinery to achieve a suitable mechanism.

So generally the use of sem_t might turn out to be quite inefficient for such a purpose.

Alternatives

If the API for the function would have gone a different way, namely in returning the precise error code instead of -1, this would be much simpler to handle. No access to pseudo-global variables would be necessary, no side effects would take place.

This strategy has been chosen for the other POSIX lock primitives such as pthread_mutex_t or pthread_rwlock_t.
Both are suitable for the purpose. An implementation with one pthread_mutex_t, one pthread_cond_t and an unsigned should be relatively obvious.

pthread_rwlock_t is even directly suitable for a locking structure for resource control. Just have all users of a resource issue a pthread_rwlock_rdlock when they start to use it and a pthread_rwlock_unlock when they cease to do so. If the resource is unused may then simply tested by a call to pthread_rwlock_wrlock.

Advertisements

2 Comments

  1. I disagree that the muxex/condvar/int approach is better than sem_t. One of the most useful but overlooked properties of sem_post is that it’s async-signal-safe. Thus you can use sem_t as an atomic counter that’s incremented in a signal handler – often a very useful place to make atomic increments, for instance if the signal is generated by a timer or some sort of hardware event.

    As for accessing errno, it’s some overhead, but not nearly as much as using a mutex and cond var. Personally I would not bother saving/restoring its value. Unless documented otherwise, standard library functions are permitted to clobber errno, and I generally assume that third-party libraries and application-internal interfaces may do the same. If you need the value of errno after it’s generated, you should be saving it as soon as the function that generated it returns. (Note that even strerror, perror, etc. can clobber errno!)

    Comment by R.. — January 24, 2012 @ 17:23

    • Yes and no. I agree that both have their pros and cons. Probably there is no unique solution and all depends on the goals you have for a particular data structure.

      What I’d say today, after more than a year from that post, atomic counters are best done … with atomic counters. C11 give a precise way how these atomics are to be handled and under which circumstances they are signal safe.

      The kind of counters that I had in mind, where the falling of the counter to 0 would be waited for (e.g a reference count), is best done, if you have it, with an atomic counter (C11 has it) and a futex (linux has it). My second choice then still would be an atomic counter plus mtx_t and cnd_t for the slow path, since these come with C11, too. Unfortunately C11 has no semaphores.

      Comment by Jens Gustedt — January 24, 2012 @ 19:23


RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Blog at WordPress.com.