wxThread 类参考 - 中文版(醇酒 2014.6.5)

这是wxWidgets论坛的中文版本。在这里,您可以用您的母语汉语讨论上面任一子论坛所涉及的所有关于wxWidgets的话题。欢迎大家参与到对有价值的帖子的中英互译工作中来!
Post Reply
ChunJiu
Knows some wx things
Knows some wx things
Posts: 35
Joined: Thu Jun 05, 2014 2:52 pm

wxThread 类参考 - 中文版(醇酒 2014.6.5)

Post by ChunJiu »

因为要使用到线程,所以仔细读完了相关文档,顺手译成中文当做学习笔记。 :D
醇酒 2014.6.5


wxThread 类参考
#include <wx/thread.h>


详细说明
线程是程序的一个基本执行途径。线程有时也被称为轻量进程,但线程和进程之间的根本区别在于不同进程的内存空间是分开的,而所有线程共享相同的地址空间。虽然这使得它更容易共享多个线程之间的共同数据,但也更容易绊到自己的脚,所以要谨慎使用同步对象,注意互斥体(见wxMutex)或临界区(见wxCriticalSection)的建议。此外,不要创建全局线程对象,因为他们在构造函数中分配内存,这将导致内存检查系统的问题。

wxThread的类型
wxWidgets中有两种类型的线程:独立的和联合的,以POSIX的线程API为蓝本。而Win32的 API中所有线程都是联合的。

默认情况下wxWidgets的线程wxThread是独立的,独立线程一旦执行完毕后就删除自己。当它们完成自己的处理任务、或调用一个Delect ( ) 后就删除了,因此必须在堆上面创建(例如通过new操作符)。通常情况下,需要保存那些分配好的独立型wxThread实例,这样就可以调用它们自身的函数。但由于其性质,访问它们的时候需要一直使用临界区:

Code: Select all

// 声明新的事件类型给我们的MyThread类使用:
wxDECLARE_EVENT(wxEVT_COMMAND_MYTHREAD_COMPLETED, wxThreadEvent);
wxDECLARE_EVENT(wxEVT_COMMAND_MYTHREAD_UPDATE, wxThreadEvent);
class MyFrame;

class MyThread : public wxThread
{
public:
    MyThread(MyFrame *handler)
        : wxThread(wxTHREAD_DETACHED)
        { m_pHandler = handler }
    ~MyThread();
protected:
    virtual ExitCode Entry();
    MyFrame *m_pHandler;
};

class MyFrame : public wxFrame
{
public:
    ...
    ~MyFrame()
    {
        // 最好是在 OnClose() 事件处理程序中做那些任何与线程清理
        // 有关的事情,而不是在析构函数里面做。 
        // 因为一个顶级窗口的事件循环在调用拆构函数的时候就不活动
        // 了,如果这个线程还想在结束的时候发送一些事件,则只能在
        //  OnClose() 内结束这个线程才能处理到这些事件。
        //  可参阅 @ref overview_windowdeletion的更多信息。
    }
    ...
    void DoStartThread();
    void DoPauseThread();
    // 一个恢复例程,几乎就等同于DoPauseThread()
    void DoResumeThread() { ... }
    void OnThreadUpdate(wxThreadEvent&);
    void OnThreadCompletion(wxThreadEvent&);
    void OnClose(wxCloseEvent&);

protected:
    MyThread *m_pThread;
    wxCriticalSection m_pThreadCS;    // 保护m_pThread指针
    wxDECLARE_EVENT_TABLE();
};

wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_CLOSE(MyFrame::OnClose)
    EVT_MENU(Minimal_Start,  MyFrame::DoStartThread)
    EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_MYTHREAD_UPDATE, MyFrame::OnThreadUpdate)
    EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_MYTHREAD_COMPLETED, MyFrame::OnThreadCompletion)
wxEND_EVENT_TABLE()

