What's the connection between objects and threads?

D

darren

Hi

I have to write a multi-threaded program. I decided to take an OO
approach to it. I had the idea to wrap up all of the thread functions
in a mix-in class called Threadable. Then when an object should run
in its own thread, it should implement this mix-in class. Does this
sound like plausible design decision?

I'm surprised that C++ doesn't have such functionality, say in its
STL. This absence of a thread/object relationship in C++ leads me to
believe that my idea isn't a very good one.

I would appreciate your insights. thanks
 
G

Gianni Mariani

darren said:
Hi

I have to write a multi-threaded program. I decided to take an OO
approach to it. I had the idea to wrap up all of the thread functions
in a mix-in class called Threadable. Then when an object should run
in its own thread, it should implement this mix-in class. Does this
sound like plausible design decision?

I'm surprised that C++ doesn't have such functionality, say in its
STL. This absence of a thread/object relationship in C++ leads me to
believe that my idea isn't a very good one.

The next revision of the C++ standard does in fact have thread support.

The threading concepts in the next revision will probably work with what
you describe but there is a few newer concepts - one is called a "future".

In the meantime, there are some alternative libraries - boost implements
many of the standard's new features already as well as other like
commonc++ and Austria c++ also have cross platform threading support for
C++.

In most of my newer projects, I use thread pools which is very different
to threads.
 
S

Szabolcs Ferenczi

"What's the connection between objects and threads?"

Good question. In C++ there is no connection between objects and
threads since there are no threads in C++.

C++ does not have the notion of the process or the thread of
computation at the language level. On the other hand, C++ does have
the notion of the object at the language level. It is an object-
oriented programming language but not a concurrent object-oriented
one. The bad news is that it is not going to be one either.

The good news is that you can introduce threads of computation into a C
++ program via some libraries. The coming C++0x would even include
such a library, though.

So, you can take any concurrency libraries to mix threads into an
object-oriented program in C++. Objects and threads of computation are
usually orthogonal to each other. Objects are (passive) data
structures and, on the other hand, threads of computation are rather
activities with their own program counters in your program.

You can keep them separate as was done in the first class-based
concurrent programming language, Concurrent Pascal, or you can combine
them together as was done, for instance, in an early language proposal
called Distributed Processes (DP).
I had the idea to wrap up all of the thread functions
in a mix-in class called Threadable."

What do you mean by "thread functions"?
Does this
sound like plausible design decision?

It looks like you have the approach in mind where the objects are data
structures (passive elements) and the threads of computation (active
elements) are orthogonal to each other. In this case you could keep
them separate and do not intermix them, it would not be a good idea.
Although, if you must use any lower level library (such as Pthread),
it is a good idea to build your own higher level abstractions, I
think.

In this case it is very important to make a difference between the
objects which are meant to be used exclusively from one thread of
computation only and between the objects which are to be shared among
many threads. The shared objects must be protected so that they could
be accessed by the threads in a mutually exclusive way only (see the
Monitor concept).

Best Regards,
Szabolcs
 
D

darren

Thanks for the responses everybody.

I have actually heard of Boost. I saw it in another post on this user
group about threads. My dilemma is that for this project, we were
specifically told that we must use the pthread library and our
deliverable must only be source code and a Makefile.

Doest Boost use pthread library calls in its implementation, or are
they up to something else? Maybe I'll email my prof and ask if using
Boost is permitted.

As for my current idea:

I planned to write a class called myThreads which would wrap up calls
to the pthread library, like pthread_create. Then anytime i need a
thread I'll instantiate one of these objects. I was thinking that in
one of these 'myThread" constructors, a new thread would be created
and ran. Again, i'm new to thread programming, so i dont know if this
will work or not.

there was also mention about threadpools. Our instructor said this may
be a good way to implement our project. How are they significantly
different than concepts of thread programming (as somebody here
mentioned they were).

thanks again.
 
D

darren

Thanks for the responses everybody.
I have actually heard of Boost. I saw it in another post on this user
group about threads. My dilemma is that for this project, we were
specifically told that we must use the pthread library and our
deliverable must only be source code and a Makefile.
Doest Boost use pthread library calls in its implementation, or are
they up to something else? Maybe I'll email my prof and ask if using
Boost is permitted.
As for my current idea:
I planned to write a class called myThreads which would wrap up calls
to the pthread library, like pthread_create. Then anytime i need a
thread I'll instantiate one of these objects. I was thinking that in
one of these 'myThread" constructors, a new thread would be created
and ran. Again, i'm new to thread programming, so i dont know if this
will work or not.

