Windows并发&异步编程(1)JAVA&多线程

本文在基于C/C++/Windows相关知识的基础上,初步封装一个像JAVA一样的多线程类–Win32Thread。使操作线程能像JAVA一样两步搞定:

  1. 继承基类Win32Thread,并覆盖其中的run方法;
  2. 定义线程类对象,调用start(),即可启动一个线程。

整个一套太极打下来,就应该是如下这样…

class TestThread : public Win32Thread {
public:
    TestThread(){};
    ~TestThread(){};
protected:
    void run();
private:
};

void TestThread::run(){
    for (int i = 0; i < 30; ++i){
        printf("son %d\n", i);
    }
}

int main(void){
    TestThread son;
    son.start();
    return 0;
}

目录:

Windows Thread?

由于是初步仿JAVA,封装一个Windows Thread类,这里只实现了常用的十几个方法:setThreadPriority、getThreadPriority、start、join、suspend、resume、getThreadId、setThreadName、getThreadName、isAlive、terminateThread、sleep、exitThread、threadProc、createThread、getExitCodeThread,后期继续加入notify、notifyAll等方法,并调整Win32Thread类,使其更符合实际需求。

virtual run

为了让子类覆盖基类,用儿子自己的run方法体。那么,老子的run方法就必须为虚,最好是纯虚。这样才能熟练的使用C++的多态,战士打靶,指哪打哪。

virtual void run() = 0;

static threadProc

其实run为我们做出了很大的牺牲,因为它必须紧抱C++的多态的大腿。

  • 另外,既然是要实现仿JAVA,那么run就必须为:void run();

很忧伤,这并不符合Windows中线程执行体的规范,(偷偷告诉你,下面这个就是Windows规范)

typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );

为了补偿run,这里我就给他添加一个帮手“static threadProc”。

static DWORD WINAPI threadProc(LPVOID lpVoid = NULL);

这下OK了,threadProc完美符合Windows老大的要求。如此,Thread已经达到了初步在CPU上奔跑要求了《奔跑吧,兄弟》… 一人得道鸡犬升天,我们的run也可以借助帮手,暗度陈仓。

DWORD WINAPI Win32Thread::threadProc(LPVOID lpVoid){
    if (_thread){
        _thread->run();
        if (_thread->_hThread){
            ::CloseHandle(_thread->_hThread);
        }
        return 0L;
    }
    return -1L;
}

一行“_thread->run()”,让我们的run兄弟终于见到了阳光。可是也是付出了不少代价,threadProc是静态的,没有this指针。这里run还得感谢全局的_thread变量老大哥的提携啊!

Win32Thread

好了,扶贫工程最困难的村已经攻坚了,run兄弟也走向了大康道路。那么,也到了大刀阔斧的时刻了——是时候表演真正的技术了!

Music…

#pragma once

#include <Windows.h>  
#include <string>  

class Win32Thread{
public:
    Win32Thread(char* threadName = NULL);
    virtual ~Win32Thread();
    void setThreadPriority(int nPriority);
    int getThreadPriority();
    void start();
    void join(DWORD dwMilliseconds = INFINITE);
    void suspend();
    void resume();
    DWORD getThreadId();
    void setThreadName(char* threadName);
    const char* getThreadName();
    bool isAlive();
    void terminateThread();
    void sleep(DWORD dwMilliseconds);
    //void notify();
    //void notifyAll();
protected:
    virtual void run() = 0;
    void exitThread();
private:
    static DWORD WINAPI threadProc(LPVOID lpVoid = NULL);
    void createThread();
    DWORD getExitCodeThread();
private:
    static Win32Thread* _thread;
    HANDLE _hThread;
    std::string _threadName;
};

这里为何?将notify/notifyAll两兄弟给注释了,当然是有原因的。但这里暂且不表,不远(不需要坐飞机),后文就会给他两个公道。

  • 开始一个一个介绍Win32Thread家的成员,板凳瓜子准备。

锲子:

标准C运行库是在1970年左右发明的,而多线程诞生就比较晚了(国内第一台支持多线程的是,1993年10月的“曙光1号”)。

所以,“标准C/C++运行库最初不是为多线程应用而设计”。

创建新线程时,一定不要调用操作系统的CreateThread函数。相反,必须调用C/C++运行库函数_beginthreadex。
——《Windows核心编程》(第五版)

_beginthreadex和_endthreadex是一对孪生兄弟,他们的父母是_beginthread先生与_endthread女生。由于_beginthread老人家在创建线程时,存在参数较少的局限性;而_endthread又存在一个鲜为人知的bug——她在调用ExitThread之前,会调用CloseHandle。

_beginthreadex和_endthreadex,应潮流而生。

这里深入讲解原理性东西,我也没实践过,纸上得来终觉浅。也不敢妄言。具体可以参见上面推荐的那本神作。

但是,本文讲的是Windows API,因为创建线程最终调用的还是CreateThread,下文不在谈论_beginthreadex等C/C++运行库问题。

