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;
}
//---------------------------------------------------------------------------

问题:

- 如果我快速关闭窗口(只有一个线程执行该函数,它永远不会离开程序 - 永远等待,并且在调试器中只有第一个线程退出,其他两个不退出)。我正在使用 OnCloseQuery 事件来检查线程是否已完成。我做错了什么?

[编辑]Memo1->Lines->Add(TextData);按照大卫赫弗南评论中的建议删除后,它正确退出,因此这部分问题得到解决,以下内容仍然存在:

  • 像上面那样在共享函数内部调用是否可以csShared->Enter();,或者我必须在每个线程中调用它外部,如下所示:

    void __fastcall TForm1::IdThreadComponent1Run(TIdThreadComponent *Sender)
    {
    csShared->Enter();
    SharedFunction("Thread 1 calling");
    csShared->Leave();
    IdThreadComponent1->Stop();
    }
    
  • 这比上面的版本更好吗(csShared->Enter();在函数本身内调用)?还是一样?两个版本似乎都可以正常工作,我想知道是否有区别,因为第一个版本更干净。

如果您想知道,我不需要Synchronize,这将用于磁盘写入而不是用于更新 VCL,因此上述 SharedFunction 仅用于示例目的。

4

2 回答 2

3

将调用放在共享函数内部是很好的,甚至是可取的Enter()Leave()但是,如果你要使用try/__finally那么你需要把它放在Enter()外面try,以防它失败。你不想做Leave()一些你没有成功的事情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,因此您应该使用它自己的类mutexlock类,那么您不必担心try/__finally,Enter()或根本不用担心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访问,使用TIdSyncorTIdNotify类以线程安全的方式执行该代码,例如:

#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 只有一个线程执行该函数(线程 Y)。

调用 csShared->Enter(); 可以吗?在上面的共享函数内部还是我必须在每个线程中调用它外部,如下所示:

这取决于你。您必须定义在哪里调用它,因为您有责任定义哪些代码必须仅在一个线程的上下文中运行,而其他代码将等待它完成开始(串行执行)。

  • 如果要序列化整个函数体,我认为最好将关键部分 Enter 放在函数内(作为第一行),因为恕我直言,这会导致调用站点中的代码更清晰,同时确保您不会忘记在调用函数之前进入临界区。
  • 如果函数调用是更复杂操作的一部分,则必须在该操作开始时调用 CriticalSection 输入,显然是在函数体之外。
  • 函数的某些部分也可以并行运行,因此您必须支持并发并仅在函数内部需要时调用 CriticalSection.Enter。

请记住,每个关键部分都是瓶颈。一个故意的,但你必须谨慎使用它以避免在不需要的地方引入等待。

于 2012-12-03T23:05:23.480 回答