tags:

views:

146

answers:

4

I've been looking at the GCC docs for defining macros and it looks like what I want isn't possible, but I figure if it is, someone here would know.

What I want to do is define this macro:

synchronized(x) {
  do_thing();
}

Which expands to:

{
    pthread_mutex_lock(&x);
    do_thing();
    pthread_mutex_unlock(&x);
}

In C++ I could just make a SynchronizedBlock object that gets the lock in its constructor and unlocks in the destructor, but I have no idea how to do it in C.

I realize I could use a function pointer in the form synchronized(x, &myfunction);, but my goal is to make some C code look as much like Java as possible. And yes, I know this is evil.

+4  A: 

Here's a start, but you may need to tweak it:

#define synchronized(lock, func, args...) do { \
    pthread_mutex_lock(&(lock)); \
    func(##args); \
    pthread_mutex_unlock(&(lock)); \
} while (0)

Use like this (unfortunately, not the Java-like syntax you wanted):

synchronized(x, do_thing, arg1, arg2);
Jonathan
It looks like the `do ... while(0)` aren't necessary, the `{ .. }` alone work (at least with gcc). This still requires a block like `synchronized(x, function)` instead of `synchronized(x){ /* function */ }` though.
Brendan Long
Nevermind, i see what the `do...while(0)` is for.
Brendan Long
Here's the reason for the do/while: http://kernelnewbies.org/FAQ/DoWhile0
Jonathan
+10  A: 

EDIT: Changed to nategoose's version

#define synchronized(lock) \
for (pthread_mutex_t * i_#lock = &lock; i_#lock;
     i_#lock = NULL, pthread_mutex_unlock(i_#lock)) \
    for (pthread_mutex_lock(i_#lock); i_#lock; i_#lock = NULL)

And you can use it like this:

synchronized(x) {
    do_thing(x);
}

Or even without braces

synchronized(x)
    do_thing();
Joe D
Very impressive. Even the line numbers in errors would come out right. The only real faults is that the lock argument shouldn't appear twice within the macro and you left out most of your trailing `\\`s
nategoose
That is simultaneously amazing and horrifying. I never would've thought of that.
Brendan Long
This should fix both: `#define synchronized(lock)` \`for (pthread_mutex_t * i_ = i_; i_ = NULL)` \` for (pthread_mutex_lock(i_); i_;` \` pthread_mutex_unlock(i_), i_ = NULL)`
nategoose
or, with a single `for()` and a check if the lock has been aquired: `#define synchronized(MUTEX) \ for(pthread_mutex_t *synchronized_mutex_ = \ synchronized_mutex_ \ pthread_mutex_unlock(synchronized_mutex_), synchronized_mutex_ = 0)`
Christoph
+1 (well to be split with @nategoose) for a very efficient version of scope bound ressource management. Just a bit nit picking: you should have `()` around the evaluation of `lock`, and one should mention that this needs C99 because of the `for`-scope variable.
Jens Gustedt
Also, this has some pitfalls concerning flow control. `break`, `continue`, `goto` and `return` may do different than the programmer expects: the first two will just terminate the `for` inside the macro, and not of an enclosing construct. The later two will jump over the `pthread_mutex_unlock`, so the lock will not be released.
Jens Gustedt
@Jens: Drat! I left out the `()` when I retyped it (trying to make it readable as a comment). Flow control will most definitely do bad things here, which is sad because this is such a clever trick otherwise.
nategoose
This version won't let you nest synchronized blocks. To fix that I changed `i_` to `i_##lock` (so `synchronized(some_lock)` expands to `for(pthread_mutex_t *i_some_lock = ...`. As a bonus, `synchronized(some_lock) synchronized(some_lock) {}` will cause a compiler error.
Brendan Long
Oh, except adding `##lock` makes it not work with struct members :(
Brendan Long
Ok, so I can make them nestable, but not with magic deadlock checking by using Santiago Lezica's code so each lock is named `_lock_ ## __COUNTER__`
Brendan Long
@nategoose, @Brendan, there are ways to make such macros a bit more controllable for `break` etc and to have them properly nested without using extensions like `__COUNTER__`. P99 will provide a general framework for that: http://p99.gforge.inria.fr/p99-html/group__preprocessor__blocks_gaa864200ae44c5666b1c42cfa60836a63.html#gaa864200ae44c5666b1c42cfa60836a63
Jens Gustedt
+1  A: 

This was the best I came up with:

#define synchronized(x, things) \
      do { \
           pthread_mutex_t * _lp = &(x); \
           pthread_mutex_lock(_lp);      \
           (things);                     \
           pthread_mutex_unlock(_lp);    \
      } while (0)

...

        synchronized(x,(
                          printf("hey buddy\n"),
                          a += b,
                          printf("bye buddy\n")
                        ));

Note that you have to use the rarely used comma operator and there are restrictions to what code can live within the (not quite java-like) synchronization code list.

nategoose
+1  A: 

Very interesting question!

I looked at the other answers and liked the one using for. I have an improvement, if I may! GCC 4.3 introduces the COUNTER macro, which we can use to generate unique variable names.

#define CONCAT(X, Y) X##__##Y
#define CONCATWRAP(X, Y) CONCAT(X, Y)
#define UNIQUE_COUNTER(prefix) CONCATWRAP(prefix, __COUNTER__)

#define DO_MUTEX(m, counter) char counter; \
for (counter = 1, lock(m); counter == 1; --counter, unlock(m))

#define mutex(m) DO_MUTEX(m, UNIQUE_COUNTER(m))

Using those macros, this code...

mutex(my_mutex) {
    foo();
}

... will expand to...

char my_mutex__0;
for (my_mutex__0 = 1, lock(my_mutex); my_mutex__0 == 1; --my_mutex__0, unlock(m)) {
    foo();
}

With my_mutex__n starting in 0 and generating a new name each time its used! You can use the same technique to create monitor-like bodies of code, with a unique-but-unknown name for the mutex.

Santiago Lezica
I like the approach, but it doesn't work if I pass a struct member. `synchronized(object->lock) { /*stuff*/ }` expands to `char object->lock__0; /*etc*/`, which causes a problem because lock__0 isn't a struct member.
Brendan Long
Ahh, true =( well, the modified answer above looks better anyway, and doesn't need counters. Cheers!
Santiago Lezica
Just naming the variable `_lock_ ## __COUNTER__` seems to work (and solve a problem in the other implementation).
Brendan Long