 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Lesley Anne Guest
|
Posted: Tue Feb 20, 2007 12:11 am Post subject: Passing info between threads |
|
|
(As the subject line suggests, this is actually a threading question rather
than a socket question, but threading didn't seem to fit the topics for any
of the boards here. Socket communication is why I needed the threads in the
first place though, so this was as close as I could find to a relevant place
to ask this.)
I've worked occasionally with multi-threaded apps before, but in those
instances the main VCL thread would spawn temporary helper threads which
would do one thing and then finish. I would use Synchronize() to pass info
from the helper threads back to the VCL thread, but the only info passed
from the VCL thread to the helper threads would be passed as parameters to
the thread class's constructor.
Now I've got a different situation, where I need to keep a secondary thread
active throughout the life of an application, and be able to repeatedly pass
information to it from the main VCL thread. I'm trying to figure out how to
do that in a thread-safe manner. Perhaps I should start with showing what I
have so far, and then explain my concerns about it.
Declared globally in the main form's unit, and extern in the thread class's
unit, I have:
String VCLToSctpMessage;
unsigned int VCLToSctpAssoc;
TCriticalSection *pLockSendString;
TCriticalSection *pLockAssocID;
Within one of the functions on my main form, I have:
pLockSendString->Acquire();
pLockAssocID->Acquire();
try {
VCLToSctpMessage = String(buffer);
VCLToSctpAssoc = associationID[Site];
}
__finally {
pLockSendString->Release();
pLockAssocID->Release();
}
if (sctpThread)
sctpThread->Send();
And in my SCTP thread (a TThread descendant) the Send function looks like
this:
// -----------------------------------------------
void TsctpThread::Send(void)
{
unsigned char Message[4096];
unsigned int associationID;
int Length;
pLockSendString->Acquire();
pLockAssocID->Acquire();
try {
Length = VCLToSctpMessage.Length();
strcpy(Message, VCLToSctpMessage.c_str());
associationID = VCLToSctpAssoc;
}
__finally {
pLockSendString->Release();
pLockAssocID->Release();
}
sctp_send(associationID, 0, Message, Length,
SCTP_GENERIC_PAYLOAD_PROTOCOL_ID,
SCTP_USE_PRIMARY, SCTP_NO_CONTEXT,
SCTP_INFINITE_LIFETIME,
SCTP_ORDERED_DELIVERY,
SCTP_BUNDLING_DISABLED);
}
// -----------------------------------------------
OK, now I'm pretty sure that the TCriticalSections will make it thread-safe
in terms of not having both threads access the variables at the same time,
but I'm not sure whether I can count on the sctpThread reading in the values
of the variables before another function in the main VCL thread (or another
instance of the same function) changes the variables to something else.
If the first section of code above (the part in my main form) were to fire
off twice in rapid succession, once setting VCLToSctpMessage to "hello" and
then setting it to "goodbye", could I count on the Send function in my
sctpThread picking up "hello" for the first instance and "goodbye" for the
second, or could it wind up seeing "goodbye" twice?
If my concern is valid, and it could show up as "goodbye" twice, then how do
I fix it? I got stuck with repeatedly using global variables because I
didn't think the scope rules would allow me to send local variables to the
thread, but I don't want to allow re-use of those global variables until the
thread has seen their current values. (Or would it be OK to pass local
variables to the thread? How would that work?) |
|
| Back to top |
|
 |
