0

(メインスレッドで) 共有関数を利用し、3 つのスレッドから使​​用しようとしています。この関数は、ディスクへの書き込みなどの潜在的に時間のかかる操作を実行し、起こりうる問題を回避するためにロックします。私はインディIdThreadComponentTCriticalSection. これがどのように見えるかです:

//---------------------------------------------------------------------------
// In header file
//---------------------------------------------------------------------------
boost::scoped_ptr<TCriticalSection> csShared;

//---------------------------------------------------------------------------
// Main file
//---------------------------------------------------------------------------

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
csShared.reset(new TCriticalSection);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
try
    {
    csShared->Enter();           // As suggested by Remy this is placed incorrectly and needs to be moved outside of try block
    //Memo1->Lines->Add(TextData); // [EDIT] calling this within thread is wrong
    Sleep(2000);
    }
__finally
    {
    csShared->Leave();
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 1 calling");
IdThreadComponent1->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent2Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 2 calling");
IdThreadComponent2->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdThreadComponent3Run(TIdThreadComponent *Sender)
{
SharedFunction("Thread 3 calling");
IdThreadComponent3->Stop();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
IdThreadComponent1->Start();
IdThreadComponent2->Start();
IdThreadComponent3->Start();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
// Note - these 3 Stop() calls are used if threads are set to run infinitely
// But in this example it is not needed as they stop themselves
//IdThreadComponent1->Stop();
//IdThreadComponent2->Stop();
//IdThreadComponent3->Stop();

// Now wait for lock to be released [WRONG - COMMENTED IN EDIT]
//while (!csShared->TryEnter())
//  {
//  Sleep(500);
//  }
//csShared->Leave();

// [EDIT v1] easier and faster way to wait than above
//csShared->Enter();
//csShared->Leave();

// [EDIT v2] block exit until all threads are done
while (IdThreadComponent1->Active || IdThreadComponent2->Active || IdThreadComponent3->Active)
{
Sleep(200); // make wait loop less CPU intensive
};

CanClose = true;
}
//---------------------------------------------------------------------------

問題点:

- ウィンドウをすばやく閉じた場合 (関数を実行するスレッドは 1 つだけで、プログラムから離れることはありません。永遠に待機し、デバッガーでは最初のスレッドのみが終了し、他の 2 つのスレッドは終了しません)。スレッドが完了したかどうかを確認するために OnCloseQuery イベントを使用しています。私が間違っていることは何ですか?

[編集] Memo1->Lines->Add(TextData);David Heffernanのコメントで提案されているように削除した後、問題のこの部分が解決され、以下が残るように適切に終了します:

  • 上記のように共有関数内で呼び出しcsShared->Enter();ても問題ありませんか、それとも次のように各スレッドの外部で呼び出す必要がありますか?

    void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
    {
    csShared->Enter();
    SharedFunction("Thread 1 calling");
    csShared->Leave();
    IdThreadComponent1->Stop();
    }
    
  • これは上記のバージョン (csShared->Enter();関数内で呼び出す) よりも優れていますか? それとも同じ?どちらのバージョンも問題なく動作しているようですが、最初のバージョンの方がクリーンであるため、違いがあるのではないかと思います。

これはディスク書き込み用であり、VCL の更新用ではないSynchronizeため、上記の SharedFunction は単なる例です。

4

2 に答える 2

3

呼び出しを共有関数の内部にEnter()置くことは問題なく、むしろ望ましいことです。Leave()ただし、使用する場合は、失敗した場合に備えて の外側try/__finallyに配置する必要があります。成功しなかったことはしたくありません。たとえば、次のようになります。Enter()tryLeave()Enter()

void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
    csShared->Enter();
    try
    {
        //...
        Sleep(2000);
    }
    __finally
    {
        csShared->Leave();
    }
}

void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
    SharedFunction("Thread 1 calling");
    IdThreadComponent1->Stop();
}

とにかくBoostを使用しているので、代わりに独自のクラスを使用する必要がありmutexます。次に、、、またはまったくlock心配する必要はありません。例:try/__finallyEnter()Leave()

#include <boost/thread/recursive_mutex.hpp>
#include <boost/thread/locks.hpp>

boost::recursive_mutex mutex;

void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
    boost::lock_guard<boost::recursive_mutex> lock(mutex);
    //...
    Sleep(2000);
}

TMemoアクセスに関しては、またはクラスを使用して、そのTIdSyncコードTIdNotifyをスレッドセーフな方法で実行します。次に例を示します。

#include <IdSync.hpp>

class TMemoNotify : public TIdNotify
{
protected:
    String TextData;

    void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(TextData);
    }

public:
    __fastcall TMemoNotify(const String &ATextData) : TextData(ATextData) {}
};


void __fastcall TForm1::SharedFunction(UnicodeString TextData)
{
    ...
    (new TMemoNotify(TextData))->Notify(); // TIdNotify is self-freeing
    ...
}
于 2012-12-04T00:43:29.013 に答える
2

(メインスレッドで) 共有関数を利用し、3 つのスレッドから使​​用しようとしています。

メソッドまたはプロシージャ (通常はコードの一部) は、スレッド自体には属しません。アプリケーション内の任意のスレッドから任意のコードを呼び出すことができる可能性があり、異なるスレッドから同時に呼び出された場合、同時に複数回実行される可能性があります。

例えば

procedure A();
begin
  //do some work.
end;

次のように実行できます。

main thread
     |
  SomeFunc();
     |
     |      spawns
     |     thread X  
     |---------|
     |         |
     |         |
     |      OtherF()
    A()        |
     |         |   spawns thread Y
     |         |-------------|
     |        A()            |
     |         |             |
     |         |            A()
     |         |             | 
     |         |             |
  t1>|         |             |
     |         |             |
   A returns   |             |
    B()        |             |
     |         |             |
  t2>|         |             |
     |         |             |
     |        A returns      |
     |        thread end     |
     |                       |
     |                       |
  t3>|                       |
     |                      A returns
     |                      thread end
     |
   program end

t1 では 3 つの異なるスレッドが関数 A() を実行しており、t2 では 2 つのスレッド (X および Y) が引き続き実行されており、t3 では 1 つのスレッドのみがその関数 (スレッド Y) を実行しています。

csShared->Enter(); を呼び出しても問題ありませんか? 上記のように共有関数内で呼び出すか、次のように各スレッドの外部で呼び出す必要がありますか?

これはあなた次第です。どこで呼び出すかを定義する必要があります。これは、あるスレッドのコンテキストでのみ実行する必要があるコードを定義する責任があり、他のスレッドはこれが終了して開始するのを待っている間 (シリアル実行) です。

  • 関数本体全体をシリアル化する必要がある場合は、重要なセクション Enter を関数内に (最初の行として) 配置することをお勧めします。これにより、呼び出すサイトのコードがよりクリーンになり、忘れないようにすることができます。関数を呼び出す前にクリティカル セクションに入ります。
  • 関数呼び出しがより複雑な操作の一部である場合は、その操作の開始時に CriticalSection enter を呼び出し、明らかに関数本体の外側で呼び出す必要があります。
  • 関数の一部を並行して実行することも可能であるため、同時実行を優先し、関数内で必要な場合にのみ CriticalSection.Enter を呼び出す必要があります。

各 CriticalSection がボトルネックであることを忘れないでください。意図的なものですが、実際には必要のない場所で待機が発生しないように注意して使用する必要があります。

于 2012-12-03T23:05:23.480 に答える