Windows多线程程序设计之线程同步分析(结合事件对象)(上)
2013-06-25 19:04
555 查看
声明: 关于这几篇《Windows多线程程序设计之线程同步分析(结合xx对象)》的博文中的程序, 大家最好能在一台单核CPU中进行测试, 本人使用的双核CPU在测试这些程序的过程中出现了各种各样的不同的问题, 但并不影响我们对多线程程序实现原理的理解, 出现的问题, 由于本人能力有限, 并没能给出相关的解释, 所以也就没有将问题写出来, 希望日后我能够找出这些问题的原因, 此系列的文章仅供参考, 文中如有不对的地方请各位读者给以明示, Thank you!!!
先来看代码吧, 如下:
ThreadEvent1.cpp
分析:
本程序中我们创建了一个事件对象g_hEvent(代码中的第23行)以及两个线程(线程一ThreadProc1和线程二ThreadProc2), 然后在两个线程中分别调用WaitForSingleObject()函数去请求该事件对象(g_hEvent), 一旦某个线程请求到了该事件对象(g_hEvent), 则就可进入到该线程中受保护的代码段中, 但是我们看到程序并没有正常的执行, 这是为什么呢?? 我们再来分析代码, 注意到代码中创建事件对象的那一行代码(第23行代码),
我们将CreateEvent()函数的第二个参数设置为TRUE,而第三个参数设置为FALSE, 第二个参数为TRUE表示新创建的事件对象(g_hEvent)是人工重置, 第三个参数传递为FALSE, 表示此时创建的事件对象的状态是非信号态,这样, 在线程一和线程二中尽管我们调用WaitForSingleObject()函数去请求事件对象, 但是, 因为事件对象一直处于非信号态, 导致程序阻塞在请求事件对象的代码处, 线程一和线程二一直在请求着事件对象, 所以程序也就不能正常的执行下去。解决上面的问题其实很简单,
就是因为初始创建的事件对象的状态为非信号态, 所以, 线程一和线程二不能够请求到事件对象, 因此 我们可以重置事件对象的状态为信号态, 以让线程一和线程而能够正常的请求到事件对象。而刚才我们说到CreateEvent()函数的第二个参数为TRUE, 表示新创建的事件对象(g_hEvent)是人工重置, 而对于人工重置的事件对象, 我们要想改变其状态, 就必须通过调用SetEvent()函数, 将事件对象(g_hEvent)设置为信号态, 修改后的代码如下:
ThreadEvent2.cpp
可以看到程序打印出来的很乱, 并没能达到线程间同步的效果, 这是为什么呢?
分析:
还是来看代码中的第23行, 在对ThreadEvent1.cpp中代码进行分析时, 我们知道, 我们新创建的匿名事件对象(g_hEvent)为人工重置的事件对象, 在本代码中(ThreadEvent2.cpp), 我们通过调用函数SetEvent()将该事件对象(g_hEvent)设置为信号态, 而当人工重置的事件对象得到通知时(即变为信号态), 等待该事件对象的所有线程均变为可调度线程。即在本程序(ThreadEvent2.cpp)中线程一、线程二均变为可调度的线程,
两个线程同时运行, 那么, 对于线程一和线程二中受保护的代码就可以同时的被执行, 也就导致了上面commend命令行中输出的结果。线程一和线程二能够同时的运行起来, 表示了我们进行线程间同步失败。此时, 要想解决这个问题, 我们还是来分析代码以找到解决的方法, 通过分析代码, 有人可能会想, 既然通过手动将事件对象设置为信号态(即通过调用SetEvent()函数实现), 那么线程一和线程二都会去请求该事件对象(g_hEvent), 而对于单核CPU来说, 肯定只有一个线程请求到了该事件对象(g_hEvent),
那么当我们让请求事件对象的线程请求得到事件对象之后, 调用函数ResetEvent()将事件对象设置为非信号态, 这样如果第二个线程去请求事件对象, 便不能请求到, 达到了某个时间段内只有一个线程运行的效果, 在运行线程的受保护代码段执行完毕之后, 我们在调用函数SetEvent()将事件对象设置为信号态, 而处于等待状态的另一个线程便会立即请求得到事件对象, 同样的, 请求到之后, 调用ResetEvent()函数将事件对象设置为非信号态, 在受保护代码执行完成后, 调用SetEvent()函数将事件对象的状态改为信号态,
这样就达到了一个时间段内只有一个在运行的线程, 根据这种想法, 我们得到更改后的代码, 如下:
ThreadEvent3.cpp
效果并不是预期的那样, 这是为什么呢?来看看我们下面的分析;
分析:
我们都知道多线程程序的执行是按照CPU的时间片来划分的, 如果一个线程分的的时间片完了, 操作系统就会自动切换到另一个线程中执行, 同理, 我们来分析上面的代码(ThreadEvent3.cpp), 当程序执行到线程一的WaitForSingleObject()函数并且执行完了该行代码(代码中的36行), 即线程一已经请求到了事件对象(g_hEvent),此时, 它(线程一)的CPU时间片到了, 操作系统就切换到线程二中去执行, 而此时线程一中用于设置事件对象(g_hEvent)到非信号态的ResetEvent()函数并没有执行,
事件对象(g_hEvent)还是处于信号态, 所以, 线程二就会顺顺利利的请求到事件对象(g_hEvent), 此时的程序中, 线程一和线程二都请求到了事件对象(g_hEvent), 我们原本的一个时间段只能有一个线程拥有事件对象便失败了, 这也就造成了程序数据如图中所示的结果。
所以对于线程同步我们不能通过创建手工重置的事件对象来实现, 我们必须的创建自动重置的线程来实现;
更改后的代码如下:
ThreadEvent4.cpp
看到这种结果, 我们肯定会说why?我也会这么问的,来我们看看分析,如下:
分析:
当一个自动重置的事件对象得到通知时(变为信号态), 等待该事件对象的线程中只有一个线程变为可调度线程。(我的计算机是双核的, 可能输出的结果和单核的不太一样, 在单核CPU中, 此处只能有一个线程被执行, 至于双核的CPU执行的结果为什么是这样的, 大家多想一想也应该能想明白, 但是对于最后的线程同步, 我们肯定能保证, 一个时刻只有一个线程在运行) 在本程序(ThreadEvent4.cpp)中, 程序执行后肯定会有其中的一个线程被调度, 不管那个被调度, 在执行线程时, 都会调用WaitForSingleObject()函数去请求事件对象(g_hEvent),
当请求到事件对象(g_hEvent)后, 操作系统就会自动将事件对象设置为非信号态(因为是自动重置嘛), 当执行着的线程执行到Sleep()函数时, 运行线程休眠, 此时, 操作系统会切换到另一线程开始执行, 另一个线程在执行过程中也会请求事件对象(g_hEvent), 但是, 事件对象(g_hEvent)一直是处于非信号态的, 所以, 请求不到事件对象, 只能暂停下来等待, 而休眠中的线程在休眠结束后继续执行其线程代码, 打印出相关信息后继续开始下一轮的循环, 在下一轮的循环中, 该线程(处于运行中的线程)还会调用WaitForSingleObject()函数去请求事件对象(g_hEvent),
因为事件对象(g_hEvent)处于非信号态, 导致该线程最终请求不到事件对象(g_hEvent), 这样, 结果, 两个线程都处于等待状态, 主线程(main())继续执行, 程序结束。
这样还是没有达到线程同步的要求啊! 那么怎么实现线程同步呢?更改后的代码如下所示:
ThreadEvent5.cpp
这个就不给分析了, 自己结合上一个程序和分析, 应该是可以想明白的!!!
先来看代码吧, 如下:
ThreadEvent1.cpp
#include <windows.h> #include <iostream> using namespace std ; DWORD WINAPI ThreadProc1(LPVOID lpParameter) ; DWORD WINAPI ThreadProc2(LPVOID lpParameter) ; int tickets = 100 ; HANDLE g_hEvent ; int main() { HANDLE hThread1 ; HANDLE hThread2 ; hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ; hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ; CloseHandle(hThread1) ; CloseHandle(hThread2) ; g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL) ; // 创建一个匿名的手动重置的事件对象, 并且将初始化状态设置为非信号态 Sleep(4000) ; return 0 ; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread two sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; }先来看看程序的执行结果, 如下:
分析:
本程序中我们创建了一个事件对象g_hEvent(代码中的第23行)以及两个线程(线程一ThreadProc1和线程二ThreadProc2), 然后在两个线程中分别调用WaitForSingleObject()函数去请求该事件对象(g_hEvent), 一旦某个线程请求到了该事件对象(g_hEvent), 则就可进入到该线程中受保护的代码段中, 但是我们看到程序并没有正常的执行, 这是为什么呢?? 我们再来分析代码, 注意到代码中创建事件对象的那一行代码(第23行代码),
我们将CreateEvent()函数的第二个参数设置为TRUE,而第三个参数设置为FALSE, 第二个参数为TRUE表示新创建的事件对象(g_hEvent)是人工重置, 第三个参数传递为FALSE, 表示此时创建的事件对象的状态是非信号态,这样, 在线程一和线程二中尽管我们调用WaitForSingleObject()函数去请求事件对象, 但是, 因为事件对象一直处于非信号态, 导致程序阻塞在请求事件对象的代码处, 线程一和线程二一直在请求着事件对象, 所以程序也就不能正常的执行下去。解决上面的问题其实很简单,
就是因为初始创建的事件对象的状态为非信号态, 所以, 线程一和线程二不能够请求到事件对象, 因此 我们可以重置事件对象的状态为信号态, 以让线程一和线程而能够正常的请求到事件对象。而刚才我们说到CreateEvent()函数的第二个参数为TRUE, 表示新创建的事件对象(g_hEvent)是人工重置, 而对于人工重置的事件对象, 我们要想改变其状态, 就必须通过调用SetEvent()函数, 将事件对象(g_hEvent)设置为信号态, 修改后的代码如下:
ThreadEvent2.cpp
#include <windows.h> #include <iostream> using namespace std ; DWORD WINAPI ThreadProc1(LPVOID lpParameter) ; DWORD WINAPI ThreadProc2(LPVOID lpParameter) ; int tickets = 100 ; HANDLE g_hEvent ; int main() { HANDLE hThread1 ; HANDLE hThread2 ; hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ; hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ; CloseHandle(hThread1) ; CloseHandle(hThread2) ; g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL) ; // 创建一个匿名的手动重置的事件对象, 并且将初始化状态设置为非信号态 SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 Sleep(4000) ; return 0 ; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread two sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; }注意到代码中的第25行, 我们调用SetEvent()函数将刚刚创建的匿名事件对象(g_hEvent)设置为了信号态度。现在再来看看程序运行的结果, 如下所示:
可以看到程序打印出来的很乱, 并没能达到线程间同步的效果, 这是为什么呢?
分析:
还是来看代码中的第23行, 在对ThreadEvent1.cpp中代码进行分析时, 我们知道, 我们新创建的匿名事件对象(g_hEvent)为人工重置的事件对象, 在本代码中(ThreadEvent2.cpp), 我们通过调用函数SetEvent()将该事件对象(g_hEvent)设置为信号态, 而当人工重置的事件对象得到通知时(即变为信号态), 等待该事件对象的所有线程均变为可调度线程。即在本程序(ThreadEvent2.cpp)中线程一、线程二均变为可调度的线程,
两个线程同时运行, 那么, 对于线程一和线程二中受保护的代码就可以同时的被执行, 也就导致了上面commend命令行中输出的结果。线程一和线程二能够同时的运行起来, 表示了我们进行线程间同步失败。此时, 要想解决这个问题, 我们还是来分析代码以找到解决的方法, 通过分析代码, 有人可能会想, 既然通过手动将事件对象设置为信号态(即通过调用SetEvent()函数实现), 那么线程一和线程二都会去请求该事件对象(g_hEvent), 而对于单核CPU来说, 肯定只有一个线程请求到了该事件对象(g_hEvent),
那么当我们让请求事件对象的线程请求得到事件对象之后, 调用函数ResetEvent()将事件对象设置为非信号态, 这样如果第二个线程去请求事件对象, 便不能请求到, 达到了某个时间段内只有一个线程运行的效果, 在运行线程的受保护代码段执行完毕之后, 我们在调用函数SetEvent()将事件对象设置为信号态, 而处于等待状态的另一个线程便会立即请求得到事件对象, 同样的, 请求到之后, 调用ResetEvent()函数将事件对象设置为非信号态, 在受保护代码执行完成后, 调用SetEvent()函数将事件对象的状态改为信号态,
这样就达到了一个时间段内只有一个在运行的线程, 根据这种想法, 我们得到更改后的代码, 如下:
ThreadEvent3.cpp
#include <windows.h> #include <iostream> using namespace std ; DWORD WINAPI ThreadProc1(LPVOID lpParameter) ; DWORD WINAPI ThreadProc2(LPVOID lpParameter) ; int tickets = 100 ; HANDLE g_hEvent ; int main() { HANDLE hThread1 ; HANDLE hThread2 ; hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ; hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ; CloseHandle(hThread1) ; CloseHandle(hThread2) ; g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL) ; // 创建一个匿名的手动重置的事件对象, 并且将初始化状态设置为非信号态 SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 Sleep(4000) ; return 0 ; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; ResetEvent(g_hEvent) ; // 设置事件对象的状态为非信号态 if (tickets > 0) { Sleep(1) ; cout << "thread sell ticket: " << tickets-- << endl ; } else { break ; } SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 } return 0 ; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; ResetEvent(g_hEvent) ; // 设置事件对象的状态为非信号态 if (tickets > 0) { Sleep(1) ; cout << "thread two sell ticket: " << tickets-- << endl ; } else { break ; } SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 } return 0 ; }注意到我们在线程一和线程二中加入了相关的代码(线程一中的第38和50行, 线程二中的62和74行), 现在我们在来看看程序的执行效果, 如下:
效果并不是预期的那样, 这是为什么呢?来看看我们下面的分析;
分析:
我们都知道多线程程序的执行是按照CPU的时间片来划分的, 如果一个线程分的的时间片完了, 操作系统就会自动切换到另一个线程中执行, 同理, 我们来分析上面的代码(ThreadEvent3.cpp), 当程序执行到线程一的WaitForSingleObject()函数并且执行完了该行代码(代码中的36行), 即线程一已经请求到了事件对象(g_hEvent),此时, 它(线程一)的CPU时间片到了, 操作系统就切换到线程二中去执行, 而此时线程一中用于设置事件对象(g_hEvent)到非信号态的ResetEvent()函数并没有执行,
事件对象(g_hEvent)还是处于信号态, 所以, 线程二就会顺顺利利的请求到事件对象(g_hEvent), 此时的程序中, 线程一和线程二都请求到了事件对象(g_hEvent), 我们原本的一个时间段只能有一个线程拥有事件对象便失败了, 这也就造成了程序数据如图中所示的结果。
所以对于线程同步我们不能通过创建手工重置的事件对象来实现, 我们必须的创建自动重置的线程来实现;
更改后的代码如下:
ThreadEvent4.cpp
#include <windows.h> #include <iostream> using namespace std ; DWORD WINAPI ThreadProc1(LPVOID lpParameter) ; DWORD WINAPI ThreadProc2(LPVOID lpParameter) ; int tickets = 100 ; HANDLE g_hEvent ; int main() { HANDLE hThread1 ; HANDLE hThread2 ; hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ; hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ; CloseHandle(hThread1) ; CloseHandle(hThread2) ; g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL) ; // 创建一个匿名的自动重置的事件对象, 并且将初始化状态设置为非信号态 SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 Sleep(4000) ; return 0 ; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread two sell ticket: " << tickets-- << endl ; } else { break ; } } return 0 ; }运行程序后, 程序的执行结果如下:
看到这种结果, 我们肯定会说why?我也会这么问的,来我们看看分析,如下:
分析:
当一个自动重置的事件对象得到通知时(变为信号态), 等待该事件对象的线程中只有一个线程变为可调度线程。(我的计算机是双核的, 可能输出的结果和单核的不太一样, 在单核CPU中, 此处只能有一个线程被执行, 至于双核的CPU执行的结果为什么是这样的, 大家多想一想也应该能想明白, 但是对于最后的线程同步, 我们肯定能保证, 一个时刻只有一个线程在运行) 在本程序(ThreadEvent4.cpp)中, 程序执行后肯定会有其中的一个线程被调度, 不管那个被调度, 在执行线程时, 都会调用WaitForSingleObject()函数去请求事件对象(g_hEvent),
当请求到事件对象(g_hEvent)后, 操作系统就会自动将事件对象设置为非信号态(因为是自动重置嘛), 当执行着的线程执行到Sleep()函数时, 运行线程休眠, 此时, 操作系统会切换到另一线程开始执行, 另一个线程在执行过程中也会请求事件对象(g_hEvent), 但是, 事件对象(g_hEvent)一直是处于非信号态的, 所以, 请求不到事件对象, 只能暂停下来等待, 而休眠中的线程在休眠结束后继续执行其线程代码, 打印出相关信息后继续开始下一轮的循环, 在下一轮的循环中, 该线程(处于运行中的线程)还会调用WaitForSingleObject()函数去请求事件对象(g_hEvent),
因为事件对象(g_hEvent)处于非信号态, 导致该线程最终请求不到事件对象(g_hEvent), 这样, 结果, 两个线程都处于等待状态, 主线程(main())继续执行, 程序结束。
这样还是没有达到线程同步的要求啊! 那么怎么实现线程同步呢?更改后的代码如下所示:
ThreadEvent5.cpp
#include <windows.h> #include <iostream> using namespace std ; DWORD WINAPI ThreadProc1(LPVOID lpParameter) ; DWORD WINAPI ThreadProc2(LPVOID lpParameter) ; int tickets = 100 ; HANDLE g_hEvent ; int main() { HANDLE hThread1 ; HANDLE hThread2 ; hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL) ; hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL) ; CloseHandle(hThread1) ; CloseHandle(hThread2) ; g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL) ; // 创建一个匿名的自动重置的事件对象, 并且将初始化状态设置为非信号态 SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 Sleep(4000) ; CloseHandle(g_hEvent) ; return 0 ; } DWORD WINAPI ThreadProc1(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread one sell ticket: " << tickets-- << endl ; } else { break ; } SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 } return 0 ; } DWORD WINAPI ThreadProc2(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent, INFINITE) ; if (tickets > 0) { Sleep(1) ; cout << "thread two sell ticket: " << tickets-- << endl ; } else { break ; } SetEvent(g_hEvent) ; // 设置事件对象的状态为信号态 } return 0 ; }运行结果如下:
这个就不给分析了, 自己结合上一个程序和分析, 应该是可以想明白的!!!
相关文章推荐
- Windows多线程程序设计之线程同步分析(结合互斥对象)(中)
- Windows多线程程序设计之线程同步分析(结合互斥对象)(下)
- Windows多线程程序设计之线程同步分析(结合事件对象)(下)
- Windows多线程程序设计之线程同步分析(结合临界区(关键代码段))
- Windows-核心编程-09-如何用内核对象进行线程同步-事件内核对象
- 《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“事件内核对象”
- Windows多线程程序设计之线程同步分析(结合互斥对象)(上)
- 线程同步之事件对象(类比互斥对象进行分析)
- Windows Via C/C++:内核模式下的线程同步——事件内核对象
- 多线程编程Demo[利用事件对象实现线程同步]
- windows笔记-【内核对象线程同步】事件内核对象
- windows笔记-【内核对象线程同步】事件内核对象
- 多线程实现线程同步——事件对象
- 多线程火车票售票系统——人工重置事件对象实现线程同步(会有问题的)
- 多线程火车票售票系统——自动重置事件对象实现线程同步
- Windows多线程总结(4)-- 线程同步(使用互斥对象实现线程同步 只运行一个对象)
- windows笔记-【内核对象线程同步】事件内核对象
- windows多线程系列003_利用事件对象实现线程同步
- 面向对象程序设计与分析--ATM机系统
- windows笔记-【内核对象线程同步】线程同步对象速查表