Paul at NCF Guest
|
Posted: Tue Feb 20, 2007 3:16 am Post subject: Re: Passing info between threads |
|
|
Hi,
I might be missing something here, or misunderstanding your design or
problem. But it seems you want to pass a string and assoc ID to the thread,
to then call sctp_send to send this information?
Why don't you just call the send method in the thread with (stack based)
parameters rather than globals:
if (sctpThread)
sctpThread->Send( String(buffer), associationID[Site] )
Then make the Send method thread safe with a critical section to do any
actions that need it (around adding or removing items from lists / queues).
eg. put the parameters passed in on to a queue, and signal the thread there
is a job to do. The thread then wakes up, and removes the oldest job from
the queue thread safely, until the queue is empty.
As I said, I might be missing something.
Regards,
Paul |
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Tue Feb 20, 2007 3:48 am Post subject: Re: Passing info between threads |
|
|
| Quote: | I might be missing something here, or misunderstanding your design or
problem. But it seems you want to pass a string and assoc ID to the
thread, to then call sctp_send to send this information?
|
Yes.
| Quote: | Why don't you just call the send method in the thread with (stack based)
parameters rather than globals:
if (sctpThread)
sctpThread->Send( String(buffer), associationID[Site] )
|
That's the effect I'm trying to achieve, but I'm having trouble figuring out
a thread-safe method of doing that.
| Quote: | Then make the Send method thread safe with a critical section to do any
actions that need it (around adding or removing items from lists /
queues).
|
If the parameters are passed in, how/where do I put in a critical section
lock? And does that do anything to help the scope problem if the VCL thread
has gone on to something else by the time the SCTP thread picks this up?
| Quote: | eg. put the parameters passed in on to a queue, and signal the thread
there is a job to do. The thread then wakes up, and removes the oldest
job from the queue thread safely, until the queue is empty.
|
How do I do that? |
|
| Back to top |
|
 |
Paul at NCF Guest
|
Posted: Tue Feb 20, 2007 4:54 am Post subject: Re: Passing info between threads |
|
|
Quick question:
What components are you trying to use, and are you dropping them on the main
form / instantiating them in the main form, or are you instantiating them in
the 'worker' thread? Or both?
ie. I create say a UDP component in the thread and don't use it or reference
it on the main form at all, that way I don't have to worry about the VCL
components and VCL being non-thread safe (or using syncronize).
Paul |
|
| Back to top |
|
 |
Paul at NCF Guest
|
Posted: Tue Feb 20, 2007 9:10 am Post subject: Re: Passing info between threads |
|
|
Hi,
Off to work soon, so just in case this helps, an example help thread:
void
ClientTask::SendAMessage( int ID,
char * Message )
{
if (MyMbx)
{
// class/structure to add params to input Q
AMessage * sl = new AMessage( int ID, char * Message );
// My own thread safe mainbox, add item to end of list. It has
critical section around add and remove methods,
// and signals the thread using its TEvent when adding
// You could use TThreadList (LockList, UnlockList, Add, Remove
etc), and TEvent directly: event->SetEvent();
MyMbx->AddMailMsg(sl);
}
}
ClientTask::Execute()
{
while (!Terminated))
{
// get msg off queue, if one exists (thread safe), or TThreadList
msg = (AMessage *) MyMbx->RemoveMailMsg();
// if no msg, wait for wakeup
if (!msg)
{
// wait for multiple objects API using TEvent handle, or
timeout
signalled = WaitForWakeUp(1000);
// get message off fron of Q, as before this is my Q system, but
you could use TThreadList
msg = (AMessage *) MyMbx->RemoveMailMsg();
}
// if msg on queue, send
if (msg)
{
SendToServer(msg);
delete msg;
}
} // do forever (or nearly)
}
| Quote: | That's the effect I'm trying to achieve, but I'm having trouble figuring
out a thread-safe method of doing that. |
|
|
| Back to top |
|
 |