如果要使用_beginthreadex,很简单。Ctrl+A(全选)+Ctrl+H(替换)即可。将CreateThread直接替换为_beginthreadex,当然,函数是要成对使用的,这时候ExitThread也要替换为_endthreadex。但是,C/C++运行库并不是为Windows而存在的,这里会有些许的参数类型不一致的问题,强行转换即可。

CreateThread

作为瑞士军刀,这位老大哥是第一个出场的。简单介绍一些它的外貌,

CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,                         //线程堆栈大小
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,      //run兄弟
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,    //线程参数
    _In_ DWORD dwCreationFlags,                      //创建标志
    _Out_opt_ LPDWORD lpThreadId
    );

和上一篇文章《Windows并发&异步编程(0)创建、终止进程》中的CreateProcess老大哥一样,乍看一眼,参数还蛮子多的…

/*
功能:创建线程并将其挂起
描述:CREATE_SUSPENDED 表示,创建后将进程挂起,之所以创建后挂起,是为了后续配置更多的线程参数,如优先级..等
*/
void Win32Thread::createThread(){
    _hThread = ::CreateThread(NULL, 0, threadProc, NULL, CREATE_SUSPENDED, NULL);
}

不要慌张,搞不清楚的参数,我们直接NULL或者0。Windows这人还是蛮好的,我们搞不清楚的,它就会给一个默认的值。正如,dwStackSize一样,它就会给俺一个4M大小的默认堆栈空间。

  • CREATE_SUSPENDED,这个参数很重要啊,如果没有这哥们。CreateThread之后,这线程就自个跑起来了。那还怎么装逼的仿照JAVA调用start()启动线程?

start

既然,上文已经提到了start。那不讲它都不行了。我本来是想先讲ResumeThread这哥们的,先委屈你了。

/*
功能:启动线程
描述:将挂起的线程重置为就绪状态,等待CPU调度
*/
void Win32Thread::start(){
    resume();
}

ResumeThread

打铁还需自身硬,start就知道来些虚的。还不是靠ResumeThread老大哥来干活,上面看到的resume其实就是我的小名。

/*
功能:唤醒线程
描述:将线程重置为就绪状态,等待CPU调度
*/
void Win32Thread::resume(){
    if (_hThread){
        ::ResumeThread(_hThread);
    }
}

SuspendThread

SuspendThread立马跑出来拆台了,这娃从小就和ResumeThread不对眼。它的口号是:“她南辕,我就北辙”。两人一唱一和的,几十年过去了,也到还相安无事。

/*
功能:挂起线程
*/
void Win32Thread::suspend(){
    if (_hThread){
        ::SuspendThread(_hThread);
    }
}
  • 其他,无关紧要的小虾米。

SetThreadPriority

为了争夺CPU,每个线程的使劲了吃奶力气。可是,裁判就是这位SetThreadPriority大佬啊!莫名的让我想起了学校的奖学金。

/*
功能:设置线程优先级
描述:nPriority =
Win32Thread_BASE_PRIORITY_IDLE
Win32Thread_BASE_PRIORITY_LOWRT
Win32Thread_BASE_PRIORITY_MIN
Win32Thread_BASE_PRIORITY_MAX
*/
void Win32Thread::setThreadPriority(int nPriority){
    if (_hThread){
        ::SetThreadPriority(_hThread, nPriority);
    }
}

GetThreadPriority

一个好汉还三个帮,Priority的一个好帮手!

/*
功能:获取线程优先级
描述:默认线程优先级为0
*/
int Win32Thread::getThreadPriority(){
    return ::GetThreadPriority(_hThread);
}

GetThreadId

俗话说:“人的名,树的影”!(其实我是在小说里看到这句话的),每个线程也不可或缺的需要一个UID。还是唯一的哦,好比身份证。

/*
功能:获取线程ID
描述:线程ID是操作系统对每个线程的唯一标识,这里需要注意,当线程释放后该ID可能被分配给其他线程
*/
DWORD Win32Thread::getThreadId(){
    return ::GetThreadId(_hThread);
}

ExitThread

天下没有不散的宴席,这里介绍两个比较暴力的终止线程函数。ExitThread是第一个暴力分子,

  • ExitThread终止,相当于线程自杀!

后面,还有更可怕的哦。

/*
功能:终止线程(处于哪个线程空间,则终止谁)
描述:(不建议调用)
*/
void Win32Thread::exitThread(){
    if (_hThread){
        DWORD dwExitCode = getExitCodeThread();
        if (dwExitCode == STILL_ACTIVE && _hThread){
            ::ExitThread(dwExitCode);
        }
    }
}

TerminateThread

这位就是第二位暴力分子了,终极杀人王——火云邪神。

  • TerminateThread终止线程,相当于是谋杀!

也就是,TerminateThread它可以在线程A中,将线程B终结了!我脑海里又想起了英雄联盟中的shut down…..哈哈哈。

