Board index » cppbuilder » Some thread questions

Some thread questions


2008-06-04 08:39:13 PM
cppbuilder21
Hi,
I've some basic thread-related questions:
1) Using TThread indicates to use Synchronize() function while accessing VCL
properties and methods. I've
tried to produce an error by not using Synchronize(), but I couldn't see an
AV or an exception. Using
Synchronize() involves using some global variables instead of general
function parameters, and this is really
annoying.What about CreateThread function? If I use CreateThread() instead
of TThread is it ok to access VCL
properties and methods directly? If not, because CreateThread() cannot call
a Synchronize(), does it mean that
CreateThread() is almost useless?
2)Does "VCL properties and methods" include user defined Form-member
functions? What about IntToStr()?
3)What is the difference between the codes below?
(Common Code)
CThread1 *thread1, *thread2;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
InitializeCriticalSection(&cs);
::Sleep(500);
threadId = 0;
thread1 = new CThread1(false);
::Sleep(500);
threadId = 1;
thread2 = new CThread1(false);
}
------------------------------------
__fastcall CThread1::CThread1(bool CreateSuspended)
: TThread(CreateSuspended)
{
ID = Form1->threadId;
}
(Code-1)
void __fastcall CThread1::SyncFunc()
{
Form1->Memo1->Lines->Add("I'm thread:" + IntToStr(ID));
}
//---------------------------------------------------------------------------
void __fastcall CThread1::Execute()
{
static int i = 0;
//---- Place thread code here ----
while(i < 100)
{
EnterCriticalSection(&Form1->cs);
Synchronize(SyncFunc);
i++;
LeaveCriticalSection(&Form1->cs);
}
}
(Code-2)
void __fastcall CThread1::SyncFunc()
{
EnterCriticalSection(&Form1->cs);
Form1->Memo1->Lines->Add("I'm thread:" + IntToStr(ID));
LeaveCriticalSection(&Form1->cs);
}
//---------------------------------------------------------------------------
void __fastcall CThread1::Execute()
{
static int i = 0;
//---- Place thread code here ----
while(i < 100)
{
Synchronize(SyncFunc);
i++;
}
}
Code-1 runs as I expect(0,1,0,1,0,1,0...), but what is the explaination of
Code-2? Sometimes two sequential 0s or 1s
are displayed.
4) Assume two threads share the same thread function. Local variables are
unique to each thread. What if the threads
call another, same function. Are the local variables int the called-function
unique to each thread again?
Thanks...
 
 

Re:Some thread questions

CreateThread is not a good function to use in a compiler generated program.
Use _beginthreadNT instead. The help on _beginthreadNT has a discussion of
the reasons for this.
Quote
...I've tried to produce an error by not using Synchronize(), but I
couldn't see an AV or an exception...
Not all errors create exceptions of faults. Many if not most thread-related
errors relate to variables being read or written while they already are in
the process of being altered. If that creates a fault or exception is a
function both of the response of your code to incorrect values and of the
manner in which the variable is altered or accessed.
Ideally all VCL GUI related activity will be isolated to the main thread.
Items such as IntToStr are merely Delphi-style mimics of what one normally
does when writing in C++ and are not likely to have thread issues except for
if the calling argument or destination of the assignment of the result have
such issues.
. Ed
Quote
UO wrote in message
news: XXXX@XXXXX.COM ...

I've some basic thread-related questions:

1) Using TThread indicates to use Synchronize() function while accessing
VCL properties and methods. I've tried to produce an error by not using
Synchronize(), but I couldn't see an AV or an exception. Using
Synchronize() involves using some global variables instead of general
function parameters, and this is really annoying.

What about CreateThread function? If I use CreateThread() instead of
TThread is it ok to access VCL properties and methods directly? If not,
because CreateThread() cannot call a Synchronize(), does it mean that
CreateThread() is almost useless?

2)Does "VCL properties and methods" include user defined Form-member
functions? What about IntToStr()?

3)What is the difference between the codes below?

(Common Code)

