C++:多线程类库的设计与实现(六)

原文地址: http://yuyunwu.blog.sohu.com/81487237.html
线程类的具体实现

一、将接口和实现相分离

分离接口和实现是OO设计中的重要思想。它其实反映了现实世界中抽象和具象、一般和特殊的基本现象和规律。而我们,正是基于这种思想,来完成我们类库的跨平台特性。

基本上所有的OO开发环境都提供了支持接口和实现分离的机制。很多机制是基于编程语言本身的,如可继承性和多态性,而有些机制还基于体系结构特性,比如CORBA和COM。

我们把有关线程的具体实现封装到“实现类”里面,命名为TheadImpl。实际上ThreadImpl只是一个薄层封装,为上层Thread类提供一个统一线程API调用界面。

二、包装系统的API

虽然只是一个薄层封装,我们不得不做常常是让类库设计者最头疼的一件事情---统一操作系统API的调用界面。每种操作系统都提供API,常常都是相互风格迥异,这给统一包装API界面带来很多困难---虽然同时也增加了这项工作的艺术性。设计者不得不充分调研各种对象操作系统,了解每个API的调用规格,详细归纳,慎重取舍,以设计出良好的统一包装界面。在这里系统编程经验至关重要。

虽然本系列的文章顺序是先介绍线程类Thread的接口,再来讨论ThreadImpl的具体实现,这可能会给读者带来自上而下(Top down)的设计路线的印象。而实际上,和许多工作的实际过程一样,C++类库设计也不是简单的自上而下或者自下而上(Bottom up),而是一个PDCA循环(规划 Plan-执行 DO-检查 Check-措施 Action)。据我的个人经验,每个类库在第一版设计完成后都要经过重复数次的修改,以达到一个相对稳定的版本。

以上的经验之谈是希望读者能理解和大多数工作一样C++类库设计也不是一件一蹴而就的直线性工作。下面介绍我们的对象系统的线程API---我们的包装对象。

三、盘点POSIX与Windows的线程API
和其他POSIX系统API一样,POSIX线程的API在POSIX规范 IEEE 1003系列中被定义,目前的版本为2004年版。POSIX线程提供丰富的系统调用,不过我们的线程类只使用其中的一部分。如果读者手头没有IEEE的文档也没有关系,因为POSIX线程被广泛实现于各种Linux平台,绝大多数Linux系统的手册页(Manual page)都有相关的叙述。

创建线程:

POSIX:
int pthread_create (
     pthread_t *restrict thread,
     const pthread_attr_t *restrict attr,
     void *(*start_routine)(void*), 
     void *restrict arg
);

pthread_create()创建线程并立刻执行它。


Windows:
HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      SIZE_T dwStackSize,
      LPTHREAD_START_ROUTINE lpStartAddress,
      LPVOID lpParameter,
      DWORD dwCreationFlags,
      LPDWORD lpThreadId
);

CreateThread()允许在dwCreationFlags参数中指定线程是否以挂起状态被创建(CREATE_SUSPENDED标志)。另外CreateThread允许通过dwStackSize指定线程初始栈大小。

这两个函数都接受一个用户例程函数指针,作为线程运行的用户代码入口。

用户例程的形式定义:
POSIX:
void * start_routine(void * param);

Windows:
DWORD WINAPI ThreadProc(LPVOID lpParameter);

设置线程初始栈:

Posix:
通过线程属性设定。

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

Windows:
在CreateThread()中指定。

判断线程死活/有效性

POSIX:
IEEE 1003中并没有明确指定判断线程死活的API。不过很多人使用pthread_kill():
int pthread_kill(pthread_t thread, int sig);

通过发送信号0来测试线程的有效性。这样的做的依据可能来源于标准IEEE 1003关于pthread_kill()的一段注释:
“Upon successful completion, the function shall return a value of zero. The pthread_kill ( ) function shall request that a signal be delivered to the specified thread. As in kill( ), if sig is zero, error checking shall be performed but no signal shall actually be sent.”

另外规范中对于pthread_kill()的返回值定义如下:
[ESRCH] No thread could be found corresponding to that specified by the given thread ID.
[EINVAL] The value of the sig argument is an invalid or unsupported signal number.

调用pthread_kill,发送信号0,检测pthread_kill的返回值。如果pthread_t型的ID指向一个有效并且执行中的线程,则pthread_kill应该返回0;如果是一个无效ID或者线程已经结束,则pthread_kill应该返回ESRCH。

