Multi thread, array yes or no?

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Multi thread, array yes or no?

Postby mark_c » Mon Jan 20, 2020 12:37 pm

I was wondering the difference between the two solutions below: aren't they casually equivalent?

Code: Select all
        TMyThread *TMyTh;
        try
        {
           for(int i = 0; i < 100; ++i)
                {
              TMyTh = new TMyThread("adr_" + IntToStr(i+1));
              TMyTh->OnTerminate = &ThreadTerminated;
              TMyTh->Resume();
              ++ThreadsRunning;
                }
        } catch(...){ }
   Caption = "Created";


Code: Select all
        TMyThread *TMyTh[100];
        try
        {
           for(int i = 0; i < 100; ++i)
                {
              TMyTh[i] = new TMyThread("adr_" + IntToStr(i+1));
              TMyTh[i]->OnTerminate = &ThreadTerminated;
              TMyTh[i]->Resume();
              ++ThreadsRunning;
                }
        } catch(...){ }
   Caption = "Created";
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby rlebeau » Mon Jan 20, 2020 2:04 pm

As far as the creation of the threads goes, the 2 code examples are identical. The only difference is in how you are referring to the threads after creating them. In the 1st example, TMyTh is a single pointer, so it can point only at 1 thread at a time, in this case to the last thread that was created. In the 2nd example, TMyTh is an array of 100 pointers, so it can point at up to 100 threads at the same time.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Multi thread, array yes or no?

Postby mark_c » Mon Jan 20, 2020 2:24 pm

therefore, without arrays, I remain with 99 threads out of control which, in any case, are still terminated by the VCL when I close the process, right?

thanks Remy
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby rlebeau » Mon Jan 20, 2020 2:43 pm

mark_c wrote:therefore, without arrays, I remain with 99 threads out of control


Yes, in your example, there would be 99 threads that you have no pointer to, except for the Sender parameter in the OnTerminate event.

mark_c wrote:which, in any case, are still terminated by the VCL when I close the process, right?


Wrong. Threads are NEVER terminated automatically by the VCL, and certainly not during process closure. If there are still threads running, the process will not fully exit, even after your GUI has been closed. You need to terminate your threads explicitly when you are done using them.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Multi thread, array yes or no?

Postby mark_c » Wed Jan 22, 2020 5:00 am

thanks again Remy.
Using this version of the code, which is honestly yours, how do I know which 100 element array thread is terminated?

The purpose of my question is that I would like to instantiate a new thread every time one ends so that we always have 100 threads running simultaneously with new parameters.

Code: Select all
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

#define THREADCOUNT 100

class TMyThread : public TThread
{
private:
   AnsiString myadr, msg;
protected:
   void __fastcall Execute();
public:
   __fastcall TMyThread(const AnsiString &Adr);
   void __fastcall MyMemo1();
};

int ThreadsRunning = 0;

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

__fastcall TMyThread::TMyThread(const AnsiString &Adr)
: TThread(true), myadr(Adr)
{
   FreeOnTerminate = true;
}
//---------------------------------------------------------------------------

void __fastcall TMyThread::MyMemo1()
{
   Form1->Memo1->Lines->Add(msg);
}
//---------------------------------------------------------------------------

void __fastcall TMyThread::Execute()
{
   DWORD dwBytesWritten;
   HANDLE hFile;

   AnsiString fname = "dirout\\" + myadr + ".txt";

   hFile = CreateFile(fname.c_str(),
   GENERIC_WRITE,
   FILE_SHARE_WRITE,
   NULL,
   CREATE_ALWAYS,
   FILE_ATTRIBUTE_NORMAL,
   NULL);

   WriteFile( hFile,
   myadr.c_str(),
   myadr.Length(),
   &dwBytesWritten,
   NULL);

   CloseHandle(hFile);

   msg=myadr;
   Synchronize(&MyMemo1);
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
   TMyThread *TMyTh[THREADCOUNT];

   try
   {
      for(int i = 0; i < THREADCOUNT; i++)
      {
         TMyTh[i] = new TMyThread("adr_" + IntToStr(i+1));
         TMyTh[i]->OnTerminate = &ThreadTerminated;
         TMyTh[i]->Resume();
         ThreadsRunning++;
      }
   } catch(...){ }

   Caption = "Created";
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   ThreadsRunning--;
   if (ThreadsRunning == 0)
   {
      Caption = "Terminated";
   }
}
//---------------------------------------------------------------------------
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby HsiaLin » Wed Jan 22, 2020 3:32 pm