CThread1 *thread1, *thread2;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
InitializeCriticalSection(&cs);
::Sleep(500);
threadId = 0;
thread1 = new CThread1(false);
::Sleep(500);
threadId = 1;
thread2 = new CThread1(false);
}
------------------------------------

__fastcall CThread1::CThread1(bool CreateSuspended)
: TThread(CreateSuspended)
{
ID = Form1->threadId;
}

(Code-1)
void __fastcall CThread1::SyncFunc()
{
Form1->Memo1->Lines->Add("I'm thread:" + IntToStr(ID));
}

//---------------------------------------------------------------------------
void __fastcall CThread1::Execute()
{
static int i = 0;
//---- Place thread code here ----
while(i < 100)
{
EnterCriticalSection(&Form1->cs);
Synchronize(SyncFunc);
i++;
LeaveCriticalSection(&Form1->cs);
}
}

(Code-2)
void __fastcall CThread1::SyncFunc()
{
EnterCriticalSection(&Form1->cs);
Form1->Memo1->Lines->Add("I'm thread:" + IntToStr(ID));
LeaveCriticalSection(&Form1->cs);
}

//---------------------------------------------------------------------------
void __fastcall CThread1::Execute()
{
static int i = 0;
//---- Place thread code here ----
while(i < 100)
{
Synchronize(SyncFunc);
i++;
}
}

Code-1 runs as I expect(0,1,0,1,0,1,0...), but what is the explaination of
Code-2? Sometimes two sequential 0s or 1s
are displayed.

4) Assume two threads share the same thread function. Local variables are
unique to each thread. What if the threads
call another, same function. Are the local variables int the
called-function unique to each thread again?

Thanks...



 

Re:Some thread questions

"UO" <uo@uott>wrote in message news: XXXX@XXXXX.COM ...
Quote
Using TThread indicates to use Synchronize() function while
accessing VCL properties and methods. I've tried to produce
an error by not using Synchronize(), but I couldn't see an AV
or an exception.
There is no guarantee that an error or AV would occur. But accessing the
main thread's VCL objects is still not thread-safe without using
Synchronize() or other inter-thread synchronization of your choosing.
Otherwise, undefined/unexpected behavior can still occur without you knowing
it right away.
Quote
Using Synchronize() involves using some global variables
instead of general function parameters and this is really annoying.
They don't have to be global. You can make them be members of your TThread
class instead. Or use a wrapper class to hold the variables.
Quote
What about CreateThread function? If I use CreateThread() instead
of TThread is it ok to access VCL properties and methods directly?
No. The same problems still exist. They have nothing to do with how the
thread is created. The act of having a thread in general creates the
problem. Besides, TThread uses CreateThread() internally anyway.
Quote
Does "VCL properties and methods" include user defined
Form-member functions?
Yes, if they access any properties/components of any
form/datamodule/class/whatever that runs in the context of the main thread.
Quote
What about IntToStr()?
That is safe to call without Synchronize(), provided that the String you are
passing to it is being accessed safely.
Quote
What is the difference between the codes below?
<snip>
Code-1 runs as I expect(0,1,0,1,0,1,0...)
Your use of a critical section is forcing a task switch between the two
threads after each Synchronize(), so they alternate access to the TMemo.
Quote
what is the explaination of Code-2? Sometimes two sequential 0s
or 1s are displayed.
That code is not forcing alternating access to the TMemo, so one thread is
able to call Synhronize() multiple times before the OS task-switches to the
other thread on its own accord.
Quote
Assume two threads share the same thread function. Local variables
are unique to each thread.
Yes.
Quote
What if the threads call another, same function. Are the local variables
int the
called-function unique to each thread again?
Yes.
Gambit
 

{smallsort}

Re:Some thread questions