/*
功能:终止线程(在线程A中,终止线程B)
描述:(不建议调用)
*/
void Win32Thread::terminateThread(){
    if (_hThread){
        DWORD dwExitCode = getExitCodeThread();
        if (dwExitCode == STILL_ACTIVE && _hThread){
            ::TerminateThread(_hThread, dwExitCode);
        }
    }
}

GetExitCodeThread

虽然,不建议暴力终止Thread。但是,有时候就是不可避免。Windows为了尽可能的降低损失,精神上的和内存上的。她提供了一个检测线程当前状态的函数——GetExitCodeThread。

/*
功能:获取线程终止时状态
描述:状态通过GetExitCodeWin32Thread函数,第二个参数带回
*/
DWORD Win32Thread::getExitCodeThread(){
    if (_hThread){
        DWORD exitCode;
        ::GetExitCodeThread(_hThread, &exitCode);
        if (exitCode == STILL_ACTIVE){
            return exitCode;
        }
    }
    return -1L;
}

STILL_ACTIVE

借助上面这个哥们,顺带的把isAlive给捣鼓出来了…

/*
功能:检测线程是否存活
*/
bool Win32Thread::isAlive(){
    DWORD exitCode = getExitCodeThread();
    if (exitCode == STILL_ACTIVE){
        return true;
    }
    return false;
}

WaitForSingleObject

在《Windows并发&异步编程(0)创建、终止进程》一文中,提到了WaitForMultipleObjects,并没有继续讲述。这里对其做一个补充。

WaitForSingleObject(
    _In_ HANDLE hHandle,
    _In_ DWORD dwMilliseconds
    );

既然,讲到了WaitForSingleObject,那么SetEvent、CreateEvent以及上文中被注释了的notify、notifyAll,也是时候讲解一波了。

  • WaitForSingleObject 顾名思义,等待。那么要何时结束等待呢?有两个出口:
    • 单个HANDLE称为有标记状态;(线程运行完毕,自动被置为有标记状态)。
    • 等待超时。
CreateEvent(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,  //安全属性
    _In_ BOOL bManualReset,       //复位方式
    _In_ BOOL bInitialState,      //初始状态
    _In_opt_ LPCSTR lpName         
    );
  • CreateEvent 其实,WaitForSingleObject真正是用来等待CreatEvent事件,而不是半路子出身的HANDLE(CreateThread会返回一个HANDLE)。
    • bManualReset 复位方式。指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
    • bInitialState 初始状态。指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
  • SetEvent 主要是用来将Event(HANDLE)置为有标记状态,一旦WaitForSingleObject检测到,hHandle为有标记状态,将不再等待。这里需要注意,SetEvent有个返回值。

    • false 如果将CreateThread的返回值(也是一个HANDLE),作为SetEvent()参数,将始终返回false,也就是失败。
    • true 必须是正宗的CreateEvent的事件,才能用SetEvent来操作。
  • notify、notifyAll 有了上面的基础知识,notify和notifyAll也基本能实现了。但是这里需要维护一个list<Event>,或者说是list<HANDLE>

    • notify 通知单个为有标记状态,直接从list中取出一个HANDLE,然后SetEvent即可。
    • notifyAll 将所有list<HANDLE>置为有标记状态。
WaitForMultipleObjects(
    _In_ DWORD nCount,    // <= 64
    _In_reads_(nCount) CONST HANDLE *lpHandles,
    _In_ BOOL bWaitAll,   //true,等待所有HANDLE为有标记状态
    _In_ DWORD dwMilliseconds
    );

WaitForMultipleObjects

WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象。

  • WaitForMultipleObjects 与上面的区别是,这哥们它能等待多个内核对象。
    • CONST HANDLE *lpHandles,所以这个参数应该是一个类似HANDLE handle[MAXIMUM_WAIT_OBJECTS]的句柄数组。当然,这个多个内核对象个数是有最大限度的。nCount可以设置的最大值就是64,当然得根据参数2中,HANDLE数组的实际大小来设定。
#define MAXIMUM_WAIT_OBJECTS 64     // Maximum number of wait objects
  • bWaitAll 这是一个很重要的参数,可以设置为以下两种状态:
    • true 表示直到HANDLE array中所有的内核对象都成为有标记状态,才往下执行。
    • false 只要有其中一个内核对象成为有标记状态,就可以往下执行。
/*
功能:暂停线程n毫秒
*/
void Win32Thread::sleep(DWORD dwMilliseconds){
    if (dwMilliseconds > 0){
        Sleep(dwMilliseconds);
    }
}

/*
功能:将子线程并入主线程
描述:DWORD dwMilliseconds 为无限大,则相当于子线程执行完毕再执行主线程,这样的话就和直接过程调用无区别
参数:默认dwMilliseconds = INFINITE = -1,表示无限大时间
*/
void Win32Thread::join(DWORD dwMilliseconds){
    if (_hThread && (dwMilliseconds >= 0 || dwMilliseconds == INFINITE)){
        WaitForSingleObject(_hThread, dwMilliseconds);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值