Sign in to follow this  
Tispe

Class that has its own member thread

Recommended Posts

Tispe    1468
Hi

How do you make a Class that completly encapsulates thread creation and management? Something along these lines:

[CODE]
//This class uses its own thread to load Data files and prepare them for the main thread
#ifndef DATALOADER_H
#define DATALOADER_H
class DATALOADER
{
public:
DATALOADER()
{
//init
InitializeCriticalSection(&MainMutex);
InitializeCriticalSection(&LoaderMutex);
LoaderThread = CreateThread( 0, 0, BackgroundThread_Loop, 0, 0, 0);
}
~DATALOADER()
{
//release
WaitForSingleObject(backgroundThread, INFINITE);
DeleteCriticalSection(&loadRequestMutex);
DeleteCriticalSection(&loadResultMutex);
}
//Main thread functions
bool SendMessageToLoaderThread(Message &msg);
bool ReadMessageFromLoaderThread(Message &msg);
//The thread function
DWORD WINAPI BackgroundThread_Loop( LPVOID lpParam )
{
bool quit = false;
while(!quit)
{
//Read messages from main thread
//Do stuff
//Send messages to Main thread
}
}
//Loader thread functions
bool SendMessageToMainThread(Message &msg);
bool ReadMessageFromMainThread(Message &msg);

protected:
//Main thread variables
HANDLE LoaderThread;

//Shared member variables, message queues
CRITICAL_SECTION MainMutex;
CRITICAL_SECTION LoaderMutex;
std::deque<Message> MainThreadInbox;
std::deque<Message> LoaderThreadInbox;

//Loading thread variables
};
#endif
[/CODE]

Share this post


Link to post
Share on other sites
rip-off    10976
I'm not entirely sure what you are asking. You can encapsulate many details, but the asynchronous nature of the class is fundamentally a leaky abstraction.

It might be easier to understand the context of your question if you could give us an example program which might use this class.

Share this post


Link to post
Share on other sites
Tispe    1468
Hi

The following code produces the following error:
WinMain.obj : error LNK2019: unresolved external symbol "public: static unsigned long __stdcall LOADER::ThreadRoutine(void *)" (?ThreadRoutine@LOADER@@SGKPAX@Z) referenced in function "public: __thiscall LOADER::LOADER(void)" (??0LOADER@@QAE@XZ)

Should the "static DWORD WINAPI ThreadRoutine(LPVOID param);" be where it is, or somewhere else? I don't like to have a global lurking around. Does it have to be static?

[CODE]
class LOADER
{
public:
LOADER()
{
//init stuff
InitializeCriticalSection(&RequestLoadMutex);
InitializeCriticalSection(&CompletedLoadMutex);
hLoadingThread = CreateThread(NULL, 0, LOADER::ThreadRoutine, this, NULL, NULL);
}
~LOADER()
{
//release stuff
DeleteCriticalSection(&RequestLoadMutex);
DeleteCriticalSection(&CompletedLoadMutex);
}

protected:
CRITICAL_SECTION RequestLoadMutex;
CRITICAL_SECTION CompletedLoadMutex;
std::vector<DWORD> Mutex_Request;
std::vector<ThreadReturnMessage> Mutex_Completed;
//System related
HANDLE hLoadingThread;
static DWORD WINAPI ThreadRoutine(LPVOID param);

};
[/CODE] Edited by Tispe

Share this post


Link to post
Share on other sites
the_edd    2109
[quote name='Tispe' timestamp='1349035239' post='4985465']
The following code produces the following error:
WinMain.obj : error LNK2019: unresolved external symbol "public: static unsigned long __stdcall LOADER::ThreadRoutine(void *)" (?ThreadRoutine@LOADER@@SGKPAX@Z) referenced in function "public: __thiscall LOADER::LOADER(void)" (??0LOADER@@QAE@XZ)

Should the "static DWORD WINAPI ThreadRoutine(LPVOID param);" be where it is, or somewhere else?
[/quote]