"Remy Lebeau (TeamB)" < XXXX@XXXXX.COM >, haber iletisinde žunlar?
yazd?4846ea94$ XXXX@XXXXX.COM ...
Quote
That code is not forcing alternating access to the TMemo, so one thread is
able to call Synhronize() multiple times before the OS task-switches to
the other thread on its own accord.
Ok, I get it. But now consider the following code:
void __fastcall CThread1::SynchFunc()
{
Form1->Memo1->Lines->Add("I'm thread:" + IntToStr(ID));
}
//---------------------------------------------------------------------------
void __fastcall CThread1::Execute()
{
static int i = 0;
//---- Place thread code here ----
FreeOnTerminate = true;
while(!Terminated)
{
EnterCriticalSection(&Form1->cs);
i++;
if(i>1000)
break;
Synchronize(SynchFunc);
LeaveCriticalSection(&Form1->cs);
//::Sleep(20);
for(int k = 0; k < 5*1000*1000; k++)
{
}
}
}
If I add a code such as Sleep() or a for loop, then I see again a few
sequential 0s(0,0,..) or 1s(1,1,...).
It seems like a thread enters the critical section more than once but I
don't understand exactly why.
What I expect is something like this:
- Thread-0 enters critical section.
- Thread-1 waits to enter critical section.
- Thread-0 displays "I'm thread:0"
- Thread-0 leaves critical section:
- Thread-1 enters critical section:
- Thread-0 does some lengthy job, which causes Thread-0 to be blocked and
Thread-1 is executed.
- Thread-1 displays "I'm thread:1"
- Thread-1 leaves critical section.
- Thread-1 does some lengthy job, which causes Thread-1 to be blocked and
Thread-0 is executed.
- Thread-0 continues with while() loop.
- Thread-0 enters critical section.
- ...
But, appearently this is not what always happens. So, what do I
misunderstand?
Thanks...
 

Re:Some thread questions

"UO" <uo@uott>wrote in message news: XXXX@XXXXX.COM ...
Quote
EnterCriticalSection(&Form1->cs);
i++;
if(i>1000)
break;
That is a deadlock waiting to happen. You are not calling
LeaveCriticalSection() when 'i' reaches 1001.
Quote
If I add a code such as Sleep() or a for loop, then I see again
a few sequential 0s(0,0,..) or 1s(1,1,...).
Again, you are at the mercy of the OS's Task Scheduler. You are not inside
the critical section while sleeping/looping, so you have a race condition
regarding which thread will be active and can enter the critical section
first the next time around.
Quote
It seems like a thread enters the critical section more than
once but I don't understand exactly why.
It is possible that Thread-0 wakes up and enters the critical section again
before Thread-1 can do so first.
Quote
What I expect is something like this:
Your expectation is not taking task scheduling into account again.
Quote
- Thread-0 leaves critical section:
- Thread-1 enters critical section:
- Thread-0 does some lengthy job, which causes Thread-0
to be blocked and Thread-1 is executed.
Not necessarily. If Thread-0 does not perform a yielding operation during
the job, such as a Sleep(), then Thread-1 will still be waiting its turn at
the CPU, allowing Thread-0 to finish its job and re-enter the critical
section again because the OS did not switch to Thread-1 yet.
Quote
But, appearently this is not what always happens. So, what
do I misunderstand?
Task scheduling by the OS.
Gambit
 

Re:Some thread questions

"Remy Lebeau (TeamB)" < XXXX@XXXXX.COM >, haber iletisinde žunlar?
yazd?4846ea94$ XXXX@XXXXX.COM ...
Quote
>Does "VCL properties and methods" include user defined
>Form-member functions?

Yes, if they access any properties/components of any
form/datamodule/class/whatever that runs in the context of the main
thread.
Sorry for the late reply. I've been studying event objects and some other
things, in order to use them instead of
TThread::Synchronize().
Secondary threads add some debug strings to Form1 to inform the user/me for
various states of the application.
For example:
Form1->Memo1->Lines->Add(hostName + " joined the session");
Instead of putting the code into Synchronize() I've decided to maintain a
few TStringList objects and a Timer.
All of them are member of the main thread (Form1). The timer checks the
TStringList objects periodically
and adds the strings to memos accordingly.So,
(Form1)
TStringList *memo1List = new TStringList;
(Thread)
Form1->memo1List->Add(hostName + " joined the session"); //X
(Form1->Timer)
for(i = 0; i < memo1List->Count; i++)
Memo1->Lines->Add(memo1List->Strings[i]);
memo1List->Clear();
I'm not sure about what I understand. The comment about TThread says:
// Important: Methods and properties of objects in VCL can only be
// used in a method called using Synchronize.
"TStringList *memo1List", here, is a user-defined VCL-object and is a
member of the Form1.
But is not related to VCL-GUI.
So, the question is, should the line marked with //X be put into
TThread::Synchronize() or not?
Thanks...
 

