Advertisement

Multithreading within a class

Started by February 22, 2001 04:43 PM
19 comments, last by griffenjam 23 years, 11 months ago
I have a game object with a GameMain() in it, I would like to start a thread that just runs that function. I would like to keep it in the class, meaning I don''t want to have a non-class member function like this. ThreadFunc(...) { game_object.GameMain(); } I don''t like this, but my attempts to use send &GameMain for the function pointer have failed. Basicly I get an error saying that the game object func pointer doesn''t match a function pointer, which I understand, is there a work around, can I have some code? Jason Mickela ICQ : 873518 E-Mail: jmickela@pacbell.net ------------------------------ "Evil attacks from all sides but the greatest evil attacks from within." Me ------------------------------
"The paths of glory lead but to the grave." - Thomas GrayMy Stupid BlogMy Online Photo Gallery
I doubt this can be done without any external function. Problem with member-functions is, that they require an implicit parameter -- pointer to the object instance. You might give a try with the static member-functions as apparently they don''t need it, but this may not be a suitable solution for you.

Have a look at

this article
by Dan Royer. He proposes an elegeant workaround.

Advertisement
Sure, it''s possible

Make it a static function, and pass the "this" pointer as the PVOID parameter.

Static functions do not take implicit this arguments.
VK
That article hit on the basic points.

Embedding the thread process inside classes is something we do at work all the time, so it''s useful to us (we''re not game programmers, though).

The basic ingredients for a win32 solution are:
- your class
- a static member of the class that will be your threadproc function
- a member of the class to hold the thread handle
- a member of the class to hold a stop signal

If you want to do the typical "start the thread in the ctor and end it in the dtor", this is the easy-bake way:
  CWT // ClassWithThread, clever eh?{  HANDLE m_hThread; // handle to thread  bool m_bStopThread; // flag to stop thread, could be event too  static DWORD WINAPI threadProc (LPVOID); // thread entrypublic:  CWT ();  // ctor starts thread  ~CWT (); // dtor ends thread};CWT::CWT (){  m_bStopThread = false;  DWORD unusedThreadId;  m_hThread = CreateThread (    NULL, // security attributes    0, // stack size, 0: use default    threadProc, // address of start of thread    this, // LPVOID argument to threadProc, almost always this    0, // additional flags, can use "CreateSuspended" if you want    &unusedThreadId // required field to receive id, not used    );    if (INVALID_HANDLE == m_hThread)    throw nasty_exception (); // somethin'' bad happened}DWORD WINAPI CWT::threadProc (LPVOID pArg){  // almost always, the first thing you do is dereference this  CWT* pCWT = (CWT*) pArg;  // now, you have public, protected & private access to "this"  // through pCWT  pCWT->someMethod (); // etc, etc  // you''ll probably have a loop in your thread--check the  // stop flag every time..  while (!pCWT->m_bStopThread)  {    // do your thing...  }  // got m_bStopThread, do any cleanup you need to then exit  return some_return_code;}CWT::~CWT (){  m_bStopThread = true; // dtor tells thread to stop  // now wait for the thread to exit before continuing  WaitForSingleObject (m_hThread, INFINITE);  CloseHandle (m_hThread); // be nice & clean up handles  // other dtor code}  


There you have it in a nutshell. There are, of course, variations on the theme. I like to make m_bStopThread an Event, and instead of a while in my thread I have a WaitForSingleObject with a timeout so I can sleep until my next update period. You''re also not restricted to starting and stopping your thread in the ctor and the dtor; you can have separate functions. Another style thing is to have your thread do nothing but call a member function of CWT and THAT is your main thread; this precludes you from having to put pCWT-> in front of everything.