这看起来是个不错的办法,可惜笔者在实践中发现,在某些POSIX实现(具体的说是Cygwin的某些版本)中,调用pthread_kill并传递一个无效的线程ID将直接导致指针操作异常而使程序异常结束。

笔者发现使用另一个API好像更为安全:

int pthread_getschedparam(
    pthread_t thread, 
    int *restrict policy,
    struct sched_param *restrict param
);

pthread_getschedparam()有类似的调用语义规范:

“RETURN VALUE
If successful, the pthread_getschedparam( ) and pthread_setschedparam( ) functions shall return zero; otherwise, an error number shall be returned to indicate the error.
The pthread_getschedparam() function may fail if:
 [ESRCH] The value specified by thread does not refer to an existing thread.”

所以,在我们的版本中,用pthread_getschedparam取代pthread_kill来测试线程死活。pthread_getschedparam的这一用途在笔者所测试的Cygwin和Linux版本中发挥正常。

Windows:
DWORD WaitForSingleObject(
      HANDLE hHandle,
      DWORD dwMilliseconds
);

Windows编程的规范做法,是调用WaitForSingleObject,指定等待时间为0。如果返回值为WAIT_TIMEOUT则说明线程为有效,否则为无效线程HANDLE或者线程已经结束。

线程的睡眠函数:
POSIX:
unsigned sleep(unsigned seconds); 
int usleep(useconds_t useconds);

这两个API提供不同时间粒度的睡眠。我们可以把它们组合起来以实现毫秒级粒度的睡眠功能。另外某些老版本的Linux实现中把sleep()和usleep()解释为进程级别的睡眠API---这已经过时了。根据IEEE 1003 2004的说明:
“The sleep( ) function shall cause the calling thread to be suspended from execution until either the number of realtime seconds specified by the argument seconds has elapsed or a signal is delivered to the calling thread and its action is to invoke a signal-catching function or to terminate the process.”

usleep也有内似的说明。目前的流行Linux版本的sleep和usleep都是线程级别的。

Windows:

VOID Sleep(DWORD dwMilliseconds); 

强行结束线程:
POSIX:
int pthread_cancel(pthread_t thread);

Windows:
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);

程序员应该尽量避免调用强行结束线程的API,尤其是在C++程序设计中。我们将在后续的文章中解释其原因。

四、完成线程实现类的代码
下面给出POSIX线程实现类代码。代码中使用了Java风格的注释。

//---- ThreadImpl_Linux.h----

#ifndef THREAD_IMPL_LINUX_H
#define THREAD_IMPL_LINUX_H

#include <pthread.h>
#include <unistd.h>

class Thread;

/**
 * Linux(POSIX)版本的线程实现类。
*/
class ThreadImpl  
{

public:
    static const int        WAIT_INFINITE            = -1;
    static const int        INVALID_THREAD_ID        = 0;
    static const size_t     DEFAULT_STACK_SIZE       = 0;
    static const int        WAIT_TIME_SLICE          = 10;
    
    
public:

    /**
     * 线程实现类的内部结构体。包含线程的平台相关信息。
     */
    struct ThreadStruct {
        pthread_t     tThread;
        
        ThreadStruct() 
            :
            tThread(INVALID_THREAD_ID)
            
        {
        }

        ~ThreadStruct() {
        }
    };
    

public:
    typedef int (* THREAD_FUNCTION) (Thread *pParam);
    typedef void * (*POSIX_THREAD_ROUTINE) (void *pParam);
    
public:

    /**
     * 创建线程。
     * @param ts线程实现类的内部结构体型变量。
     * @param thread_func 用户例程函数指针。
     * @param cbStackSize 线程初始栈大小,字节单位。
     * @param pThread 绑定的Thread对象指针。
     * @return 调用成功返回true,失败返回false。
     */
    static bool CreateThread(ThreadStruct & ts, 
                    THREAD_FUNCTION thread_func,
                    size_t cbStackSize,
                    Thread * pThread) {
        
        
        pthread_attr_t *pAttr = NULL;
        pthread_attr_t attr;
        
        if(cbStackSize != DEFAULT_STACK_SIZE) {
            
            if(0 != pthread_attr_init(&attr)) {
                return false;
            }
            
            if(0 != pthread_attr_setstacksize(&attr, cbStackSize)) {
                pthread_attr_destroy(&attr);
                return false;
            }
            
            pAttr = &attr;
        }
        
        int iRes = pthread_create(&(ts.tThread), 
                            pAttr, 
                            (POSIX_THREAD_ROUTINE)thread_func, 
                            (void*)pThread);
                
        if(NULL != pAttr) {
            pthread_attr_destroy(&attr);
        }
                
        if(0 != iRes) {
            return false;
        }
        
        pthread_detach(ts.tThread);
        
        return true;
    }