[...]

You don't generally want to create threads in constructors because of a
possible race-condition. Think of starting a thread in an object's
constructor which starts to run and operate on it _before_ its ctor has
finished... Anyway:

http://groups.google.com/group/comp.programming.threads/browse_frm/th...
(please read all!)

Here is some very simple source-code that might help you:
____________________________________________________________________

/* VERY Simple Thread Library Code
______________________________________________________________*/
#include <pthread.h>

class thread_base;
extern "C" void* thread_base_entry(void*);
static void thread_create(thread_base* const);
static void thread_join(thread_base* const);

class thread_base {
pthread_t m_id;
friend void* thread_base_entry(void*);
friend void thread_create(thread_base* const);
friend void thread_join(thread_base* const);
virtual void on_thread_entry() = 0;
public:
virtual ~thread_base() throw() = 0;

};

thread_base::~thread_base() throw() {}

void* thread_base_entry(void* state) {
reinterpret_cast<thread_base*>(state)->on_thread_entry();
return 0;

}

void thread_create(thread_base* const _this) {
int const status = pthread_create(&_this->m_id, NULL,
thread_base_entry, _this);
if (status) {
throw int(status);
}

}

void thread_join(thread_base* const _this) {
int const status = pthread_join(_this->m_id, NULL);
if (status) {
throw int(status);
}

}

/* Very Simple Application Code
______________________________________________________________*/
#include <cstdio>

class my_thread : public thread_base {
public:
my_thread() {
std::printf("(%p)-my_thread::my_thread()\n",
reinterpret_cast<void*>(this));
}

~my_thread() throw() {
std::printf("(%p)-my_thread::~my_thread() throw()\n",
reinterpret_cast<void*>(this));
}

private:
void on_thread_entry() {
std::printf("void (%p)-my_thread::eek:n_thread_entry()\n",
reinterpret_cast<void*>(this));
}

};

#define RUN_DEPTH() 10
#define THREAD_DEPTH() 32
int main() {
int runs = 0;
for (; runs < RUN_DEPTH(); ++runs) {
int i = 0;
my_thread threads[THREAD_DEPTH()];

try {
for (; i < THREAD_DEPTH(); ++i) {
thread_create(&threads);
}
} catch (int const& e) {
std::printf("thread_create throws status %d!\n", e);
}

for (--i; i > -1; --i) {
try {
thread_join(&threads);
} catch (int const& e) {
std::printf("thread_join throws status %d!\n", e);
}
}
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
std::puts("\n\n____________________________________________\n\
Press <ENTER> to exit...");
std::getchar();
return 0;

}

____________________________________________________________________

Any questions?


hi Chris. Thanks for the reply and the sample code. I'll have to
spend some time understanding it. For starters why do you have this
ection:
class thread_base;
extern "C" void* thread_base_entry(void*);
static void thread_create(thread_base* const);
static void thread_join(thread_base* const);

I've never seen code like this. are the parameters taking pointers to
a class??? it seems like thread_create is declared twoice, once as
friend and once not...

I've also never seen extern C before, i'll have to look up what that
is.

thanks again.
 
M

ManicQin

Thanks for the responses everybody.

I have actually heard of Boost. I saw it in another post on this user
group about threads. My dilemma is that for this project, we were
specifically told that we must use the pthread library and our
deliverable must only be source code and a Makefile.

Doest Boost use pthread library calls in its implementation, or are
they up to something else? Maybe I'll email my prof and ask if using
Boost is permitted.

As for my current idea:

I planned to write a class called myThreads which would wrap up calls
to the pthread library, like pthread_create. Then anytime i need a
thread I'll instantiate one of these objects. I was thinking that in
one of these 'myThread" constructors, a new thread would be created
and ran. Again, i'm new to thread programming, so i dont know if this
will work or not.

there was also mention about threadpools. Our instructor said this may
be a good way to implement our project. How are they significantly
different than concepts of thread programming (as somebody here
mentioned they were).

thanks again.

At the company that I'm working we work the way you suggested
(which could be imply that YOU DONT WANT TO USE IT! ;) )
but it works fine and most of the times it's transparent.