Hope that helps.
I'd recommend either making it an abstract base-class, like this:
    class BaseThread{  static unsigned ThreadKickstart(void *pThread);public:  virtual unsigned ThreadProc() = 0;  virtual ~BaseThread();  virtual bool Start(); // start thread};  

and implement pretty much the way stoffel explains,
or to do it the Java way with one interface, like this:
  class IThread{public:  virtual unsigned ThreadProc() = 0;};class Thread{  IThread *pThread;  static unsigned ThreadKickstart(void pThread);public:  Thread(IThread *pInThread);};  

The difference is that ThreadKickstart() casts its argument to (BaseThread *) in the first example and (IThread *) in the second.
Personally in most cases I prefer the first, but in some cases, especially when multiple inheritance is needed, I prefer to inherit several interfaces rather than several classes (too bad *real* interfaces is not a C++ feature yet).
However in the first example, if you want to be able to derive the class I recommend not to start the thread in the ctor of the base-class, that's the reason I added the Start()-method. This is because things you initialise in the derived ctor may not be initalised when the thread starts running (depending on thread-scheduler), since the base ctor is called first.

Edited by - amag on February 23, 2001 4:15:09 AM
Thanks alot, your info really helped.
"The paths of glory lead but to the grave." - Thomas GrayMy Stupid BlogMy Online Photo Gallery
Advertisement
First, a thread-safe boolean type for the stop event:



/////////////////////////////////////////////////////////////////////////////
// Logic event.hpp : a thread-safe boolean type
//
// Copyright © 2000 Michael Matthews
/////////////////////////////////////////////////////////////////////////////

#pragma once

namespace revamp
{

namespace logic
{

class api event
{
public:

enum { forever = INFINITE };

event(const bool set = false, const bool manual = true);
~event();

event& operator = (const bool set);
operator const bool () const;
const bool wait(const unsigned long milliseconds = forever);

private:
HANDLE handle;
};

};

};


/////////////////////////////////////////////////////////////////////////////
// Logic event.cpp : a thread-safe boolean type
//
// Copyright © 2000 Michael Matthews
/////////////////////////////////////////////////////////////////////////////

#include "Pch.hpp"
#include "Logic event.hpp"

namespace revamp
{

namespace logic
{

event::event(const bool set, const bool manual)
: handle(0)
{
handle = CreateEvent(0, manual, set, 0);

assert( handle );
}


event::~event()
{ if( handle ) CloseHandle(handle); }


event& event::operator = (const bool set)
{
if( set ) SetEvent(handle); else ResetEvent(handle);

return( *this );
}


event::operator const bool () const
{ return( WaitForSingleObject(handle, 0) == WAIT_OBJECT_0 ); }

const bool event::wait(const unsigned long milliseconds)
{ return( WaitForSingleObject(handle, milliseconds) == WAIT_OBJECT_0 ); }

};

};



Second, an abstract base class that requires only the overloading of a virtual function in a derived class:



/////////////////////////////////////////////////////////////////////////////
// Logic thread.hpp : a thread of execution
//
// Copyright © 2000 Michael Matthews
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include "Logic event.hpp"

namespace revamp
{

namespace logic
{

class api thread
{
public:
thread();
virtual ~thread();
void suspend();
void resume();

protected:
virtual void process() = 0;
const bool done() const;

private:
void stop();

static DWORD CALLBACK procedure(LPVOID);
HANDLE handle;
DWORD id;

logic::event is_done;
};


inline const bool thread::done() const { return( is_done ); }

};

};


/////////////////////////////////////////////////////////////////////////////
// Logic thread.cpp : a thread of execution
//
// Copyright © 2000 Michael Matthews
/////////////////////////////////////////////////////////////////////////////

#include "Pch.hpp"
#include "Logic thread.hpp"

namespace revamp
{

namespace logic
{

thread::thread() : handle(0), id(0), is_done()
{
handle = CreateThread(0, 0, procedure, static_cast( this ), 0, &id);

assert( handle );
assert( id );
}


thread::~thread()
{
stop();

if( handle )
{
CloseHandle(handle);

handle = 0;
id = 0;
}
}


void thread::suspend()
{
assert( this );
assert( handle );

DWORD dw = SuspendThread(handle);

assert( dw != 0xFFFFFFFF );
}


void thread::resume()
{
assert( this );
assert( handle );

DWORD dw = ResumeThread(handle);

assert( dw != 0xFFFFFFFF );
}


void thread::stop()
{
assert( this );
assert( handle );

is_done = true;

DWORD dw = WaitForSingleObject(handle, INFINITE);

assert( dw == WAIT_OBJECT_0 );
}


DWORD CALLBACK thread::procedure(LPVOID lpParam)
{
thread* const the_thread = static_cast<thread*>( lpParam );

assert( the_thread );
assert( the_thread->handle );
assert( the_thread->id );

if( the_thread->is_done ) return( -1 );

the_thread->process();

return( 0 );
}

};

};



I added those assertions to make multithreaded debugging easier.
Why not use g_evExit = CreateEvent(0, FALSE, FALSE, 0)?

if(WAIT_OBJECT_0==WaitForSingleObject(g_evExit, 0)
_endthreadex(0);
else
{
//do stuff here...
}

Magmai Kai Holmlor
- The disgruntled & disillusioned
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Magmai,

Using a non-kernel object (i.e. a regular variable) to signal for shutdown is faster than constantly calling WaitFor...()

I can''t remember exactly but don''t the WaitFor...() API commands have to transition from user mode to kernel mode and then back? If this is the case, those API calls can eat up quite a few CPU cycles per iteration.

My own thread class uses two classes instead of one.

thread and threadproc .

threadproc is a class that provides four pure virtual member functions: initinstance(), exitinstance(), shutdown(), run(). thread provides methods to resume, shutdown, create, suspend, adjust priority, query for state etc. It also has one static member function named InternalThreadProc which gets passed to CreateThread(). A user derives from threadproc , implements the member functions, instantiates it and passes it to thread ''s constructor or create() member function. InternalThreadProc then calls threadproc ''s initinstance(), run(), and exitinstance() in order.

I actually have 3 different thread classes to use I like multithreading


Dire Wolf
www.digitalfiends.com
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
quote:

Using a non-kernel object (i.e. a regular variable) to signal for shutdown is faster than constantly calling WaitFor...()



Yes, so there must be a reason for using a kernel object now, mustn''t it? I''d rather have a program that works than one really fast that doesn''t.
Besides null_pointer uses an event, not a normal variable.

null_pointer:
As I stated in my earlier post, it is a design error to start the thread in the ctor in a base-class you are supposed to derive for each thread, consider the following case:
  class mythread : public thread{  int important_variable;public:  mythread() : important_variable(5) {}  virtual void process();};//void mythread::process(){  if(important_variable != 5)    crash();}  

Now since you know that the base-class''s ctor is called before the derived, it is quite possible for the new thread to start mythread::process() before important_variable has been assigned 5, which will result in a crash. So instead you should have a start()-method (or whatever) that you call from the derived ctor.

This topic is closed to new replies.

Advertisement