wxDEFINE_EVENT(wxEVT_COMMAND_MYTHREAD_COMPLETED, wxThreadEvent)
wxDEFINE_EVENT(wxEVT_COMMAND_MYTHREAD_UPDATE, wxThreadEvent)

void MyFrame::DoStartThread()
{
    m_pThread = new MyThread(this);
    if ( m_pThread->Run() != wxTHREAD_NO_ERROR )
    {
        wxLogError("Can't create the thread!");
        delete m_pThread;
        m_pThread = NULL;
    }
    // 只要在调用了wxThread::Run() 后,无论何时m_pThread都是“ 不安全 ” 的,
    // 线程随时会退出(因为它已经完成了工作)。 
    // 要避免指针空置,则在线程结束时OnThreadExit() 将它置为NULL。
}

wxThread::ExitCode MyThread::Entry()
{
    while (!TestDestroy())
    {
        // ... 做任务中的一点工作...
        wxQueueEvent(m_pHandler, new wxThreadEvent(wxEVT_COMMAND_MYTHREAD_UPDATE));
    }
    // 发出这个线程准备拆构的信号事件给处理程序。
    // 注意:我们假设在这里使用 pHandler 指针是安全的,因为在这个例子里是通过
    // MyFrame的拆构函数来保证的。
    wxQueueEvent(m_pHandler, new wxThreadEvent(wxEVT_COMMAND_MYTHREAD_COMPLETED));
    return (wxThread::ExitCode)0;     // 成功
}

MyThread::~MyThread()
{
    wxCriticalSectionLocker enter(m_pHandler->m_pThreadCS);
    // 由于该线程被销毁,要确保不会留下自身的悬空指针。
    m_pHandler->m_pThread = NULL;
}

void MyFrame::OnThreadCompletion(wxThreadEvent&)
{
    wxMessageOutputDebug().Printf("MYFRAME: MyThread exited!\n");
}

void MyFrame::OnThreadUpdate(wxThreadEvent&)
{
    wxMessageOutputDebug().Printf("MYFRAME: MyThread update...\n");
}

void MyFrame::DoPauseThread()
{
    // 由于我们随时要访问m_pThread指针,就必须确保它不会被同时修改; 因为只有
    // 唯一的一个线程才能在指定时间内进入给定的临界区,所以下面的代码是安全的:
    wxCriticalSectionLocker enter(m_pThreadCS);
    if (m_pThread)         // 线程还存在吗?
    {
        // 没有一个临界区的话,一旦达到这一点上就可能发生操作系统调度程序将控
        // 制权交给MyThread::Entry() 函数,等切换回来时可能会因为返回(因为
        // 它完成工作)变成了无效的m_pThread指针。
        if (m_pThread->Pause() != wxTHREAD_NO_ERROR )
            wxLogError("Can't pause the thread!");
    }
}

void MyFrame::OnClose(wxCloseEvent&)
{
    {
        wxCriticalSectionLocker enter(m_pThreadCS);
        if (m_pThread)         // 线程还存在吗?
        {
            wxMessageOutputDebug().Printf("MYFRAME: deleting thread");
            if (m_pThread->Delete() != wxTHREAD_NO_ERROR )
                wxLogError("Can't delete the thread!");
        }
    }       // 从这个临界区退出,给线程进入它的拆构函数提供可能(这是由
            // m_pThreadCS临界区守护的)。
    while (1)
    {
        {   // 是这个~MyThread()函数执行吗?
            wxCriticalSectionLocker enter(m_pThreadCS);
            if (!m_pThread) break;
        }
        // 等待这个线程完成
        wxThread::This()->Sleep(1);
    }
    Destroy();
}
如需更详细和全面的示例,请参见线程的范例。若想用一个比较简单的方式来共享主、辅助线程之间的数据和同步对象请参阅wxThreadHelper。它可以为主线程创建一个简易的辅助线程,只需要在声明和定义主线程类的时候同时继承这个wxThreadHelper类(所以它的名字才叫“线程助手”)。

