Re: a really simple C++ abstraction around pthread_t...
"Szabolcs Ferenczi" <szabolcs.ferenczi@gmail.com> wrote in message
news:980d8b20-a9bb-4912-a0fb-3f989dba5bcd@r15g2000prh.googlegroups.com...
On Oct 31, 10:14 pm, "Chris M. Thomasson" <n...@spam.invalid> wrote:
What exactly do you have in mind wrt integrating monitor, when keyword
and
active object?
I did not meant integrating all the three but it is an interesting
idea too.
I meant that if we have high level wrappers for easy coding of the
monitor construction, it is possible to put together applications
where there are active objects and passive ones for shared data
communication around. One such an example is the producers and
consumers example.
How far off the corrected version of my example?
It is promising. Better than I expected, however, the `broadcast();'
should be also included into the RAII (the `when' object).
Okay. Also, there was a little problem in the when macros. I need another
level of indirection in order to properly expand the __LINE__ macro. Here is
new version:
________________________________________________________________
class monitor {
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
public:
monitor() {
pthread_mutex_init(&m_mutex, NULL);
pthread_cond_init(&m_cond, NULL);
}
~monitor() {
pthread_cond_destroy(&m_cond);
pthread_mutex_destroy(&m_mutex);
}
struct lock_guard {
monitor& m_monitor;
lock_guard(monitor& monitor_) : m_monitor(monitor_) {
m_monitor.lock();
}
~lock_guard() {
m_monitor.unlock();
}
};
struct signal_guard {
monitor& m_monitor;
bool const m_broadcast;
signal_guard(monitor& monitor_, bool broadcast = true)
: m_monitor(monitor_), m_broadcast(broadcast) {
}
~signal_guard() {
if (m_broadcast) {
m_monitor.broadcast();
} else {
m_monitor.signal();
}
}
};
void lock() {
pthread_mutex_lock(&m_mutex);
}
void unlock() {
pthread_mutex_unlock(&m_mutex);
}
void wait() {
pthread_cond_wait(&m_cond, &m_mutex);
}
void signal() {
pthread_cond_signal(&m_cond);
}
void broadcast() {
pthread_cond_broadcast(&m_cond);
}
};
#define when_xx(mp_pred, mp_line) \
monitor::lock_guard lock_guard_##mp_line(*this); \
monitor::signal_guard signal_guard_##mp_line(*this); \
while (! (mp_pred)) this->wait();
#define when_x(mp_pred, mp_line) when_xx(mp_pred, mp_line)
#define when(mp_pred) when_x(mp_pred, __LINE__)
________________________________________________________________
Now it properly expands __LINE__ and it also automatically broadcasts.
I did not think of using the preprocessor for the task but rather some
pure C++ construction. That is why I thought that the RAII object
could not only be a simple `lock_guard' but something like a
`when_guard'. Then one does not have to explicitly place the
`broadcast();' and the `while (! (mp_pred)) this->wait();' can be part
of the constructor of the `when_guard'.
On the other hand, the preprocessor allows to keep the more natural
syntax.
There is a problem using the preprocessor hack; as-is the following code
will deadlock:
struct object : monitor {
int m_state; // = 0
void foo() {
when (m_state == 1) {
// [...];
}
when (m_state == 2) {
// [...];
}
}
};
this is because the local objects created by the first call to `when' will
still be around during the second call; the soultion is easy:
struct object : monitor {
int m_state; // = 0
void foo() {
{ when (m_state == 1) {
// [...];
}
}
{ when (m_state == 2) {
// [...];
}
}
}
};
but looks a bit awkward. Speaking of awkward, the version using pure C++ and
no pre-processor would be kind of painful to code for. You would basically
need to wrap up all the predicates in separate functions. Lets see here...
It would be something like this:
class when {
monitor& m_monitor;
public:
template<typename P>
when(monitor* monitor_, P pred) : m_monitor(*monitor_) {
m_monitor.lock();
while (! pred()) m_monitor.wait();
}
~when() {
m_monitor.broadcast();
m_monitor.unlock();
}
};
template<typename T>
struct buffer : monitor {
unsigned const m_max;
std::deque<T> m_buffer;
bool pred_push() const {
return m_buffer.size() < m_max;
}
bool pred_pop() const {
return ! m_buffer.empty();
}
public:
buffer(unsigned const max_) : m_max(max_) {}
void push(T const& obj) {
when guard(this, pred_push);
m_buffer.push_back(obj);
}
T pop() {
when guard(this, pred_pop);
T obj = m_buffer.front();
m_buffer.pop_front();
return obj;
}
};
This would work, but it forces you to create a special function
per-predicate. Also, it suffers from the same problem the macro version
does. Two when guard objects residing in the same scope will deadlock...
Humm, personally, I kind of like the macro version better. It seems cleaner
for some reason and allows one to use full expressions for the predicate
instead of a special function. What would be neat is if I use an expression
as a template parameter and have it be treated as if it were a function. The
preprocessor makes this easy...