Windows并发&异步编程(1)JAVA&多线程
2017-07-15 13:27
357 查看
本文在基于C/C++/Windows相关知识的基础上,初步封装一个像JAVA一样的多线程类–Win32Thread。使操作线程能像JAVA一样两步搞定:
继承基类Win32Thread,并覆盖其中的run方法;
定义线程类对象,调用start(),即可启动一个线程。
整个一套太极打下来,就应该是如下这样…
目录:
Windows Thread
virtual run
static threadProc
Win32Thread
CreateThread
start
ResumeThread
SuspendThread
SetThreadPriority
GetThreadPriority
GetThreadId
ExitThread
TerminateThread
GetExitCodeThread
STILL_ACTIVE
WaitForSingleObject
WaitForMultipleObjects
另外,既然是要实现仿JAVA,那么run就必须为:void run();
很忧伤,这并不符合Windows中线程执行体的规范,(偷偷告诉你,下面这个就是Windows规范)
为了补偿run,这里我就给他添加一个帮手“static threadProc”。
这下OK了,threadProc完美符合Windows老大的要求。如此,Thread已经达到了初步在CPU上奔跑要求了《奔跑吧,兄弟》… 一人得道鸡犬升天,我们的run也可以借助帮手,暗度陈仓。
一行“_thread->run()”,让我们的run兄弟终于见到了阳光。可是也是付出了不少代价,threadProc是静态的,没有this指针。这里run还得感谢全局的_thread变量老大哥的提携啊!
Music…
这里为何?将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而存在的,这里会有些许的参数类型不一致的问题,强行转换即可。
和上一篇文章《Windows并发&异步编程(0)创建、终止进程》中的CreateProcess老大哥一样,乍看一眼,参数还蛮子多的…
不要慌张,搞不清楚的参数,我们直接NULL或者0。Windows这人还是蛮好的,我们搞不清楚的,它就会给一个默认的值。正如,dwStackSize一样,它就会给俺一个4M大小的默认堆栈空间。
CREATE_SUSPENDED,这个参数很重要啊,如果没有这哥们。CreateThread之后,这线程就自个跑起来了。那还怎么装逼的仿照JAVA调用start()启动线程?
其他,无关紧要的小虾米。
ExitThread终止,相当于线程自杀!
后面,还有更可怕的哦。
TerminateThread终止线程,相当于是谋杀!
也就是,TerminateThread它可以在线程A中,将线程B终结了!我脑海里又想起了英雄联盟中的shut down…..哈哈哈。
既然,讲到了WaitForSingleObject,那么SetEvent、CreateEvent以及上文中被注释了的notify、notifyAll,也是时候讲解一波了。
WaitForSingleObject 顾名思义,等待。那么要何时结束等待呢?有两个出口:
单个HANDLE称为有标记状态;(线程运行完毕,自动被置为有标记状态)。
等待超时。
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也基本能实现了。但是这里需要维护一个
notify 通知单个为有标记状态,直接从list中取出一个HANDLE,然后SetEvent即可。
notifyAll 将所有
WaitForMultipleObjects 与上面的区别是,这哥们它能等待多个内核对象。
CONST HANDLE *lpHandles,所以这个参数应该是一个类似HANDLE handle[MAXIMUM_WAIT_OBJECTS]的句柄数组。当然,这个多个内核对象个数是有最大限度的。nCount可以设置的最大值就是64,当然得根据参数2中,HANDLE数组的实际大小来设定。
bWaitAll 这是一个很重要的参数,可以设置为以下两种状态:
true 表示直到HANDLE array中所有的内核对象都成为有标记状态,才往下执行。
false 只要有其中一个内核对象成为有标记状态,就可以往下执行。
继承基类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); } }
相关文章推荐
- Java多线程编程--(7)学习Java5.0 并发编程包--Lock & Condition
- java多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- 多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- Windows并发&异步编程(3)临界区Critical Section
- Windows并发&异步编程(0)创建、终止进程
- Java多线程编程--(8)学习Java5.0 并发编程包--线程池、Callable & Future 简介
- 多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- Thinking in Java——第二十一章-( 一)并发&Java中的多线程
- Windows并发&异步编程(2)原子操作Interlocked
- 多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- Java 并发 (多线程) 讲解<一>
- Java多线程和并发基础面试问答
- JAVA多线程和并发基础面试问答
- 【Java.Concurrency】多线程的代价 & Why Threads Are A Bad Idea (for most purposes)
- Java笔记3 多线程<1>线程概述、多线程的创建、多线程的安全问题、静态同步函数的锁、死锁
- JAVA多线程和并发基础面试问答
- 菜鸟之路——Java并发(一)多线程
- java多线程(对象和变量的并发访问)
- 黑马程序员—7、JAVA基础&多线程
- java 并发编程 多线程