不过,联合线程在做处理时不会删除自己,所以在栈上面创建它是安全的。联合线程也提供了一个功能:即通过Wait ( ) 从Entry ( ) 获得返回值。但先不要急着将所有线程都创建为联合线程,因为还有一个缺点:必须为一个联合线程做Wait ( ) ,否则使用的系统资源将永远得不到释放。而且如果不是在栈上创建它的,就必须自己去删除相应的wxThread。相反地,独立线程是“发射后不管”那种:只需要启动一个独立线程,它就会在终止时自我销毁。

删除wxThread

不管它终止与否,都应该调用一个联合线程的Wait ( ) 来释放它的内存,参见wxThread的类型概括。如果在堆上创建一个联合线程,记得要用delete运算符、或类似于独立线程程序这种类型才需要的内存管理方式来手动将其删除。

因此,独立线程完成处理之后会自己销毁,在调用一个例程时必须要注意这一点。如果打算在这些线程还在运行时就结束它,可调用Delete ( ) 优雅地结束它(这意味着该线程在调用Delete ( ) 时将被删除)。这也意味着绝对不要尝试用C++的delete运算符或类似的手段去删除独立线程。

如前所述,Wait ( ) 或Delete ( ) 函数分别尝试优雅地终止一个联合、与独立的线程。它们会等待直到相关的线程调用TestDestroy ( ) 或结束处理(即从wxThread::Entry返回)。显然,如果线程没有调用TestDestroy ( ) 就不能结束,只有在调用了线程的Wait ( ) 或Delete ( ) 后才会停止。这就是为什么在线程的Entry ( ) 例程内部调用TestDestroy ( ) 是多么的重要,并且在它返回真的时候立即退出。

作为最后无可奈何的手段,还可以通过Kill ( ) 来立即结束线程。但强烈建议不要这样做,因为它不会释放(虽然独立线程的wxThread对象仍然会被删除)与对象相关的资源,而且退出C运行时库时处于一个不确定的状态。

在辅助线程内调用wxWidgets

除了“主应用程序线程”(例如一个运行的wxApp::OnInit ( ) 、或在main函数内运行的)的所有其它线程被认为是“辅助线程”。

所有辅助线程对图形用户界面的调用是不安全的,比如那些wxWindow或wxBitmap,并可能提前结束应用程序。这是由于以下几个原因:包括底层的本地API,而且实际上wxThread并不运行类似于其它API、MFC那样的GUI事件循环。
有一种解决方法是在任何的GUI调用之前对一些wxWidgets的端口调用wxMutexGUIEnter ( ) ,然后在后面调用wxMutexGUILeave ( ) 。不过,推荐用简单的方法即通过wxQueueEvent () 发送一个事件然后在主线程里处理对GUI的调用。这并不表示调用这些类是线程安全的,因为大多数的wxWidgets类不是线程安全的,包括wxString。

注意:WxMutexGUIEnter ( ) 和 wxMutexGUILeave ( ) 当前仅能在Windows平台下使用。

不要去轮询一个wxThread

wxThread一个常见问题是用户按照经验,时不时在他们的主线程中检查该线程,通过IsRunning ( ) 看它是否已结束。但很快就会发现他们的应用遇到了问题,因为线程使用默认行为(独立线程)删除了自身。当然,他们能尝试使用联合线程来继续之前的不良行为,但通过轮询wxThread来检查它是否已结束真的是一个坏主意。事实上,呼吁所有正在使用wxThread的例程尽量别使用这种方式,而是要找到另一个方法来通知自己~ 线程已经结束了。

在通常的情况下只需要通知主线程,可以用wxQueueEvent ( ) 将事件发送给它。如要通知给辅助线程,在线程即将完成处理 和/或 设置一个变量的值时,去调用另一个类的例程就可能要使用互斥锁(参见wxMutex)和/或 若有必要~ 则意味着再加上其它方式的同步。

库: wxBase
类别: 线程
另请参阅
wxThreadHelper,wxMutex,wxCondition,wxCriticalSection,多线程概述
Post Reply