My problem with thread pools (and I'll be happy if someone contradicts
me)
is that until .Net did'nt introduce thread pools I have'nt heared
about it
and it makes me worried that maybe it's a bit platform specific.
(But if you dont care than it's great method to ease your work)

If you could shed some light on your project maybe people could
give a more precise counsel on the MT method you should use...
 
D

darren

At the company that I'm working we work the way you suggested
(which could be imply that YOU DONT WANT TO USE IT! ;) )
but it works fine and most of the times it's transparent.

My problem with thread pools (and I'll be happy if someone contradicts
me)
is that until .Net did'nt introduce thread pools I have'nt heared
about it
and it makes me worried that maybe it's a bit platform specific.
(But if you dont care than it's great method to ease your work)

If you could shed some light on your project maybe people could
give a more precise counsel on the MT method you should use...

Its a multithreaded web server using pthreads and the socket api (hey
everybody else in my class looking for ideas :) ).

Here's my basic algorithm:
1. main() makes a singleton ServerKernal object to control program
flow
2. ServerKernal listens for requests on a specified port. this is done
by creating a thread that loops and continuously accepts connections.
3. A successful accepted connection is put into a queue (from the STL
library).
4. Meanwhile, back at the ranch, another thread is created and running
that continuously checks for entries into the queue.
5. if an entry is in there, a new thread is spawned to handle the
request. That thread then dies.

What do you think of this algorithm?

A couple of things i'm thinking about:
1. As per the reason i created this post, I was thinking about how
objects related to threads. At first I was thinking that an object
should create a thread. this has been advised as bad (thanks for the
advice!). Now I'm thinking that instead, threads should create
objects, and destroy them before the thread ends. for example, when a
request is in the queue, a thread is created to handle it. Within this
new thread, a RequestHandler object is instantiated. It's methods are
used to process the request. The object is then destroyed. Finally,
the thread ends.

2 I was thinking about thread pools. Maybe there could be 10 threads
set up, waiting for requests. I dont know much about thread pools yet
though. It may not be appropriate. Or, maybe the threads could be
sleeping, then awoken by signaling when a request enters the queue.
 
S

Szabolcs Ferenczi

At first I was thinking that an object
should create a thread.  this has been advised as bad (thanks for the
advice!).  

That is not a bad idea at all that an object creates a thread in the
constructor. Do not be mistaken.

There are programming models where there are objects which are active
entities. As I earlier mentioned one early proposal of this kind is
the Distributed Processes programming concept. This means that it
depends on discipline only. You can build an abstraction for yourself
where the object starts its dedicated thread as the last step in the
constructor and joins the low level thread in the destructor. That is
not a bad idea at all.

That means that you can use configurator blocks in your program where
you declare so-called thread objects and shared objects together.

{
Buffer b;
Producer p(b);
Consumer c(b);
}

The block realises a parallel block or fork-join block as they call it
in a trendy terminology.

Best Regards,
Szabolcs
 
S

Szabolcs Ferenczi

Here's my basic algorithm:
1. main() makes a singleton ServerKernal object to control program
flow
What do you think of this algorithm?

Try to avoid using singletons (if you refer to the singleton pattern).
It is not a good idea even in non-MT programs. You can always avoid
using singletons by disciplined programming. In this case you do not
need singleton either.

Best Regards,
Szabolcs
 
S

Szabolcs Ferenczi

Doest Boost use pthread library calls in its implementation, or are
they up to something else?  

As far as I know, Boost is built on Pthread library on the Unix
platform. In principle it could be implemented on any other low level
libraries as well on Unix too. That is the benefit of higher level
structures that they hide low level details.

Best Regards,
Szabolcs
 
S

Szabolcs Ferenczi

3. A successful accepted connection is put into a queue (from the STL
library).
4. Meanwhile, back at the ranch, another thread is created and running
that continuously checks for entries into the queue.

Be aware that although STL is thread-safe to a certain extent, you
must wrap around the STL data structure to make a kind of a bounded
buffer out of it.

