[MUD-Dev] Re: TECH: Distributed Muds
shren
shren at io.com
Sat Apr 28 01:08:22 CEST 2001
On Fri, 27 Apr 2001, J C Lawrence wrote:
> On Fri, 27 Apr 2001 12:57:43 -0400
> Derek Snider <derek at idirect.com> wrote:
>> Threaded/multi-process MUDs are a completely different story. It's
>> like comparing apples and oranges.
> Given a threaded IO setup with only two threads (IO gathering and
> game process), the delta really isn't that large.
I concurr. Designing entire architectures that have 30,000 threads
dancing in harmony is very, very tricky. But threading is very useful
when you are making a thread to solve a reasonably modularized problem
that has to happen (or would be better happening) independantly from
the rest of execution.
The classic example is sound. If you're writing some code that deals
with sound buffers directly, then you can either check the sound
buffers constantly in your code, to make sure your 2 second sound
buffer stays full with new sound, or you can push it off in a seperate
thread. (I'd swear that Ultima 9 had sound in the main thread. I'm
not *certain*, but the way things would skip sometimes, it seemed very
likely.)
I find the key to working with threads is state diagrams. Your
threads have to clearly be in one state or another, to make a coherent
design.
Since I've recently been told that long posts are ok, I'll dump some
code here that I wrote to do simple interaction with threads. I'm not
saying it's the best code, but I get by with it. (Actually, I'm not
even sure this is the best version of my code.)
Note : this is windows code, but the principles are similar in the
UNIX world.
This is far from the best code you'll find. Books big enough to do
mugging in Central Park are introductions to the subject. The reason
I think it will be interesting for those who don't know threads is
that it is the smallest modular implementation of threading I could
come up with. We never launched a project that *uses* this code,
however, so I can't guarantee stability. It served, however, all the
way up to the point where we realized that our product would draw
lawsuits like flies and abandoned it. I look back now and see some
things I'd change, but all code is like that.
I've got some other thread code that is object based, developed from
this code, where you can make an object have it's own thread by having
it inherit the "runnable" class, but I think this older code is better
for a c sample, as it abuses the c++ class structure for it's
advantages to essentially write c style code.
Here's how you might use this code. As for communicating between the
thread and the outside world, the best thing to do is to make your
favorite queue class threadsafe and then put instances of it inside y,
below. Since the queues are threadsafe, the thread can push things
onto them and you can pop things outside outside of the thread.
Below, three functions are declared. Init, loop, and end. These
functions are passed as pointers to the object. The first is run
once. The second is run as a loop when hGo is set. The third is
triggered when CThread goes out of scope (x is deleted).
CThread *x;
void init(void *) {//this function is run once at the beginning}
void loop(void *) {//this function is run as the main loop}
void end(void *) {//this function is run at the end}
struct something y;
x = new CThread("blah", // an arbitrary (should be unique) thread name
&y, // this pointer is passed to init, loop, and end
init,loop,end // function pointers
);
//init(y) is run in the thread at this point
// tell it to start - these three statements should be a method of
// CThread and *not* accessed externally. Thing one I'd change -
// making a start method and a stop method inside the class.
// Exercise to the reader, and all that.
HANDLE hGo;
OpenEvent(NULL,FALSE,"blah_go");
SetEvent(hGo);
//do other stuff here. while you do,
//loop(y) is run repeatedly untill....
delete x;
//loop(y) finishes if it is currently running.
//end(y) is run.
//here's the 4 source files.
/* Thread.h begin */
#include "stdafx.h"
#include "threadproc.h"
#ifndef _thread_class_h_
#define _thread_class_h_
unsigned __stdcall threadproc(void *lpThreadParameter);
class CThread
{
friend unsigned __stdcall threadproc(void *lpThreadParameter);
protected:
HANDLE hIdle,hEnd,hEndAck,hChild;
HANDLE hGo;
char szIdle[16],szEnd[16],szEndAck[16];
char szWait[16];
unsigned int id;
void *pass;
void (*start)(void *);
void (*loop)(void *);
void (*end)(void *);
struct threadinfo ti;
public:
CThread();
CThread(char *name, void *data,
void(*a)(void *),void(*b)(void *),void(*c)(void *));
virtual ~CThread();
};
#endif
/* Thread.h end */
/* Threadproc.h begin */
#ifndef _threadproc_h_
#define _threadproc_h_
unsigned int __stdcall threadproc(void *x);
struct threadinfo {
char szIdle[16];
char szEnd[16];
char szEndAck[16];
char szGo[16];
void (*start)(void *);
void (*loop)(void *);
void (*end)(void *);
void *data;
};
#endif
/* Threadproc.h end */
/* Thread.cpp begin */
#include "stdafx.h"
#include "Thread.h"
#include "threadproc.h"
CThread::CThread()
{
}
CThread::CThread(char *name, void *data, void(*a)(void *),
void(*b)(void *),void(*c)(void *))
{
ODS("thread constructor\n");
pass = data;
// store the function pointers and the data pointer.
ti.start = a;
ti.loop = b;
ti.end = c;
ti.data = data;
// create the event names.
sprintf(ti.szIdle,"%s_idle",name);
sprintf(ti.szEnd,"%s_end",name);
sprintf(ti.szEndAck,"%s_end_ack",name);
sprintf(ti.szGo,"%s_go",name);
// create the thread running threadproc()
hChild = (HANDLE)(
_beginthreadex(NULL,0,threadproc,(void *)&ti,CREATE_SUSPENDED,&id)
);
// events
// this indicates that the thread is idle when set.
// if you want to modify the data outside the class,
// then you can, outside the class, Reset the go
// event then wait for the Idle event.
hIdle = CreateEvent(0,TRUE,FALSE,ti.szIdle);
ResetEvent(hIdle);
// end and endack are used for synchronous shutdown
hEnd = CreateEvent(0,TRUE,FALSE,ti.szEnd);
ResetEvent(hEnd);
hEndAck = CreateEvent(0,TRUE,FALSE,ti.szEndAck);
ResetEvent(hEndAck);
// to execute the loop or not execute the loop?
hGo = CreateEvent(0,TRUE,FALSE,ti.szGo);
ResetEvent(hGo);
// make the thread start
ResumeThread(hChild);
}
CThread::~CThread()
{
ODS("destructor\n");
WaitForSingleObject(hIdle,INFINITE);
ODS("thread is idle\n");
SetEvent(hEnd);
WaitForSingleObject(hEndAck,INFINITE);
ODS("thread is terminated\n");
}
/* Thread.cpp end */
/* Threadproc.cpp begin */
#include "stdafx.h"
#include "Thread.h"
#include "threadproc.h"
unsigned int __stdcall threadproc(void *in)
{
HANDLE hIdle, hEnd, hEndAck, hGo;
HRESULT hr;
struct threadinfo *pass;
pass = (struct threadinfo *)in;
// open copies of the events made inside the class.
hIdle = OpenEvent(EVENT_MODIFY_STATE,FALSE,pass->szIdle);
if (hIdle == 0) {ODS("dead idle handle!\n");}
hEnd = OpenEvent(EVENT_MODIFY_STATE,FALSE,pass->szEnd);
if (hEnd == 0) {ODS("dead end handle!\n");}
hEndAck = OpenEvent(EVENT_MODIFY_STATE,FALSE,pass->szEndAck);
if (hEndAck == 0) {ODS("dead endack handle!\n");}
hGo = OpenEvent(EVENT_MODIFY_STATE,FALSE,pass->szGo);
if (hGo == 0) {ODS("dead wait handle!\n");}
// run the start function the user passed us
pass->start(pass->data);
while (0==0)
{
// if we can go, run the main loop once
hr = WaitForSingleObject(hGo,0);
if (hr == WAIT_OBJECT_0)
{
pass->loop(pass->data);
}
// we are idle
SetEvent(hIdle);
// let the other processes have some time
Sleep(0);
// check if we've been instructed to terminate
hr = WaitForSingleObject(hEnd,0);
if (hr == WAIT_OBJECT_0)
{
SetEvent(hEndAck);
pass->end(pass->data);
_endthreadex(0);
}
// we are no longer idle
ResetEvent(hIdle);
}
return 0;
}
/* Threadproc.cpp end */
--
Give a man a fish, and you feed him for a day.
Teach a man to fish, and you've created competition.
Competition keeps you on your toes.
_______________________________________________
MUD-Dev mailing list
MUD-Dev at kanga.nu
https://www.kanga.nu/lists/listinfo/mud-dev
More information about the mud-dev-archive
mailing list