    /**
     * 销毁线程实现类的内部结构体。
     * @param ts 线程实现类的内部结构体型变量。
     */
    static void DestroyThread(ThreadStruct & ts) {
    }


    /**
     * 在指定时间内等待线程结束。
     * @param ts 线程实现类的内部结构体。
     * @param iTimeout 指定的等待时间。
     * @return 线程在指定时间内结束的情况下返回true,否则返回false。
     * 如果传入的线程结构体未关联到有效线程,则返回true。
     */
    static bool WaitForThreadEnd(const ThreadStruct & ts, int iTimeout) {
        
        int iDelta = WAIT_TIME_SLICE;
        int iTotal = iTimeout;
        
        if(iTimeout == WAIT_INFINITE) { // Cause to do unlimited loop.
            iDelta = 0;
            iTotal = 1;
        }
        
        for(int i=0; i<iTotal; i+=iDelta) {
            if(!IsAlive(ts)) {
                return true;
            } else {
                Sleep(WAIT_TIME_SLICE);
            }
        }
        
        return false;
    } 

    /**
     * 强行结束线程。
     * @param ts 线程实现类的内部结构体型变量。
     */
    static void TerminateThread(ThreadStruct & ts) {
        ::pthread_cancel(ts.tThread);
    }

    /**
     * 判断线程死活。
     * @param ts 线程实现类的内部结构体型变量。
     * @return 线程为有效则返回true,否则返回false。
     */
    static bool IsAlive(const ThreadStruct & ts) {

        int iPolicy;
        struct sched_param sp;
        
        int iRes = pthread_getschedparam(ts.tThread, &iPolicy, &sp);
        
        if(0 == iRes) {
            return true;
        } else {
            return false;
        }
    } 
    
    /**
     * 获取线程ID。该ID在进程域内唯一。
     * @param ts线程实现类的内部结构体型变量。
     */
    static int GetThreadId(const ThreadStruct & ts) {
        return (int) ts.tThread;
    }

    /**
     * 导致调用线程被挂起若干时间。
     * @param iMs 线程挂起时间,毫秒单位。
     */
    static void Sleep(int iMs) {
        
        int iS  = iMs / 1000;
        int iUs = (iMs % 1000) * 1000;
        
        if(iS > 0) {
            sleep(iS);
        }
        
        if(iUs > 0) {
            usleep(iUs);
        }
        
        return;
    }
    
private:
    // 禁止构造函数。
    ThreadImpl();
    
    // 禁止拷贝构造函数。
    ThreadImpl(const ThreadImpl &) throw();

    // 禁止赋值操作符。
    void operator=(const ThreadImpl &);
}; // class ThreadImpl

#endif // #ifndef ThreadImpl_LINUX_H

//----EOF----


Windows的线程实现类的代码如下:

//----ThreadImpl_WIN32.h----

#ifndef THREAD_IMPL_WIN32_H
#define THREAD_IMPL_WIN32_H

#include <Windows.h>

class Thread;

/**
 * Windows 32位平台版本的线程实现类。
 */
class ThreadImpl  
{


public:
    static const int        WAIT_INFINITE            = -1;
    static const int        INVALID_THREAD_ID        = 0;
    static const size_t    DEFAULT_STACK_SIZE        = 0;

public:
    /**
     * 线程实现类的内部结构体。包含线程的平台相关信息。
     */
    struct ThreadStruct {
        
        HANDLE     hThread;
        DWORD    dwThreadId;
        
        ThreadStruct() 
            :
            hThread(NULL),
            dwThreadId(INVALID_THREAD_ID)
        {
        }
        
        void Cleanup() {
            if(hThread != NULL) {
                CloseHandle(hThread);
                hThread = NULL;
            }
            
        }

        ~ThreadStruct() {
            Cleanup();
        }
    };
    
public:
    typedef int (* THREAD_FUNCTION) (Thread *pParam);

public:
    