Bob Gonder Guest
|
Posted: Tue Feb 20, 2007 9:29 pm Post subject: Re: Passing info between threads |
|
|
"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote:
I'm a bit late, so I'll just throw out some comments
| Quote: | pLockSendString->Acquire();
pLockAssocID->Acquire();
|
You are releasing in the wrong order.
When using multiple locks, always (in all segments or your program)
lock them in the same order, and always release in the exact opposite
order. This reduces the dreaded Deadlock.
| Quote: | pLockSendString->Release();
pLockAssocID->Release();
|
And then, it looks like you are always using Both locks, never a
single one, which makes one wonder why you need 2? A single lock
between the sender (VCL) and the receiver (thread) sould suffice.
Ok, one way to use 2:
(This is redundant and slow/inefficient [and wrong])
VCL: LockOne() // wait for Copy
SetData()
UnlockOne() // allow Copy
NotifyThread() // wake up thread
LockTwo() // wait for thread to awaken
UnlockTwo() // allow thread to complete
Thread: LockTwo()
ThreadNotify()
LockOne() // lock the data
UnlockTwo() // inform VCL we are awake
CopyData()
UnlockOne() // allow changes to data
LockTwo() // wait for VCL to resume
ProcessData()
No, there's still a race on Two.
I suppose one could attempt a Third, but it seems to be going in the
wrong direction.
There seems to be a concern with VCL being faster than Thread, and
overwritting or dropping data.
Single Lock:
VCL:
LockData() // wait for thread
SetData()
UnlockData()
NotifyThread()
Thread:
LockData()
ProcessData()
UnlockData()
Yes, there is still a race in that VCL could cycle through before
Thread gets going and locks the data.
One way out is a simple sentinal.
VCL:
static volatile bool DataFull = false;
while( DataFull ){ ProcessMessages() }
SetData()
DataFull = true
ThreadSend( DataFull )
ThreadSend( bool & DataFull ):
ProcessData()
DataFull = false
VCL can cycle through, but will get stuck until Thread has finished.
| Quote: | (Or would it be OK to pass local
variables to the thread? How would that work?)
|
You would need to wait for the thread to copy them.
VCL:
SetData();
ThreadSend( LocalData )
WaitForSingleObject( hDataAquiredEvent, 10000 );
Thread:
CopyData();
Signal( hDataAquiredEvent );
ProcessData()
(There is probably some TEvent that makes that easier) |
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Tue Feb 20, 2007 10:39 pm Post subject: Re: Passing info between threads |
|
|
"Paul at NCF" <notplmacca (AT) clara (DOT) co.uk> wrote in message
news:45da2a86 (AT) newsgroups (DOT) borland.com...
| Quote: | Quick question:
What components are you trying to use, and are you dropping them
on the main form / instantiating them in the main form, or are you
instantiating them in the 'worker' thread? Or both?
ie. I create say a UDP component in the thread and don't use it
or reference it on the main form at all, that way I don't have
to worry about the VCL components and VCL being non-thread safe
(or using syncronize).
|
The worker thread handles all SCTP communications, while the VCL
thread handles everything else (which is primarily the GUI at the
moment). There are no components for SCTP, just a library, but
that library is initialized and used exclusively from the SCTP
thread.
The thread is instantiated at application startup, and immediately
calls sctp_initLibrary() (which allocates a raw socket that will
be used in all communications, and does some other initializations
of library variables) and sctp_registerInstance() (which lets the
library know what functions to call for events, as well as things
like local addresses and port, and how many input and output
streams to use). After that the thread calls sctp_eventLoop()
continually until Terminated.
The main VCL thread has to tell the SCTP thread when to make an
association (along with address and port parameters of where to
connect to), when to send a message (along with parameters for the
text of the message as well as which association to send it to),
and when to disconnect (along with a parameter for which
association to shut down).
When it makes a connection, the SCTP thread needs to tell the VCL
thread the ID for that association (so the VCL thread can use that
ID later to tell the SCTP thread where to send messages). When it
receives a message from the far end, it has to send that message
to the VCL thread for processing (which in our test app is just a
matter of displaying it on the screen, but will eventually involve
more complex handling). And there are various status messages it
passes back to the VCL thread as well for display/logging.
We're using Synchronize for everything passed from the SCTP thread
to the VCL thread, and critical sections around everything passed
from the VCL thread to the SCTP thread. We've got it all working,
except that I'm not positive whether the way we pass things from
the VCL to SCTP threads will still work correctly when the events
fire more rapidly. |
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Tue Feb 20, 2007 10:41 pm Post subject: Re: Passing info between threads |
|
|
"Bob Gonder" <notbg (AT) notmindspring (DOT) invalid> wrote in message
news:h21mt21bg7maqp25d2lqkr9b1iet0eu3g6 (AT) 4ax (DOT) com...
| Quote: | "Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote:
I'm a bit late, so I'll just throw out some comments
pLockSendString->Acquire();
pLockAssocID->Acquire();
You are releasing in the wrong order.
When using multiple locks, always (in all segments or your program)
lock them in the same order, and always release in the exact opposite
order. This reduces the dreaded Deadlock.
|
OK, I'll fix that. Thanks.
| Quote: | pLockSendString->Release();
pLockAssocID->Release();
And then, it looks like you are always using Both locks, never a
single one, which makes one wonder why you need 2? A single lock
between the sender (VCL) and the receiver (thread) should suffice.
|
We used separate locks for each variable we were setting because this Send
function needed to pass both variables, but the Disconnect function just
needed to pass the AssociationID. (There's also a Connect function that
uses a third TCriticalSection to lock both DestinationAddress and
DestinationPort variables.) I just showed the Send in my first post because
I thought including all three confused the issue more than it clarified it.
| Quote: | One way out is a simple sentinal.
VCL:
static volatile bool DataFull = false;
while( DataFull ){ ProcessMessages() }
SetData()
DataFull = true
ThreadSend( DataFull )
ThreadSend( bool & DataFull ):
ProcessData()
DataFull = false
VCL can cycle through, but will get stuck until Thread has finished.
|
I like that this version allows other processing to go on while it's
waiting, but if that ProcessMessages() picks up another Send function, it
could be processed before the earlier one. I'm not sure yet whether we're
going to care about the order of our messages (in most of our applications
we don't), but it would be something to keep in mind in case we end up
copying this into an application where we do care about message order.
(Even if we don't care about the order of sent messages, if that
ProcessMessages picked up a Disconnect function, having Send and then
Disconnect turned into Disconnect and then Send could cause an error.)
| Quote: | (Or would it be OK to pass local
variables to the thread? How would that work?)
You would need to wait for the thread to copy them.
VCL:
SetData();
ThreadSend( LocalData )
WaitForSingleObject( hDataAquiredEvent, 10000 );
Thread:
CopyData();
Signal( hDataAquiredEvent );
ProcessData()
(There is probably some TEvent that makes that easier)
|
This looks like the safer way to do it, so long as the wait doesn't get long
enough to hurt performance. Since this is the first app we're working on
using the SCTP library, I don't know whether it can lock out the SCTP thread
for long intervals or not. (I'm hoping it doesn't anyway.) We'll probably
try this (or perhaps its TEvent equivalent) and then do some performance
testing.
Does this waiting for an event signaling that the thread has finished
reading its input parameters take care of making it thread-safe? (i.e. Does
it allow us to just pass in parameters without needing the critical
section?) |
|
| Back to top |
|
 |
Remy Lebeau (TeamB) Guest
|
Posted: Wed Feb 21, 2007 12:49 am Post subject: Re: Passing info between threads |
|
|
"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in
message news:45d9e83a$1 (AT) newsgroups (DOT) borland.com...
| Quote: | threading didn't seem to fit the topics for any of the boards here.
|
If you read the newsgroup descriptions at
http://info.borland.com/newsgroups/cppbnewsdesc.html, you will see
exactly where to post threading questions.
Gambit |
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Wed Feb 21, 2007 3:53 am Post subject: Re: Passing info between threads |
|
|
"Remy Lebeau (TeamB)" <no.spam (AT) no (DOT) spam.com> wrote in message
news:45db42ad (AT) newsgroups (DOT) borland.com...
Thanks for a link to the descriptions, but it doesn't seem to be very
current. There are quite a few boards not listed there, and it lists some
that don't exist. It says to post threading questions on
borland.public.cppbuilder.winapi, but there is no such board on the server. |
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Wed Feb 21, 2007 3:58 am Post subject: Re: Passing info between threads |
|
|
I wrote in message news:45db6dc5$1 (AT) newsgroups (DOT) borland.com...
| Quote: |
It says to post threading questions on borland.public.cppbuilder.winapi,
but there is no such
board on the server.
|
Never mind. I found it. I guess it's now listed as "nativeapi" rather than
"winapi". (It would be nice if the description page used the current names,
though.) |
|
| Back to top |
|
 |
Remy Lebeau (TeamB) Guest
|
Posted: Wed Feb 21, 2007 5:04 am Post subject: Re: Passing info between threads |
|
|
"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in
message news:45db6dc5$1 (AT) newsgroups (DOT) borland.com...
| Quote: | Thanks for a link to the descriptions, but it doesn't seem to be
very
current.
|
No, it is not current, but it is still mostly relevant.
| Quote: | It says to post threading questions on
borland.public.cppbuilder.winapi,
but there is no such board on the server.
|
It was renamed to ".nativeapi" a long time ago.
Gambit |
|
| Back to top |
|
 |
Bob Gonder Guest
|
Posted: Wed Feb 21, 2007 8:24 am Post subject: Re: Passing info between threads |
|
|
"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote:
| Quote: | (Or would it be OK to pass local
variables to the thread? How would that work?)
You would need to wait for the thread to copy them.
VCL:
SetData();
ThreadSend( LocalData )
WaitForSingleObject( hDataAquiredEvent, 10000 );
Thread:
CopyData();
Signal( hDataAquiredEvent );
ProcessData()
Does this waiting for an event signaling that the thread has finished
reading its input parameters take care of making it thread-safe? (i.e. Does
it allow us to just pass in parameters without needing the critical
section?)
|
Yes. The thread has copied the application's local data into it's own
local data before signaling, so the application data is free to
disappear.
I see that you are also concerned about the Thread falling behing the
application, and getting messages out of order. One way out is to add
a ring buffer.
The application stuffs data into the head of the ring, then tells the
thread that new data is available. (Once again, the data is now in an
escrow buffer, so the application data can change or "go away".)
The thread takes data off the tail of the ring, and sends it.
It is possable to construct a ring buffer so that no locking is
required. Only reason to use locks would be if you have more than one
writter (application) thread, or if you have more than one thread
reading from it.
When the ring fills, the application stalls until the sender can free
up a spot.
VCL:
while( IsRingBufferFull() ) Sleep(0); // wait for an opening
StuffDataIntoRingBuffer();
NextRingBufferHead(); // fullfills RingBufferHasData()
Signal( hSendDataAvailableEvent ); // wake up
Thread:
while( ! Terminated )
{
WaitForSingleObject( hSendDataAvailableEvent, 1000 );
while( RingBufferHasData() )
{
Data = ReadRingBufferData();
NextRingBufferTail(); // unblocks VCL while()
Send( Data );
};
};
To bring this back on-topic....
I don't know if there is an STL ring buffer, but you might want to
investigate using auto-pointers to hold your messages, so the buffer
would be holding pointers to messages instead of the actual messages.
Since message may be of variable length, it might be hard to design a
ring buffer of sufficient size to hold raw messages.
If you find that your ring fills up and stays full, even if you make
it quite large, then you might want to look into boosting the thread
priority (or adding another socket if the application could use it) |
|
| Back to top |
|
 |
Paul at NCF Guest
|
Posted: Wed Feb 21, 2007 9:10 am Post subject: Re: Passing info between threads |
|
|
Looks like you have enough information now from other. Hope it all works
out.
"Paul at NCF" <notplmacca (AT) clara (DOT) co.uk> wrote in message
news:45daac91 (AT) newsgroups (DOT) borland.com...
| Quote: | Hi,
Off to work soon, so just in case this helps, an example help thread: |
|
|
| Back to top |
|
 |
Lesley Anne Guest
|
Posted: Thu Feb 22, 2007 4:00 am Post subject: Re: Passing info between threads |
|
|
"Bob Gonder" <notbg (AT) notmindspring (DOT) invalid> wrote in message
news:vj8nt2llf7deda6ioqggci4rds885qcmne (AT) 4ax (DOT) com...
| Quote: |
I see that you are also concerned about the Thread falling behind
the application, and getting messages out of order. One way out
is to add a ring buffer.
The application stuffs data into the head of the ring, then tells
the thread that new data is available. (Once again, the data is
now in an escrow buffer, so the application data can change or
"go away".) The thread takes data off the tail of the ring, and
sends it.
|
This sounds like the queue approach that Paul was talking about. I've
looked at TThreadList, and think I can use that for a queue of commands I
want the thread to process. Here's how that looks so far:
In the header, I defined a structure to hold commands and their parameters.
It looks like this:
enum TSctpCommand { scmdConnect, scmdShutDown, scmdSend };
typedef struct SctpCmdParamRec {
TSctpCommand Command;
unsigned char MsgBuffer[MAX_BUFFER_LENGTH];
int MsgLen;
unsigned int AssocID;
unsigned char DestAddr[SCTP_MAX_IP_LEN];
unsigned short DestPort;
int SiteIndex;
} TSctpCmdParams;
Then I have:
TThreadList* SctpCommandList;
defined globally in my main unit (and extern in the thread unit), which gets
instantiated with 'new' in my main form's constructor.
Whenever the main form wants to initiate something in the SCTP thread, it
does something like this:
// --------------------------------------------------------------
void __fastcall TForm1::Send(String Message, int Site)
{
if (sctpThread) {
TSctpCmdParams *SctpCmdParams = new TSctpCmdParams;
SctpCmdParams->Command = scmdSend;
SctpCmdParams->AssocID = associationID[Site];
strcpy(SctpCmdParams->MsgBuffer, Message.c_str());
SctpCmdParams->MsgLen = Message.Length();
SctpCommandList->Add(SctpCmdParams);
sctpThread->HandleCommand();
} else
Memo1->Lines->Add("ERROR: sctp Thread does not exist");
}
// --------------------------------------------------------------
(The Connect and Disconnect functions in the main form look almost identical
to this except that they set different members of SctpCmdParams.)
In the SCTP Thread unit, anything coming from the VCL Thread will enter via
this function:
// --------------------------------------------------------------
void TsctpThread::HandleCommand(void)
{
TList* CmdList;
TSctpCmdParams* SctpCmdParams = NULL;
CmdList = SctpCommandList->LockList();
if (CmdList->Count > 0) {
SctpCmdParams = (TSctpCmdParams*)CmdList->First();
CmdList->Remove(SctpCmdParams);
}
SctpCommandList->UnlockList();
if (SctpCmdParams) {
switch (SctpCmdParams->Command) {
case scmdConnect:
Connect(SctpCmdParams->DestAddr,
SctpCmdParams->DestPort,
SctpCmdParams->SiteIndex);
break;
case scmdShutDown:
ShutDown(SctpCmdParams->AssocID);
break;
case scmdSend:
Send(SctpCmdParams->MsgBuffer,
SctpCmdParams->MsgLen,
SctpCmdParams->AssocID);
break;
}
delete SctpCmdParams;
}
// should probably add an 'else' here for an error message
}
// --------------------------------------------------------------
This does have the VCL thread allocating structures which the SCTP thread
eventually deletes, but I think that should work so long as the TThreadList
buffers the two accesses adequately. The VCL thread never looks at the
SctpCmdParams it created again after putting it on the TThreadList, and the
SCTP thread uses and deletes it after getting it off of that list.
There is a bit of overhead in my TSctpCmdParams structure, because space for
all possible parameters gets allocated each time, even though only the
parameters relevant to that particular command get used. I'm not too
worried about that, though, because by far the largest piece of it is the
MsgBuffer, and that's what I'm going to need the most often anyway. (The
Send command is the one that can get called a lot. Connect and ShutDown
commands, where that MsgBuffer is unnecessary, are infrequent events.) |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|