17.2 使用wxThread

如果你要在你的代码中使用线程,首先要实现一个wxThread的派生类,并且至少要重载其虚函数Entry,这个函数包含了线程要做的主要的事情.举例来说,比如我们要用一个单独的线程来计算图片中颜色的数目,下面是我们的派生类的声明:

class MyThread : public wxThread
{
public:
    MyThread(wxImage* image, int* count):
        m_image(image), m_count(count) {}
    virtual void *Entry();
private:
    wxImage* m_image;
    int*     m_count;
};
// 一个标识符 用来在线程工作完成的时候通知应用程序.
#define ID_COUNTED_COLORS    100

Entry函数用来进行计算工作并且返回一个返回值(对于联合线程(即将介绍),Wait函数将返回这个值),下面是我们的Entry函数:

void *MyThread::Entry()
{
    (* m_count) = m_image->CountColours();
    // 使用一个已知的事件来通知应用程序.
    wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED,
                         ID_COUNTED_COLORS);
    wxGetApp().AddPendingEvent(event);
    return NULL;
}

为了简单起见,我们没有定义新的事件而是使用了一个已有的事件通知应用程序线程的工作已经做完了.

线程的创建

线程的创建分为两步,首先产生一个线程的实例,然后调用线程的Create函数:

MyThread *thread = new MyThread();
if ( thread->Create() != wxTHREAD_NO_ERROR )
{
    wxLogError(wxT("Can't create thread!"));
}

有两种不同的线程,一种线程你在启动之后就可以忘记它的存在,而另外一种,你需要等待它返回一个结果.前者我们称为分离线程,后者称为联合线程. 线程的类型是通过调用wxThread的构造函数时传递的参数是wxTHREAD_DETACHED还是wxThrEAD_JOINABLE来决定的,联合线程的返回值是由对其成员函数Wait的调用返回的,而对于分离线程,不可以调用Wait函数.

你不必把所有的线程都创建为联合线程,,因为联合线程有它不方便的地方,你必须调用联合线程的Wait函数来等待联合线程结束,否则系统为这个线程分配的所有的资源将不会被释放,并且你还需要自己删除这个线程对象(尽管这个对象只能使用一次).而分离线程则具有"点火以后就忘掉"的特性,你只需要启动它,它将自己自己中止和释放.

当然,这也意味者分离线程必须在堆上创建,因为它将在结束的时候调用delte(this).联合线程既可以在栈上创建也可以在堆上创建,不同通常也都是在堆上创建的.不要创建全局的线程对象,因为他们将在他们的构造函数执行的时候分配内存,这将导致内存检测系统出现一些问题.

指定栈大小

你可以在调用线程的Create函数的时候指定它的栈大小,默认的0代表使用操作系统默认的大小.

指定优先级

某些操作系统允许应用程序自己提供一个线程的优先级(时间片的大小),你可以通过wxThread::SetPriority函数来达到这个目的.优先级的数值在0到100之间,0为最低优先级而100为最高优先级,不过最好使用预定义的宏wxTHREADMIN_PRIORITY, wxTHREAD_DEFAULT_PRIORITY和wxTHREAD_MAX PRIORITY,他们的值分别为0,50和100.SetPriority函数应该在调用Create之后,调用Run函数之前被调用.

启动线程

调用Create函数以后,线程还没有开始运行,你还需要调用wxThread::Run函数来启动线程,这个函数将会调用你自定义的Entry函数.

怎样暂停线程以等待一个外部条件

如果线程需要等待某些事情发生,你应该避免直接使用查询和什么事都不做这样的循环,这会让你的程序"忙于等待",白白浪费CPU的时间.

如果你需要等待几秒钟,你可以使用wxThread::Sleep函数.

如果你正在等待什么事情发生,你应该使用一种调用来阻止当前线程执行直到你收到事情已经发生的通知.例如,如果你在线程中使用了 socket,你应该阻塞在socket的系统调用上,直至socket收到数据,这就不会白白浪费CPU了,或者如果你正在等待一个联合线程使用的数据,你可以调用Wait其函数来阻塞自己.

有时候你可能会想用线程的Pause和Resume函数来临时将你的线程置入睡眠状态,但是这样做有两个问题,首先,暂停机制不是所有的系统都支持,有些系统(尤其是POSIX标准的系统)的Pause是模拟的,线程必须调用TestDestroy并且在这个函数返回True的时候立刻中断自己的运行.第二个问题是,调用Pause的线程有时候很难回复正常运行,因为这使得操作系统可能在任何时候中止你的线程的运行,如果你的线程正在锁定一个信号量,很可能来不及释放这个信号量导致出现死锁.

因此,使用Pause和Resume并不是一个很好的设计,你应该尽可能使用信号量或者关键区域(参见下一小节)来重新设计你的代码.

线程中止

正如前面我们谈到的那样,分离线程是自动结束和释放自己的.而对于联合线程,你可以简单的调用wxThread::Wait函数.或者在一个GUI程序中,你可以在系统空闲事件处理函数中调用wxThread::IsAlive函数,然后仅在IsAlive返回False的时候调用 Wait函数.Wait函数是释放联合线程资源的唯一的方法.

你可以调用wxThread::Delete来请求删除一个线程,不过要让它工作正常,你需要在你的线程中周期性的调用TestDestroy函数(译者注:根据经验,在Windows平台上,对于联合线程使用这种方法好像不是一个好主意).