本文在基于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

    • 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并发异步编程(1)JAVA多线程相关推荐

  1. 【Java并发编程】Java多线程(四):FutureTask 源码分析

    前言:[Java并发编程]Java多线程(三):Runnable.Callable --创建任务的方式 在上一篇文章的末尾我们通过两个问题,引出了 FutureTask 及其设计思路,先来回顾一下: ...

  2. 逆向爬虫08 并发异步编程

    逆向爬虫08 并发异步编程 我将引用我最喜欢的路飞学城林海峰Egon老师的讲课方式来复习这一节的内容. 1. 什么是并发异步编程? 这一部分的内容来源于计算机操作系统,如果想要详细了解并发异步等概念, ...

  3. java 如何只暴露接口_Java并发异步编程,原来十个接口的活现在只需要一个接口就搞定...

    什么?对你没有听错,也没有看错 ..多线程并发执行任务,取结果归集~~ 不再忧愁-. 引言 先来看一些APP的获取数据,诸如此类,一个页面获取N多个,多达10个左右的一个用户行为数据,比如:点赞数,发 ...

  4. Java并发异步编程,原来十个接口的活现在只需要一个接口就搞定!

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:锦成同学 juejin.im/post/5d3c46d2f265da1b9163db ...

  5. Java 并发异步编程,原来十个接口的活现在只需要一个接口就搞定!

    引言 多线程并发执行任务,取结果归集 状态 队列 CAS操作 实战演练 总结 小甜点 什么?对你没有听错,也没有看错 ..多线程并发执行任务,取结果归集~~ 不再忧愁....感谢大家的双击+点赞和关注 ...

  6. 实例解析C++多线程并发---异步编程

    线程同步主要是为了解决对共享数据的竞争访问问题,所以线程同步主要是对共享数据的访问同步化(按照既定的先后次序,一个访问需要阻塞等待前一个访问完成后才能开始).这篇文章谈到的异步编程主要是针对任务或线程 ...

  7. java 编程思想 并发_java编程思想-java中的并发(一)

    一.基本的线程机制 并发编程使我们可以将程序划分为多个分离的.独立运行的任务.通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动. 线程模型为编程带来了便利,它简化了在单一程序中同时jia ...

  8. libevent c++高并发网络编程_【多线程高并发编程】Callable源码分析

    程序猿学社的GitHub,欢迎Starhttps://github.com/ITfqyd/cxyxs 本文已记录到github,形成对应专题. 前言 通过上一章实现多线程有几种方式,我们已经了解多线程 ...

  9. python3异步task_并发,异步编程_Python中的asyncio模块中的Future和Task的区别?,并发,异步编程,python,asyncio - phpStudy...

    Python中的asyncio模块中的Future和Task的区别? 问题一 按照官方文档的描述,Task是Futrue的一个subclass,标准库中也分别提供了create_task和create ...

最新文章

  1. safari post 请求接收不到_我是谁?我在哪?我要到哪去?——HTTP请求头
  2. 001-supervisor
  3. Jeson nano + 思岚激光雷达rplidar_s1 + ubuntu18.04
  4. 前端学习(1115):call apply bind的区别
  5. DevExpress控件使用小结
  6. Commons-Collections4 集合工具类的使用(一):集合操作
  7. Windows:Win10 Dell笔记本禁用触摸板
  8. python识别图片上的文字_Python程序图片和pdf上文字识别实例
  9. 如何系统地自学 Python?
  10. STM32F4最小系统硬件设计
  11. EMC Isilon存储数据恢复成功案例
  12. html设置页面宽度高度,a4纸宽度_A4纸网页打印 html网页页面的宽度设置成多少 _a4纸宽度高度...
  13. selenium+python抓取微博时遇到“展开全文”
  14. 仿网易新闻顶部菜单html,iOS仿网易新闻滚动导航条效果
  15. Watchguard Firebox 配置DKEY动态口令认证
  16. HashMap 的底层原理
  17. 《结构动力分析的MATLAB实现》,结构动力分析的MATLAB实现
  18. spongycastle加密算法
  19. 计算机系统字体安装程序,windows系统字体安装方法:使用字体安装软件-windows技巧-电脑技巧收藏家...
  20. java计算机毕业设计汽车售后服务管理系统源码+程序+lw文档+mysql数据库

热门文章

  1. 浙江大学的计算机考研难度,浙江大学计算机考研难度解析
  2. 你有张良计,我有过墙梯之策略模式
  3. 中计播客 | 戊戌年的第一篇播客,价值6000亿~
  4. 解决微擎框架在升级中,模块从已安装列表变成了未安装列表(模块显示到了未安装列表)
  5. 使用最小二乘法拟合二次函数
  6. C语言中字符串定义与文字常量区
  7. AXI EMC使用总结
  8. (抄送列表,年会抽奖)笔试强训
  9. 高清视频4G传输,串口通信,485通信,AP直连
  10. SCI论文及期刊查询