It should work where it is.

You probably haven't provided a definition for the function (as you did in your related-but-different first example).

[quote]
I don't like to have a global lurking around. Does it have to be static?
[/quote]
If it's not a global/namespace-scope function, then yes. Crudely speaking, non-static methods have an implicit 'this' argument, making a pointer to a method incompatible with a pointer to a (class-static/global) function, even if they have the same [i]apparent[/i] signature. There's nothing to stop the static function being private, or even a method on a forward-declared 'pimpl' class if you don't want to advertise the implementation.

It's quite common to pass 'this' to the thread's body as you are already doing. Whether you should be doing that in this class, rather than in a reusable thread wrapper is perhaps something worthy of further thought.

Share this post


Link to post
Share on other sites
Tispe    1468
Having the thread function static, does that mean that only one instance can be created? What is the problem if I were to make several instances of this class?

If the thread function was global, could it be instanced by many threads? Edited by Tispe

Share this post


Link to post
Share on other sites
rip-off    10976
You must distinguish between static class data and static class functions. Static class data means there will only be one copy of the data, regardless of the number of instances of the class. Static class functions just means that the function does not require an object to call it on - though one can define parameters of the class type to re-instate this constraint.


Regardless of whether a function is static or not, there is (conceptually - ignore inlining) a single "instance" of any function in your program.
There will be one instance of the "ThreadRoutine" function. This does not prevent you from having multiple LOADER instances, as each LOADER instance will pass its "this" pointer to the ThreadRoutine. Thus, each ThreadRoutine will act on a distinct loader:
[code]
DWORD WINAPI LOADER::ThreadRoutine(LPVOID param) {
LOADER loader = static_cast<LOADER *>(param);
// use loader
}
[/code]

Share this post


Link to post
Share on other sites
JohnnyCode    1046
DWORD WINAPI BackgroundThread_Loop( LPVOID lpParam )
{
bool quit = false;
while(!quit)
{
//Read messages from main thread
//Do stuff
//Send messages to Main thread
}
}

how do you alter the local bool quit variable so that thread returns?

Also general advice to threading would be:
-1 threads run or return and share memory between other threads of the process (Process is an OS object it does not run)
-2 threads run paralell (even on 1 core-taskmgr.exe), if they write or read shared data you may need to synchronize them.
-3 messaging should be applied in the scope of shared memory and have threads check them recently
for example, but this type of messaging cannot be applyied if you wish threads to rest adn not use CPU. If you wish threads not use CPU and have ability to invoke them with data, I would not advice to use Thread::Sleep(), for you rely on taskmgr.exe , but use a trick of synchronous Socket::Listen() function on a local socket, and until it does not return, there is no CPU usage at all,then read data from socket synchronously. So that you can simply just give data around to precesses and if they finish work, have them wait for other work without CPU using.
following thread function will use 1 core fully loaded to 100% on Windows, thats why I like windows, it is such a naive lover of aplications:)

void ThreadFunc(void* sharedanchor)
{
CClient* client=(CClient*)sharedanchor;
while (client.m_bRun)
{

}
}
So this is following abstraction I would use for Threads on c++;


#include <windows.h>

public statis class CThreadExecutor
{
public static bool RunThread(void* anchordata,DWORD WINAPI(*fncMethodPointer)(LPVOID) )// I will define this function inline
{
HANDLE id= CreateThread(
NULL, // default security attributes
0, // use default stack size
fncIdleMethod, // thread function name
(LPVOID)anchordata, // argument to thread function
0, // use default creation flags
NULL); //
if (!id)
return false;
else
return true;
};
}

Share this post


Link to post
Share on other sites
the_edd    2109
[quote name='JohnnyCode' timestamp='1349102195' post='4985761']
DWORD WINAPI BackgroundThread_Loop( LPVOID lpParam )
{
bool quit = false;
while(!quit)
{
//Read messages from main thread
//Do stuff
//Send messages to Main thread
}
}