Re:Some thread questions

"UO" <uo@uott>wrote in message news: XXXX@XXXXX.COM ...
Quote
Instead of putting the code into Synchronize() I've decided to
maintain a few TStringList objects and a Timer.
All of them are member of the main thread (Form1). The timer
checks the TStringList objects periodically and adds the strings
to memos accordingly.
Such an approach is not thread-safe unless you provide a locking mechanism,
such as a critical section, around the TStringList objects so that only one
thread can access them at a time.
Quote
(Form1->Timer)
for(i = 0; i < memo1List->Count; i++)
Memo1->Lines->Add(memo1List->Strings[i]);
memo1List->Clear();
You don't need to loop manually:
Memo1->Lines->AddStrings(memo1List);
memo1List->Clear();
Quote
"TStringList *memo1List", here, is a user-defined VCL-object
and is a member of the Form1. But is not related to VCL-GUI.
No, but it is still being used across multiple threads, and thus needs to be
protected from concurrent access.
Quote
So, the question is, should the line marked with //X be put
into TThread::Synchronize() or not?
Both pieces of code - the thread code that adds strings to the list, and the
timer code that reads the list - need protection. You can use Synchronize()
for that, ie:
--- Thread ---
{
//...
FMsg = hostName + " joined the session";
Synchronize(addToList);
//...
}
void __fastcall TMyThread::addToList()
{
Form1->memo1List->Add(FMsg);
}
--- Timer ---
{
Memo1->Lines->AddStrings(memo1List);
memo1List->Clear();
}
Or, you can use a critical section or other synchronization object of your
choosing, ie:
--- Form1 ---
{
memo1List = new TStringList;
memo1Lock = new TCriticalSection;
}
void __fastcall TForm1::addToList(const AnsiString &Msg)
{
memo1Lock->Enter();
try {
memo1List->Add(Msg);
}
__finally {
memo1Lock->Leave();
}
}
--- Thread ---
{
//...
Form1->addToList(hostName + " joined the session");
//...
}
--- Timer ---
{
memo1Lock->Enter();
try
{
Memo1->Lines->AddStrings(memo1List);
memo1List->Clear();
}
__finally {
memo1Lock->Leave();
}
}
Gambit
 

Re:Some thread questions

"Remy Lebeau (TeamB)" < XXXX@XXXXX.COM >wrote in message
Quote

