您的位置:首页 > 编程语言 > Java开发

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

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

继承基类Win32Thread,并覆盖其中的run方法;

定义线程类对象,调用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
virtual run

static threadProc

Win32Thread
CreateThread

start

ResumeThread

SuspendThread

SetThreadPriority

GetThreadPriority

GetThreadId

ExitThread

TerminateThread

GetExitCodeThread

STILL_ACTIVE

WaitForSingleObject

WaitForMultipleObjects

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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows java 线程