how do you alter the local bool quit variable so that thread returns?
[/quote]

As rip-off showed, lpParam is a pointer to the LOADER object, provided you passed 'this' as the corresponding argument of CreateThread. Since the function is class-static, it has access to private members, including (for example) any boolean flags you put in the object pointed to by lpParam.

[b]However[/b], beware that simply accessing a bool in a loop without proper synchronization may or may not work, depending on the compiler you're using, the optimizations enabled in your build, the architecture of the target machine, and many other subtleties.

In many situations, a compiler is allowed to keep a cached copy of a boolean flag if it can deduce that no changes are made to it. Really, you should do one of the following:
[list]
[*]use an std::atomic<bool>, added in C++11 (or similar)
[*]or the bool should be accessed under the protection of a mutex
[*]or you should wait using a synchronization primtive such as a condition variable, Windows Event or a Semaphore.
[/list]

On Visual C++, it is tempting to declare flags as volatile as Microsoft's compiler embues small-enough volatiles with load-acquire/store-release semantics and implicit compiler reordering barriers. But take care there, because that isn't sufficient for every possible use of 'atomic bools'. Other compilers also do no such thing for volatile variables (as they aren't required to do so). A proper threading primitive should be preferred.

[quote]
Also general advice to threading would be:
-1 threads run or return and share memory between other threads of the process (Process is an OS object it does not run)
-2 threads run paralell (even on 1 core-taskmgr.exe), if they write or read shared data you may need to synchronize them.
-3 messaging should be applied in the scope of shared memory and have threads check them recently
[/quote]
I don't understand these options. Avoiding mutable shared state where possible is definitely a good thing. Polling (is that what you're saying in '-3'?) is often bad, especially on devices running off batteries.

[quote]
for example, but this type of messaging cannot be applyied if you wish threads to rest adn not use CPU.
[/quote]
Indeed, using semaphores/condition variables/events, you can have threads wait for things to happen without wasting cycles. I tend to prefer condition variables for most things, but each has its place.

[quote]
If you wish threads not use CPU and have ability to invoke them with data, I would not advice to use Thread::Sleep(), for you rely on taskmgr.exe , but use a trick of synchronous Socket::Listen() function on a local socket
[/quote]
That would probably be considered an abuse of sockets unless you have threads waiting on I/O exclusively. Again, semaphores, condition variables, events.

However, if your application is spread across multiple OS processes, then that will help to minimize shared state and using sockets may well be an appropriate communication mechanism.

[quote]
following thread function will use 1 core fully loaded to 100% on Windows, thats why I like windows, it is such a naive lover of aplications:)

void ThreadFunc(void* sharedanchor)
{
CClient* client=(CClient*)sharedanchor;
while (client.m_bRun)
{

}
}
[/quote]
Where has this come from? The first question you asked seemed to imply that you did not understand this mechanism, and yet here you've just answered your own question (?!) :)

But again, you need to make sure client.m_bRun is accessed in an appropriately thread-safe manner.

[quote]
So this is following abstraction I would use for Threads on c++;
[code]
#include <windows.h>

public statis class CThreadExecutor
[/code]
[/quote]
Is this managed C++? static classes aren't in regular C++.

[quote]
[code]
{
public static bool RunThread(void* anchordata,DWORD WINAPI(*fncMethodPointer)(LPVOID) )// I will define this function inline
{
HANDLE id= CreateThread(
NULL, // default security attributes
0, // use default stack size
fncIdleMethod, // thread function name
(LPVOID)anchordata, // argument to thread function
0, // use default creation flags
NULL); //
if (!id)
return false;
else
return true;
};
}
[/code]
[/quote]

Such a function would cause a resource leak should you ever need to wait for the thread to finish (in which case, the handle should be closed). Take a look at the thread classes in boost, poco, the C++ standard, C#, Java, etc and see how they've been done (and their respective tradeoffs).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this