Re: a really simple C++ abstraction around pthread_t...
"Szabolcs Ferenczi" <szabolcs.ferenczi@gmail.com> wrote in message
news:8390584c-34f0-486d-b19e-4d2bfc40956c@o4g2000pra.googlegroups.com...
On Oct 30, 10:39 pm, "Chris M. Thomasson" <n...@spam.invalid> wrote:
Any suggestions on how I can improve this construct?
I think this construction is good enough for the moment. Now we should
turn to enhancing the communication part for shared objects in C++0x.
Okay.
[...]
The question is how would you hack the classes `Monitor' and `When' so
that active objects could be combined with this kind of monitor data
structure to complete the canonical example of producers-consumers.
Forget about efficiency concerns for now.
Here is a heck of a hack, in the form of a fully working program, for you
take a look at Szabolcs:
_______________________________________________________________________
/* Simple Thread Object
______________________________________________________________*/
#include <pthread.h>
extern "C" void* thread_entry(void*);
class thread_base {
pthread_t m_tid;
friend void* thread_entry(void*);
virtual void on_active() = 0;
public:
virtual ~thread_base() = 0;
void active_run() {
pthread_create(&m_tid, NULL, thread_entry, this);
}
void active_join() {
pthread_join(m_tid, NULL);
}
};
thread_base::~thread_base() {}
void* thread_entry(void* state) {
reinterpret_cast<thread_base*>(state)->on_active();
return 0;
}
template<typename T>
struct active : public T {
struct guard {
T& m_object;
guard(T& object) : m_object(object) {
m_object.active_run();
}
~guard() {
m_object.active_join();
}
};
active() : T() {
this->active_run();
}
~active() {
this->active_join();
}
template<typename T_p1>
active(T_p1 p1) : T(p1) {
this->active_run();
}
template<typename T_p1, typename T_p2>
active(T_p1 p1, T_p2 p2) : T(p1, p2) {
this->active_run();
}
// [and on and on for more params...]
};
/* Simple Moniter
______________________________________________________________*/
class moniter {
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
public:
moniter() {
pthread_mutex_init(&m_mutex, NULL);
pthread_cond_init(&m_cond, NULL);
}
~moniter() {
pthread_cond_destroy(&m_cond);
pthread_mutex_destroy(&m_mutex);
}
struct lock_guard {
moniter& m_moniter;
lock_guard(moniter& moniter_) : m_moniter(moniter_) {
m_moniter.lock();
}
~lock_guard() {
m_moniter.unlock();
}
};
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_signal(&m_cond);
}
};
#define when_x(mp_pred, mp_line) \
lock_guard guard_##mp_line(*this); \
while (! (mp_pred)) this->wait();
#define when(mp_pred) when_x(mp_pred, __LINE__)
/* Simple Usage Example
______________________________________________________________*/
#include <cstdio>
#include <deque>
#define PRODUCE 10000
#define BOUND 100
#define YIELD 2
template<typename T>
struct bounded_buffer : public moniter {
unsigned const m_max;
std::deque<T> m_buffer;
public:
bounded_buffer(unsigned const max_) : m_max(max_) {}
void push(T const& obj) {
when (m_buffer.size() < m_max) {
m_buffer.push_back(obj);
signal();
}
}
T pop() {
T obj;
when (! m_buffer.empty()) {
obj = m_buffer.front();
m_buffer.pop_front();
}
return obj;
}
};
class producer : public thread_base {
bounded_buffer<unsigned>& m_buffer;
void on_active() {
for (unsigned i = 0; i < PRODUCE; ++i) {
m_buffer.push(i + 1);
std::printf("produced %u\n", i + 1);
if (! (i % YIELD)) { sched_yield(); }
}
}
public:
producer(bounded_buffer<unsigned>* buffer) : m_buffer(*buffer) {}
};
struct consumer : public thread_base {
bounded_buffer<unsigned>& m_buffer;
void on_active() {
unsigned i;
do {
i = m_buffer.pop();
std::printf("consumed %u\n", i);
if (! (i % YIELD)) { sched_yield(); }
} while (i != PRODUCE);
}
public:
consumer(bounded_buffer<unsigned>* buffer) : m_buffer(*buffer) {}
};
int main(void) {
{
bounded_buffer<unsigned> b(BOUND);
active<producer> p(&b);
active<consumer> c(&b);
}
std::puts("\n\n\n__________________\nhit <ENTER> to exit...");
std::fflush(stdout);
std::getchar();
return 0;
}
_______________________________________________________________________
Please take notice of the following class, which compiles fine:
template<typename T>
struct bounded_buffer : public moniter {
unsigned const m_max;
std::deque<T> m_buffer;
public:
bounded_buffer(unsigned const max_) : m_max(max_) {}
void push(T const& obj) {
when (m_buffer.size() < m_max) {
m_buffer.push_back(obj);
signal();
}
}
T pop() {
T obj;
when (! m_buffer.empty()) {
obj = m_buffer.front();
m_buffer.pop_front();
}
return obj;
}
};
Well, is that kind of what you had in mind? I make this possible by hacking
the following macro together:
#define when_x(mp_pred, mp_line) \
lock_guard guard_##mp_line(*this); \
while (! (mp_pred)) this->wait();
#define when(mp_pred) when_x(mp_pred, __LINE__)
It works, but its a heck of a hack! ;^D
Anyway, what do you think of this approach? I add my own "keyword"... lol.