The point is that checking the data structure and carrying out the
corresponding action must be atomic. For instance checking if there is
element in the data structure and taking that element must be atomic.

Best Regards,
Szabolcs
 
M

ManicQin

A couple of things i'm thinking about:
1. As per the reason i created this post, I was thinking about how
objects related to threads. At first I was thinking that an object
should create a thread. this has been advised as bad (thanks for the
advice!). Now I'm thinking that instead, threads should create
objects, and destroy them before the thread ends. for example, when a
request is in the queue, a thread is created to handle it. Within this
new thread, a RequestHandler object is instantiated. It's methods are
used to process the request. The object is then destroyed. Finally,
the thread ends.

2 I was thinking about thread pools. Maybe there could be 10 threads
set up, waiting for requests. I dont know much about thread pools yet
though. It may not be appropriate. Or, maybe the threads could be
sleeping, then awoken by signaling when a request enters the queue.


1. sounds good maybe you would like to read about RAII
(or auto_ptr in stl - remeber not to create a continer of auto_ptrs!)

2. use thread pools. it's easy and it's a "fire and forget"
mechanizm...

Try to avoid using singletons (if you refer to the singleton pattern).
It is not a good idea even in non-MT programs. You can always avoid
using singletons by disciplined programming. In this case you do not
need singleton either.

He is right but! The fact that "sigletons are evil" (google it) is not
always a common knowledge.
If you've learned singleton you are being expected to use it and
reciting to you prof\tester why you decided to avoid using singletons
will not always be accepted.
 
S

Szabolcs Ferenczi

5. if an entry is in there, a new thread is spawned to handle the
request. That thread then dies.

If you make the shared data structure thread safe so that multiple
threads can get element from it, you do not have to create and destroy
the thread but you can launch a certain number of worker threads that
compete to obtain a work packets from the queue. This is a simple
solution. If you want to tune it dynamically according to the work
load, a thread pool can be set up.

Best Regards,
Szabolcs
 
J

James Kanze

That is not a bad idea at all that an object creates a thread
in the constructor. Do not be mistaken.

Except that, as Chris has already pointed out, it results in a
race condition. It's a serious design error.
 
J

James Kanze

On May 18, 12:43 pm, darren <[email protected]> wrote:

[...]
He is right