TMyTh[i]->Terminated is a bool, just check it.
HsiaLin
BCBJ Master
BCBJ Master
 
Posts: 320
Joined: Sun Jul 08, 2007 6:29 pm

Re: Multi thread, array yes or no?

Postby mark_c » Wed Jan 22, 2020 3:56 pm

[quote="HsiaLin"]TMyTh[i]->Terminated is a bool, just check it.[/quote

Thanks I didn't know. However, isn't there a method that tells me which thread ended without having to check them all?
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby HsiaLin » Wed Jan 22, 2020 4:49 pm

Not that i know of, i do not know how you`d know a thread was finished without checking it.
HsiaLin
BCBJ Master
BCBJ Master
 
Posts: 320
Joined: Sun Jul 08, 2007 6:29 pm

Re: Multi thread, array yes or no?

Postby rlebeau » Fri Jan 24, 2020 12:22 pm

mark_c wrote:Using this version of the code, which is honestly yours, how do I know which 100 element array thread is terminated?


The Sender parameter of the OnTerminate event handler tells you which thread object is terminating. You can search for the Sender in your array and then remove it.

However, in the example you have given, the array is local to Button1Click(), and as such it goes out of scope as soon as you finish starting up your 100 threads. So really, there is no array to access when threads are terminating. If you want that, then you need to move the array into a higher scope, either as a member of the TForm1 class, or into global scope (next to the ThreadsRunning variable).
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Multi thread, array yes or no?

Postby mark_c » Thu Jan 30, 2020 12:49 am

thanks Remy.
After some tests I found this method to trace the handle of the thread that ends.

Code: Select all
void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   ThreadsRunning--;

   if (ThreadsRunning == 0)
           Caption = "Terminated";

        TMyThread *MyTh = dynamic_cast<TMyThread*> (Sender);
        Memo2->Lines->Add( "stop: " + String(MyTh->Handle) );
}
//---------------------------------------------------------------------------



if I understand correctly, this cannot be done because the OnRead event still continues to be diminished, right?

Code: Select all
void __fastcall TMyThread::Execute()
{
   TClientSocket *MySock = new TClientSocket(NULL);

   MySock->Address = myadr;
   MySock->Port = 80;
   MySock->ClientType = ctNonBlocking; ?????????????????????
   MySock->Open();
}
//---------------------------------------------------------------------------
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby rlebeau » Fri Jan 31, 2020 12:56 pm

mark_c wrote:After some tests I found this method to trace the handle of the thread that ends.


Use static_cast instead of dynamic_cast, since you know the exact class type ahead of time. And you don't need the thread's Handle, you need the thread's TMyThread* pointer, since that is what you are storing in your array.

mark_c wrote:if I understand correctly, this cannot be done because the OnRead event still continues to be diminished, right?


Wrong. If you use the TClientSocket in ctNonBlocking mode, it creates an internal window to receive socket notifications. That window will be tied to the thread context that creates it. So only that thread can receive messages for that window, such as the FD_READ notification that triggers the TClientSocket::OnRead event. Once the thread has terminated, the window will not receive any further messages, and will be destroyed when the TClientSocket is destroyed.

Also, your Execute() example has no message loop, so you won't get any TClientSocket::OnRead events in ctNonBlocking mode at all. You need to code a message loop, eg:

Code: Select all
#include <memory>

void __fastcall TMyThread::Execute()
{
   std::auto_ptr<TClientSocket> MySock(new TClientSocket(NULL));
   // or std::unique_ptr in C++11 and later...

   MySock->Address = myadr;
   MySock->Port = 80;
   MySock->ClientType = ctNonBlocking;

   MySock->Open();

   MSG msg;
   while ((!Terminated) && (GetMessage(&msg, NULL, 0, 0) > 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }     

   MySock->Close();
}


This is why you really should not be using ctNonBlocking mode in a worker thread to begin with, if you can help it (sometimes it is actually necessary, though, but this is not one of those times). Use ctBlocking mode instead.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Multi thread, array yes or no?

Postby mark_c » Wed Feb 05, 2020 11:14 am

I still take advantage of your skills and ask you: we said that if I instantiate n threads without arrays, I lose all references and, if I want to do something about one of them, I can no longer do anything.
By setting the FreeOnTerminate = true property; it is true that a thread is terminated at its end, (I don't know if all the memory is returned simply by setting that property) but if for some reason an infinite wait is generated in the thread, a loop out of control, I can no longer terminate the thread .
In the case of such an instance, could this problem exist?
I honestly don't see any.
Why am I asking you this?
Because I would like to try to instantiate 100 threads without arrays and every time one ends, instantiate a new one in its place. In this way, I wouldn't have the problem of looking every time in the array for what number of threads has been terminated and replacing it with a new one, the index of the array, what do I need if everything is executed and terminated almost automatically?

the idea in untested code

Code: Select all
int i=0;
int test=0;

void __fastcall TMyThread::Execute()
{
   TMyThread *TMyTh;
   try
   {
      for(i = 0; i < 100; ++i)
      {
         TMyTh = new TMyThread("adr_" + IntToStr(i+1));
         TMyTh->OnTerminate = &ThreadTerminated;
         TMyTh->Resume();
         }
   } catch(...){ }
   
   Caption = "Start 100 threads created";
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   if(test <= 100)
   {
      TMyTh = new TMyThread("adr_" + IntToStr(i++));
      test++;
   }
}
//---------------------------------------------------------------------------
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby rlebeau » Wed Feb 05, 2020 6:05 pm

mark_c wrote:we said that if I instantiate n threads without arrays, I lose all references and, if I want to do something about one of them, I can no longer do anything.


Not true. Go re-read what I actually said about that.

mark_c wrote:By setting the FreeOnTerminate = true property; it is true that a thread is terminated at its end


A thread is ALWAYS terminated when it ends, regardless of the TThread::FreeOnTerminate property (if you are using TThread to begin with, vs std::thread). The TThread::FreeOnTerminate property merely controls whether or not the owning TThread object is freed from memory after the thread terminates. If true, the object is freed automatically after its Execute() method exits and its OnTerminate event has been fired. If false, the object is not freed until you explicitly free it yourself.

mark_c wrote:if for some reason an infinite wait is generated in the thread, a loop out of control, I can no longer terminate the thread.


Correct, which is why it is your responsibility to code tour threads properly to avoid such blockages.

mark_c wrote:I would like to try to instantiate 100 threads without arrays and every time one ends, instantiate a new one in its place.


If you use TThread, you can use its OnTerminate event for that purpose. The Sender parameter tells you which TThread object has terminated. I gave you an example of that earlier.

If you use std::thread, there is no such event, so you would have to start a new thread inside your thread function before it exits.

But, either way, this is not really a good design to start a new thread when a thread ends. You should just re-use the existing thread instead. For instance, create a thread-safe work queue, then create some threads to process that queue. Ideally, you should not have more threads than you have CPU cores, maybe a few more, but not much more. Put some work items in the queue, and have each thread loop, pulling an item from the queue on each loop iteration, until the queue is empty.

mark_c wrote:In this way, I wouldn't have the problem of looking every time in the array for what number of threads has been terminated and replacing it with a new one, the index of the array, what do I need if everything is executed and terminated almost automatically?


Ultimately, you should keep track of your threads, and an array is a good way to do that. If nothing else, when you want to exit your program, you need to terminate any threads that are still running, which means knowing which threads exist so you can signal them, which means keeping track of them.

Unless you use a global signal, and keep track of a counter of running threads, and then you can simply signal the global and wait for the counter to hit 0, at least then you don't need to track the individual threads.

mark_c wrote:the idea in untested code


First, that code does not belong in the thread's Execute() at all. That is the code I gave you that belongs in the main UI thread instead to start the initial threads.

Second, you are not managing your global variables correctly, either.

Try this instead:

Code: Select all
int ThreadsRunning = 0;
bool Exiting = false;

class TMyThread : public TThread
{
protected:
   void __fastcall Execute();
public:
   String name;
   __fastcall TMyThread(String AName);
};

__fastcall TMyThread::TMyThread(String AName)
   : TThread(true), name(AName)
{
   FreeOnTerminate = true;
}

void __fastcall TMyThread::Execute()
{
   // do some work...
   // check Exiting as needed...
}

__fastcall TForm1::TForm1(TComponent *Owner)
{
   try
   {
      for(int i = 0; i < 100; ++i)
      {
         TMyThread *TMyTh = new TMyThread("adr_" + IntToStr(i+1));
         TMyTh->OnTerminate = &ThreadTerminated;
         TMyTh->Resume();
         ++ThreadsRunning;
      }
   } catch(...){ }
   
   Caption = "Start " + IntToStr(ThreadsRunning) + " threads created";
}

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
   Exiting = true;
   while (ThreadsRunning > 0)
      CheckSynchronize();
}

void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   --ThreadsRunning;
   if ((ThreadsRunning < 100) && (!Exiting))
   {
      TMyThread *TMyTh = static_cast<TMyThread*>(Sender);
      TMyTh = new TMyThread(TMyTh->name);
      TMyTh->OnTerminate = &ThreadTerminated;
      TMyTh->Resume();
      ++ThreadsRunning;
   }
}
//---------------------------------------------------------------------------
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Multi thread, array yes or no?

Postby mark_c » Thu Feb 06, 2020 4:40 am

thanks Remy.
If I have understood your code well, making these changes, as you also suggested to me, I can always keep in memory, for example, the same 5 threads and when one of these ends pass a new topic to this. I don't know if the changes I made to your code are correct to ultimately get 5 threads in a loop so that when one ends, a new parameter is sent to it.

sorry Remy, ignore this message if you want. I just found out that a thread that has terminated can no longer be restarted.

Code: Select all

class TMyThread : public TThread
{
private:

protected:
   void __fastcall Execute();
public:
   AnsiString myadr;
        AnsiString msg;
   __fastcall TMyThread(const String &Adr);
        void __fastcall MyMemo1();
};

AnsiString myip(__int64 ip);

int ThreadsRunning = 0;
bool Exiting = false;
int i;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
__fastcall TMyThread::TMyThread(const AnsiString &Adr)
: TThread(true), myadr(Adr)
{
        FreeOnTerminate = false;
}
//---------------------------------------------------------------------------
void __fastcall TMyThread::MyMemo1()
{
        Form1->Memo1->Lines->Add(msg);
}
//---------------------------------------------------------------------------

void __fastcall TMyThread::Execute()
{

        for(int i=0; i<5;i++)
        {
                msg = myadr + IntToStr(i);
                Synchronize(&MyMemo1);
                Sleep(500);
        }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   try
   {
      for(i = 0; i < 5; ++i)
      {
         TMyThread *TMyTh = new TMyThread("adr_" + IntToStr(i+1));
         TMyTh->OnTerminate = &ThreadTerminated;
         TMyTh->Resume();
         ++ThreadsRunning;
      }
   } catch(...){ }

   Caption = "Start " + IntToStr(ThreadsRunning) + " threads created";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   if ( hreadsRunning < 5)
   {
      TMyThread *TMyTh = static_cast<TMyThread*>(Sender);
      MyTh->myadr="adr_" + IntToStr(i++);
      TMyTh->OnTerminate = &ThreadTerminated;
      TMyTh->Resume();
      ++ThreadsRunning;
      }
}
mark_c
BCBJ Master
BCBJ Master
 
Posts: 200
Joined: Thu Jun 21, 2012 1:13 am

Re: Multi thread, array yes or no?

Postby rlebeau » Thu Feb 06, 2020 3:05 pm

mark_c wrote:If I have understood your code well, making these changes, as you also suggested to me, I can always keep in memory, for example, the same 5 threads and when one of these ends pass a new topic to this.


Not with the code you have shown, no. Your button creates 5 new threads each time it is clicked, and each thread terminates after 5 loop iterations. Once a thread terminates, it can't be reused. If you truly want the threads to be reusable, they need to loop indefinitely until you signal them to terminate.

mark_c wrote:I don't know if the changes I made to your code are correct to ultimately get 5 threads in a loop so that when one ends, a new parameter is sent to it.


The changes are not correct for that.

mark_c wrote:sorry Remy, ignore this message if you want. I just found out that a thread that has terminated can no longer be restarted.


Correct, which is why I told you that you need to make your threads loop until signaled to stop, if you want them to be reusable.

Try something more like this instead:

Code: Select all
class TMyThread : public TThread
{
private:
   AnsiString work;
        AnsiString msg;
        void __fastcall MyMemo1();
protected:
   void __fastcall Execute();
public:
   __fastcall TMyThread();
};

int ThreadsRunning = 0;
bool StopWorking = false;
TEvent *HasWorkEvent;
TStringList *WorkList;
TCriticalSection *WorkLock;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
   : TForm(Owner)
{
   HasWorkEvent = new TEvent(NULL, true, false, "");
   WorkList = new TStringList;
   WorkLock = new TCriticalSection;

   try
   {
      for (int i = 0; i < 5; ++i)
      {
         TMyThread *TMyTh = new TMyThread();
         TMyTh->OnTerminate = &ThreadTerminated;
         TMyTh->Resume();
         ++ThreadsRunning;
      }
   }
   catch (...) {}

   Caption = "Start " + IntToStr(ThreadsRunning) + " threads created";
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
   if (ThreadRunning > 0)
   {
      StopWorking = true;

      WorkLock->Enter();
      HasWorkEvent->SetEvent();
      WorkLock->Leave();

      while (ThreadRunning > 0)
      {
         Sleep(100);
         CheckSynchronize();
      }
   }

   delete HasWorkEvent;
   delete WorkList;
   delete WorkLock;
}
//---------------------------------------------------------------------------
__fastcall TMyThread::TMyThread()
   : TThread(true)
{
   FreeOnTerminate = true;
}
//---------------------------------------------------------------------------
void __fastcall TMyThread::MyMemo1()
{
   Form1->Memo1->Lines->Add(msg);
}
//---------------------------------------------------------------------------
void __fastcall TMyThread::Execute()
{
   while (!StopWorking)
   {
      HasWorkEvent->WaitFor(INFINITE);
      if (StopWorking) return;

      WorkLock->Enter();
      if (WorkList->Count < 1)
      {
         WorkLock->Leave();
         continue;
      }

      work = WorkList->Strings[0];
      WorkList->Delete(0);

      if (WorkList->Count == 0) {
         HasWorkEvent->ResetEvent();
      }

      WorkLock->Leave();

      msg = "[" + String(ThreadID) + "] " + work;
      Synchronize(&MyMemo1);
   }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   static int adr = 1;

   WorkLock->Enter();

   for (int i = 0; i < 5; ++i) {
      WorkList->Add("adr_" + IntToStr(adr++));
   }
   HasWorkEvent->SetEvent();

   WorkLock->Leave();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ThreadTerminated(TObject *Sender)
{
   --ThreadsRunning;

   if ( (ThreadsRunning < 5) && (!StopWorking) )
   {
      TMyThread *TMyTh = new TMyThread();
      TMyTh->OnTerminate = &ThreadTerminated;
      TMyTh->Resume();
      ++ThreadsRunning;
   }
}
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1648
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Next

Return to Technical

Who is online

Users browsing this forum: No registered users and 9 guests

cron