Such an approach is not thread-safe unless you provide a locking
mechanism, such as a critical section, around the TStringList objects so
that only one thread can access them at a time.
Yes, I was aware that a locking mechanism should be used and knew how to do
(if there are ways other than Synchronize()), but I didn't want to bother
you with further code.
Quote
Or, you can use a critical section or other synchronization object of your
choosing, ie:
Yes, that answers my question. Using Synchronize() was not neccessary in
that case.
------
To make everything clear, assume I have a TMemo and a TButton on a TForm.
TMemo has 1, TButton has 2 event handlers:
--- Form1 ---
void __fastcall TForm1::Memo1Click(TObject *Sender)
{
Memo1->Lines->Add(Button1->Caption);
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Button1->Caption = "Updated in the main thread";
ShowMessage("In Button1Click");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1MouseMove(TObject *Sender,
TShiftState Shift, int X, int Y)
{
//
}
OnMouseMove is made empty deliberately.
---Thread----
void __fastcall CThread1::UpdateCaption()
{
Form1->Button1->Caption = "Updated in a thread";
}
void __fastcall CThread1::Execute()
{
::Sleep(2000);
Synchronize(UpdateCaption);
//---- Place thread code here ----
}
What is the "non-TThread::Synchronize()" equivalent of the given code?
Thanks again...
 

Re:Some thread questions

"UO" <uo@uott>wrote in message news: XXXX@XXXXX.COM ...
Quote
What is the "non-TThread::Synchronize()" equivalent of
the given code?
Your main thread code is directly accessing the GUI components, so you can't
use a critical section or other sync object to safely access the same
components from the worker thread. All access to the GUI components must be
performed in the context of the main thread only, which is exactly what
Synchronize() does for you. The only alternative that would provide the
same safety is to have the worker thread send/post a custom message to the
main thread, which then performs the update on the worker thread's behalf.
And since it is not 100% thread-safe to use the TWinControl::Handle property
from a worker thread, I would suggest either sending/posting to the
TApplication::Handle instead, or setup a private HWND for your use, ie:
--- Form1 ---
#define WM_UPDATE_BTN_CAPTION (WM_APP + 100)
class TForm1 : public TForm
{
private:
bool __fastcall AppHook(TMessage &Message);
public:
__fastcall TForm1(TComponent *Owner);
__fastcall ~TForm1();
};
__fastcall TForm1::TForm1(TComponent *Owner)
: TForm(Owner)
{
Application->HookMainWindow(&AppHook);
}
__fastcall TForm1::~TForm1()
{
Application->UnhookMainWindow(&AppHook);
}
bool __fastcall TForm1::AppHook(TMessage &Message)
{
if( Message.Msg == WM_UPDATE_BTN_CAPTION )
{
Button1->Caption = (char*) Message.LParam;
// or:
//
// AnsiString *pValue = (AnsiString*) Message.LParam;
// Button1->Caption = *pValue;
// delete pValue;
return true;
}
else
return false;
}
---Thread----
void __fastcall CThread1::UpdateCaption()
{
SendMessage(Application->Handle, WM_UPDATE_BTN_CAPTION, 0, (LPARAM)
"Updated in a thread");
// or:
//
// AnsiString *pValue = new AnsiString("Updated in a thread");
// if( !PostMessage(Application->Handle, WM_UPDATE_BTN_CAPTION, 0,
(LPARAM) pValue) )
// delete pValue;
}
void __fastcall CThread1::Execute()
{
::Sleep(2000);
UpdateCaption();
//...
}
Or:
#define WM_UPDATE_BTN_CAPTION (WM_APP + 100)
class TForm1 : public TForm
{
private:
void __fastcall MyWndProc(TMessage &Message);
public:
HWND MyWnd;
__fastcall TForm1(TComponent *Owner);
__fastcall ~TForm1();
};
__fastcall TForm1::TForm1(TComponent *Owner)
: TForm(Owner)
{
MyWnd = AllocateHWnd(&MyWndProc);
}
__fastcall TForm1::~TForm1()
{
DeallocateHWnd(MyWnd);
}
void __fastcall TForm1::MyWndProc(TMessage &Message)
{
if( Message.Msg == WM_UPDATE_BTN_CAPTION )
{
Button1->Caption = (char*) Message.LParam;
// or:
//
// AnsiString *pValue = (AnsiString*) Message.LParam;
// Button1->Caption = *pValue;
// delete pValue;
}
else
Message.Result = DefWindowProc(MyWnd, Message.Msg,
Message.WParam, Message.LParam);
}
---Thread----
void __fastcall CThread1::UpdateCaption()
{
SendMessage(Form1->MyWnd, WM_UPDATE_BTN_CAPTION, 0, (LPARAM)
"Updated in a thread");
// or:
//
// AnsiString *pValue = new AnsiString("Updated in a thread");
// if( !PostMessage(Form1->MyWnd, WM_UPDATE_BTN_CAPTION, 0, (LPARAM)
pValue) )
// delete pValue;
}
void __fastcall CThread1::Execute()
{
::Sleep(2000);
UpdateCaption();
//...
}
Gambit