引子:有这样一个程序需求,做一个文本编辑器,读取文件内容后能够进行文字拼写检查,语法检查,字数统计等工作。设计要点:①必须等到读取文件内容完全之后才能进行之后的操作②为了提高程序效率,拼写检查,语法检查,字数统计等工作最好一起进行,或者根据需求每个任务单独进行。
看到上面的需要,第一眼我们的反应是用多线程可以解决,针对该程序,利用事件内核对象将比较容易的实现。下面给出代码:
#include "stdafx.h" #include <Windows.h> #include <process.h> HANDLE g_hEvent = NULL ; int s_n1 = 20 ; int s_n2 = 20 ; int s_n3 = 20 ; DWORD WINAPI WordCount(LPVOID lp) ; DWORD WINAPI SpellCheck(LPVOID lp) ; DWORD WINAPI GrammarCheck(LPVOID lp) ; void OpenFileAndReadContentsIntoMemory() { Sleep(3000) ; printf("Ready Go!!!\n") ; } int _tmain(int argc, _TCHAR* argv[]) { g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL) ; ResetEvent(g_hEvent) ; HANDLE hThrd[3] ; DWORD dwThreadId[3] ; hThrd[0] = CreateThread(NULL, 0, WordCount, NULL, 0, &dwThreadId[0]) ; hThrd[1] = CreateThread(NULL, 0, SpellCheck, NULL, 0, &dwThreadId[1]) ; hThrd[2] = CreateThread(NULL, 0, GrammarCheck, NULL, 0, &dwThreadId[2]) ; OpenFileAndReadContentsIntoMemory() ; SetEvent(g_hEvent) ; while(TRUE) { //必须有这段代码,否则主线程退出了整个程序就退出了,后面的过程就无法实现了 } return 0; } DWORD WINAPI WordCount(LPVOID lp) { WaitForSingleObject(g_hEvent, INFINITE) ; while(s_n1-- > 0){ Sleep(1000) ; printf("WordCouting %d...\n", s_n1) ; } return 0 ; } DWORD WINAPI SpellCheck(LPVOID lp) { WaitForSingleObject(g_hEvent, INFINITE) ; while(s_n2-- > 0){ Sleep(1000) ; printf("SpellChecking %d...\n", s_n2) ; } return 0 ; } DWORD WINAPI GrammarCheck(LPVOID lp) { WaitForSingleObject(g_hEvent, INFINITE) ; while(s_n3-- > 0){ Sleep(1000) ; printf("GrammarChecking %d...\n", s_n3) ; } return 0 ; }
下面进行分析:
如何做到内容读取完全之后才开始后面的任务操作?
我们在程序的开始创建WordCount、SpellCheck、GrammarCheck线程之后,他们都各自跑了起来,难道不用担心
OpenFileAndReadContentsIntoMemory()函数没有执行完吗,如果该函数没有执行完,那么后面读取的内容就不完整,程序出现bug了!!!
不用担心,由于我们创建事件内核对象时,CreateEvent的第三个参数表明他的初始化状态是没有触发的,请看每个任务线程函数的第一行代码:WaitForSingleObject(g_hEvent, INFINITE) 。该函数一直会等待g_hEvent变为触发状态,否则该线程阻塞,不会往下执行。您看,
OpenFileAndReadContentsIntoMemory()函数执行完之后,调用了SetEvent(g_hEvent) ,该函数的目的就是使g_hEvent变为触发状态,
那么各个任务线程就开始工作了。是不是比较清晰呢
我们再来讨论第二个问题:由于某个用户的机器配置太烂,WordCount、SpellCheck、GrammarCheck三个任务一起执行,
我们的程序看起来比较卡,老板要你改方案。是不是要做很多复杂的操作呢?
不用!时间内核对象的好处就体现在这里了,容我慢慢到来。
有没有注意CreateEvent第二个参数,他是什么意思呢?为TRUE表明创建的是人工重置事件对象,为False则是自动重置事件对象。嘛?人工?自动?都是一些啥啊?
哈哈,自动重置事件对象就是当成功地调用WaitForSingleObject()后,g_hEvent会自动重置为非触发状态。
那么WordCount、SpellCheck、GrammarCheck中某一个线程成功地调用WaitForSingleObject()后,
g_hEvent会自动重置为非触发状态,其他的两个线程只能等待他孤单的执行了,~~~~(>_<)~~~~
慢着,这样程序就能单独的执行每个线程啦?聪明的朋友一定会说,既然g_hEvent都是非触发状态了,
后面等待的线程肯定没有机会执行,他们都饿死了。是的,的确是这样。您也一定也知道了在哪里改写代码了吧。对,就是在每个线程执行完之后再调用SetEvent。