    /**
     * 创建线程。
     * @param ts线程实现类的内部结构体型变量。
     * @param thread_func 用户例程函数指针。
     * @param cbStackSize 线程初始栈大小,字节单位。
     * @param pThread 绑定的Thread对象指针。
     * @return 调用成功返回true,失败返回false。
     */

    static bool CreateThread(ThreadStruct & ts, 
                    THREAD_FUNCTION thread_func,
                    size_t cbStackSize,
                    Thread * pThread) {
        

        ts.hThread = ::CreateThread(NULL, 
                    (DWORD) cbStackSize,
                    (LPTHREAD_START_ROUTINE) thread_func,
                    (LPVOID) pThread,
                    0,
                    &ts.dwThreadId);
                            
        if(ts.hThread == NULL) {
            return false;
        }
        
        return true;
    }

    /**
     * 销毁线程实现类的内部结构体。
     * @param ts 线程实现类的内部结构体型变量。
     */
    static void DestroyThread(ThreadStruct & ts) {
        ts.Cleanup();    
    }


    /**
     * 在指定时间内等待线程结束。
     * @param ts 线程实现类的内部结构体。
     * @param iTimeout 指定的等待时间。
     * @return 线程在指定时间内结束的情况下返回true,否则返回false。
     * 如果传入的线程结构体未关联到有效线程,则返回true。
     */
    static bool WaitForThreadEnd(const ThreadStruct & ts, int iTimeout) {
        
        DWORD dwTO = (DWORD) iTimeout;

        if(iTimeout == WAIT_INFINITE) {
            dwTO = INFINITE;
        }

        DWORD dwRes = WaitForSingleObject(ts.hThread, dwTO);
        
        if(dwRes == WAIT_TIMEOUT) {
            return false;
        } else {
            return true;
        }
    } 

    /**
     * 强行结束线程。
     * @param ts 线程实现类的内部结构体型变量。
     */
    static void TerminateThread(const ThreadStruct & ts) {
        ::TerminateThread(ts.hThread, -1);
    }

    /**
     * 判断线程死活。
     * @param ts 线程实现类的内部结构体型变量。
     * @return 线程为有效则返回true,否则返回false。
     */
    static bool IsAlive(const ThreadStruct & ts) {

        if(NULL == ts.hThread) {
            return false;
        }
    
        DWORD dwRes = WaitForSingleObject(ts.hThread, 0);
        
        if(WAIT_TIMEOUT == dwRes) {
            return true;    
        } else if(WAIT_OBJECT_0 == dwRes || WAIT_FAILED == dwRes) {
            return false;
        } else {
            return false;
        }
    } 
    
    /**
     * 获取线程ID。该ID在进程域内唯一。
     * @param ts线程实现类的内部结构体型变量。
     */
    static int GetThreadId(const ThreadStruct & ts) {
        return (int) ts.dwThreadId;
    }

    /**
     * 导致调用线程被挂起若干时间。
     * @param iMs 线程挂起时间,毫秒单位。
     */
    static void Sleep(int iMs) {
        return ::Sleep(iMs);
    }
    
private:
    // 禁止构造函数。
    ThreadImpl();
    
    // 禁止拷贝构造函数。
    ThreadImpl(const ThreadImpl &) throw();
// 禁止赋值操作符。
    void operator=(const ThreadImpl &);
}; // class ThreadImpl

#endif // #ifndef ThreadImpl_WIN32_H

//----EOF----

对上面的代码稍微做一点说明。

Linux版本的CreateThread()中,创建完线程后立即调用pthread_detach(),以通知POSIX线程库可以在该线程结束时立即释放线程资源。注意到我们不使用pthread_join()来等待线程结束。POSIX的现程模型使用pthread_join()来实现简单的线程调度,在笔者看来用起来很不方便,因为pthread_join()只能是无限等待,不提供超时机制,大大降低了程序设计的灵活性。

为了实现带超时的线程等待函数,如我们的WaitForThreadEnd(),我们不得不把等待时间分解成更小的时间片轮询。这样做损失了一点CPU资源,并稍微影响了超时判断的时间精度。

Windows版本的TerminateThread()中,我们将线程的结束代码(Exit code)一律指定为-1。我们的上层线程类不使用这个结束代码。

Linux版本和Windows版本的线程实现类都不提供线程优先级的相关功能。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值