Bullshit. (If you read any of Szabolcs' postings, you'll
quickly realize that he's never actually written any real code.)
You don't want to abuse them, but there are special cases where
they are the appropriate solution. In the case of detached
threads, in fact, some form of singleton is almost necessary for
a clean shutdown.
 
S

Szabolcs Ferenczi

Except that, as Chris has already pointed out, it results in a
race condition.  It's a serious design error.

Well, he did not point out anything but claimed something. He has
postings that he used to correct the next hour or the next day.

You better read carefully what I have written. I highlighted that one
needs discipline for it. It is hopless for you but at least you calm
down.

I hope I could help though.

Best Regards,
Szabolcs
 
S

Szabolcs Ferenczi

On May 18, 12:43 pm, darren <[email protected]> wrote:

    [...]
He is right

Bullshit.  (If you read any of Szabolcs' postings, you'll
quickly realize that he's never actually written any real code.)
You don't want to abuse them, but there are special cases where
they are the appropriate solution.  In the case of detached
threads, in fact, some form of singleton is almost necessary for
a clean shutdown.

Calm down, my friend, and do not write "Bullshit".

If you do not know about something, do not attack it just because of
your ignorance.

The singleton pattern is discouraged even by its creator.

Best Regards,
Szabolcs
 
J

James Kanze

On May 18, 1:13 am, ManicQin <[email protected]> wrote:

[...]
Its a multithreaded web server using pthreads and the socket
api (hey everybody else in my class looking for ideas :) ).
Here's my basic algorithm:
1. main() makes a singleton ServerKernal object to control program
flow
2. ServerKernal listens for requests on a specified port. this is done
by creating a thread that loops and continuously accepts connections.

There's really not necessarily a reason to start a separate
thread for this.
3. A successful accepted connection is put into a queue (from the STL
library).
4. Meanwhile, back at the ranch, another thread is created and running
that continuously checks for entries into the queue.
5. if an entry is in there, a new thread is spawned to handle the
request. That thread then dies.

That sounds like extra complication as well. What's wrong with
just starting the connection thread immediately when you get the
connection?
What do you think of this algorithm?

What are the constraints concerning shutdown? If you don't have
any (i.e. the only way to stop the process is to kill it), then
your connection threads are basically fire and forget: you don't
really need to keep any trace of them (which means that you
don't have any "thread" objects). In fact, unless the server
needs to access shared in memory data, it may be preferable to
use processes instead of threads. (That's what a lot of
traditional servers do: FTP, telnet, etc.)
A couple of things i'm thinking about:
1. As per the reason i created this post, I was thinking about how
objects related to threads.

They don't, necessarily. Fundamentally, the "thread" object is
somewhere in the OS, not in your code. If the thread should do
something resulting in returned data, and you will eventually
have to wait for it (join), then some sort of object is probably
desirable in your application. This object is traditionally
called a "thread" as well, but it's important to realize that it
isn't really the thread itself, it's just an interface to
certain characteristics of the thread the OS is maintaining. If
you need to support clean shutdown, then you'll need some means
of keeping track of which threads are active (which might result
in "thread" objects as well), so that you can notify them of a
desired shutdown, and wait until all of them have shutdown. But
in the simplest case of a server, it's fire and forget for each
connection. Just start the thread, and forget about it; the
thread takes care of everything itself. (Conceptually, you
might think of it as an object, and within the OS, it certainly
is, but practically, in your code, at least with pthreads or
WinThreads, it's a function with its own stack, and that's it.)
At first I was thinking that an object should create a thread.
this has been advised as bad (thanks for the advice!).

In the end, it is the call to pthread_create which will create
the thread. Depending on what you want to do with it, you might
want to create an object which manages this information. The
type of this object should not be a base class, nor should
instances be members of another object. Practically, doing so
leads to race conditions; conceptually, you're modeling
something in the OS backwards from the way the OS implements it:
in the OS, it is the thread which owns the functions it is
executing, and not vice versa---in other words, the "object"
which you execute is a member or base class of the thread, and
not vice versa. In C++, supposing you need a thread object, you
can do this in three ways: the type of the thread object can be a
template on the object to be executed, it can take over
ownership of an object create elsewhere, or it can make a copy
of an object you pass its constructor. The latter is what
Boost does, but it requires some pretty tricky template
programming (not in Boost threads, but in Boost functions, which
Boost threads uses) to work. The second solution is probably
the easiest for a beginner to implement; you define an abstract
base class Threadable or Runnable, with an pure virtual
function run, and you pass a pointer to this to your thread
object.
Now I'm thinking that instead, threads should create objects,
and destroy them before the thread ends.

Almost certainly. And each thread has its own stack on which it
can do this.
for example, when a request is in the queue, a thread is
created to handle it. Within this new thread, a
RequestHandler object is instantiated. It's methods are used
to process the request. The object is then destroyed.
Finally, the thread ends.
2 I was thinking about thread pools. Maybe there could be 10
threads set up, waiting for requests. I dont know much about
thread pools yet though. It may not be appropriate. Or, maybe
the threads could be sleeping, then awoken by signaling when a
request enters the queue.

The usual way to use thread pools is for all of the threads to
wait on a message queue. The listener thread then sends the
incoming request to the queue; depending on design constraints,
it may decide to create an additional new thread first, if no
thread is currently waiting, or to reject the connection, if no
thread picks up the message after a specified time.
 
J

James Kanze

On May 18, 1:13 am, ManicQin <[email protected]> wrote:

[...]
Its a multithreaded web server using pthreads and the socket
api (hey everybody else in my class looking for ideas :) ).
Here's my basic algorithm:
1. main() makes a singleton ServerKernal object to control program
flow
2. ServerKernal listens for requests on a specified port. this is done
by creating a thread that loops and continuously accepts connections.

There's really not necessarily a reason to start a separate
thread for this.
3. A successful accepted connection is put into a queue (from the STL
library).
4. Meanwhile, back at the ranch, another thread is created and running
that continuously checks for entries into the queue.
5. if an entry is in there, a new thread is spawned to handle the
request. That thread then dies.

That sounds like extra complication as well. What's wrong with
just starting the connection thread immediately when you get the
connection?
What do you think of this algorithm?

What are the constraints concerning shutdown? If you don't have
any (i.e. the only way to stop the process is to kill it), then
your connection threads are basically fire and forget: you don't
really need to keep any trace of them (which means that you
don't have any "thread" objects). In fact, unless the server
needs to access shared in memory data, it may be preferable to
use processes instead of threads. (That's what a lot of
traditional servers do: FTP, telnet, etc.)
A couple of things i'm thinking about:
1. As per the reason i created this post, I was thinking about how
objects related to threads.

They don't, necessarily. Fundamentally, the "thread" object is
somewhere in the OS, not in your code. If the thread should do
something resulting in returned data, and you will eventually
have to wait for it (join), then some sort of object is probably
desirable in your application. This object is traditionally
called a "thread" as well, but it's important to realize that it
isn't really the thread itself, it's just an interface to
certain characteristics of the thread the OS is maintaining. If
you need to support clean shutdown, then you'll need some means
of keeping track of which threads are active (which might result
in "thread" objects as well), so that you can notify them of a
desired shutdown, and wait until all of them have shutdown. But
in the simplest case of a server, it's fire and forget for each
connection. Just start the thread, and forget about it; the
thread takes care of everything itself. (Conceptually, you
might think of it as an object, and within the OS, it certainly
is, but practically, in your code, at least with pthreads or
WinThreads, it's a function with its own stack, and that's it.)
At first I was thinking that an object should create a thread.
this has been advised as bad (thanks for the advice!).

In the end, it is the call to pthread_create which will create
the thread. Depending on what you want to do with it, you might
want to create an object which manages this information. The
type of this object should not be a base class, nor should
instances be members of another object. Practically, doing so
leads to race conditions; conceptually, you're modeling
something in the OS backwards from the way the OS implements it:
in the OS, it is the thread which owns the functions it is
executing, and not vice versa---in other words, the "object"
which you execute is a member or base class of the thread, and
not vice versa. In C++, supposing you need a thread object, you
can do this in three ways: the type of the thread object can be a
template on the object to be executed, it can take over
ownership of an object create elsewhere, or it can make a copy
of an object you pass its constructor. The latter is what
Boost does, but it requires some pretty tricky template
programming (not in Boost threads, but in Boost functions, which
Boost threads uses) to work. The second solution is probably
the easiest for a beginner to implement; you define an abstract
base class Threadable or Runnable, with an pure virtual
function run, and you pass a pointer to this to your thread
object.
Now I'm thinking that instead, threads should create objects,
and destroy them before the thread ends.

Almost certainly. And each thread has its own stack on which it
can do this.
for example, when a request is in the queue, a thread is
created to handle it. Within this new thread, a
RequestHandler object is instantiated. It's methods are used
to process the request. The object is then destroyed.
Finally, the thread ends.
2 I was thinking about thread pools. Maybe there could be 10
threads set up, waiting for requests. I dont know much about
thread pools yet though. It may not be appropriate. Or, maybe
the threads could be sleeping, then awoken by signaling when a
request enters the queue.

The usual way to use thread pools is for all of the threads to
wait on a message queue. The listener thread then sends the
incoming request to the queue; depending on design constraints,
it may decide to create an additional new thread first, if no
thread is currently waiting, or to reject the connection, if no
thread picks up the message after a specified time.
 
M

ManicQin

What are the constraints concerning shutdown? If you don't have
any (i.e. the only way to stop the process is to kill it), then
your connection threads are basically fire and forget: you don't
really need to keep any trace of them (which means that you
don't have any "thread" objects). In fact, unless the server
needs to access shared in memory data, it may be preferable to
use processes instead of threads. (That's what a lot of
traditional servers do: FTP, telnet, etc.)


When you say processes do you mean different processes running
and communicating via files\pipelines\sockets and so on?

1) Wont it just complicate things?
2) Darren is supposed to submit his project to his prof\tester
usually spliting your projects (IMHO) to sub processes gives it a...
(maybe unjustified) scent of a bad design... Most of my professors
were
fixed on the notion that an application is one process. (I think that
spliting
your process to subs is a concept that is more acknowledged in linux
\unix platforms then
windows. AFAIK it's partially due to the reason that in linux you only
load your code
once for all the instances of the application... or something like
that... CMIIW!!!)
 

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
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top