博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【代码】Visual C++线程同步技术剖析:临界区,事件,信号量,互斥量
阅读量:2360 次
发布时间:2019-05-10

本文共 9978 字,大约阅读时间需要 33 分钟。

 

编译程序前设置:Project-->Settings-->Use MFC as a static DLL

第一部分CriticalSection

1.1、临界区结构对象CRITICAL_SECTION

  1. /*
  2. 与事件相比,临界区更适合于多个线程操作之间没有先后顺序但要求互斥的同步。
  3. 在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,
  4. 其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。
  5. 程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。
  6. 如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。
  7. 换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,
  8. 必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保 
  9. LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,
  10. 而不可用来同步多个进程中的线程。
  11. */
  12. #include   <afxwin.h> /*Project-->Settings-->Use MFC as a static DLL*/
  13. // 临界区结构对象
  14. CRITICAL_SECTION g_cs;
  15. // 共享资源
  16. char g_cArray[10];
  17. UINT ThreadProc10(LPVOID pParam)
  18. {
  19.     // 进入临界区
  20.     EnterCriticalSection(&g_cs);
  21.     // 对共享资源进行写入操作
  22.     for (int i = 0; i < 10; i++)
  23.     {
  24.         g_cArray[i] = 'a';
  25.         Sleep(1);
  26.     }
  27.     // 离开临界区
  28.     LeaveCriticalSection(&g_cs);
  29.     return 0;
  30. }
  31. UINT ThreadProc11(LPVOID pParam)
  32. {
  33.     // 进入临界区
  34.     EnterCriticalSection(&g_cs);
  35.     // 对共享资源进行写入操作
  36.     for (int i = 0; i < 10; i++)
  37.     {
  38.         g_cArray[10 - i - 1] = 'b';
  39.         Sleep(1);
  40.     }
  41.     // 离开临界区
  42.     LeaveCriticalSection(&g_cs);
  43.     return 0;
  44. }
  45. int main()
  46. {
  47.     //初始化临界区
  48.     InitializeCriticalSection(&g_cs);
  49.     //启动线程
  50.     AfxBeginThread(ThreadProc10, NULL);
  51.     AfxBeginThread(ThreadProc11, NULL);
  52.     //等待计算完毕
  53.     Sleep(300);
  54.     //报告计算结果
  55.     CString sResult = CString(g_cArray);
  56.     AfxMessageBox(sResult);
  57.     return 0;
  58. }

1.2、MFC临界区CCriticalSection类

  1. /*
  2. MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,
  3. 只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。
  4. 对于上述代码,可通过 CCriticalSection类将其改写如下:
  5. */
  6. #include   <afxwin.h> /*Project-->Settings-->Use MFC as a static DLL*/
  7. #include   <afxmt.h> /*用CSyncObject,CMutex,CSemaphore,CEvent,CCriticalSection这些类需要包含头文件 */
  8. // MFC临界区类对象
  9. CCriticalSection g_clsCriticalSection;
  10. // 共享资源
  11. char g_cArray[10];
  12. UINT ThreadProc10(LPVOID pParam)
  13. {
  14.     // 进入临界区
  15.     g_clsCriticalSection.Lock();
  16.     // 对共享资源进行写入操作
  17.     for (int i = 0; i < 10; i++)
  18.     {
  19.         g_cArray[i] = 'a';
  20.         Sleep(1);
  21.     }
  22.     // 离开临界区
  23.     g_clsCriticalSection.Unlock();
  24.     return 0;
  25. }
  26. UINT ThreadProc11(LPVOID pParam)
  27. {
  28.     // 进入临界区
  29.     g_clsCriticalSection.Lock();
  30.     // 对共享资源进行写入操作
  31.     for (int i = 0; i < 10; i++)
  32.     {
  33.         g_cArray[10 - i - 1] = 'b';
  34.         Sleep(1);
  35.     }
  36.     // 离开临界区
  37.     g_clsCriticalSection.Unlock();
  38.     return 0;
  39. }
  40. int main()
  41. {
  42.     //启动线程
  43.     AfxBeginThread(ThreadProc10, NULL);
  44.     AfxBeginThread(ThreadProc11, NULL);
  45.     //等待计算完毕
  46.     Sleep(300);
  47.     //报告计算结果
  48.     CString sResult = CString(g_cArray);
  49.     AfxMessageBox(sResult);
  50.     return 0;
  51. }

第二部分Event

2.1、等待单个事件

  1. /*
  2. 使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,
  3. 其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:
  4.   HANDLE OpenEvent(
  5.    DWORD dwDesiredAccess, // 访问标志
  6.    BOOL bInheritHandle, // 继承标志
  7.    LPCTSTR lpName // 指向事件对象名的指针
  8. );
  9. */
  10. #include <afxwin.h>
  11. #include <afxmt.h>
  12. // 事件句柄
  13. HANDLE hEvent = NULL;
  14. // 共享资源
  15. char g_cArray[10];
  16. UINT ThreadProc12(LPVOID pParam)
  17. {
  18.     // 等待事件置位
  19.     WaitForSingleObject(hEvent, INFINITE);
  20.     // 对共享资源进行写入操作
  21.     for (int i = 0; i < 10; i++)
  22.     {
  23.         g_cArray[i] = 'a';
  24.         Sleep(1);
  25.     }
  26.     // 处理完成后即将事件对象置位
  27.     SetEvent(hEvent);
  28.     return 0;
  29. }
  30. UINT ThreadProc13(LPVOID pParam)
  31. {
  32.     // 等待事件置位
  33.     WaitForSingleObject(hEvent, INFINITE);
  34.     // 对共享资源进行写入操作
  35.     for (int i = 0; i < 10; i++)
  36.     {
  37.         g_cArray[10 - i - 1] = 'b';
  38.         Sleep(1);
  39.     }
  40.     // 处理完成后即将事件对象置位
  41.     SetEvent(hEvent);
  42.     return 0;
  43. }
  44. int main()
  45. {
  46.     // 创建事件
  47.     hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  48.     // 事件置位
  49.     SetEvent(hEvent);
  50.     // 启动线程
  51.     AfxBeginThread(ThreadProc12, NULL);
  52.     AfxBeginThread(ThreadProc13, NULL);
  53.     // 等待计算完毕
  54.     Sleep(300);
  55.     // 报告计算结果
  56.     CString sResult = CString(g_cArray);
  57.     AfxMessageBox(sResult);
  58.     return 0;
  59. }

2.2、等待多个事件

  1. #include <afxwin.h>
  2. #include <afxmt.h>
  3. // 存放事件句柄的数组
  4. HANDLE hEvents[2];
  5. UINT ThreadProc14(LPVOID pParam)
  6. {
  7.     // 等待开启事件
  8.     DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
  9.     // 如果开启事件到达则线程开始执行任务
  10.     if (dwRet1 == WAIT_OBJECT_0)
  11.     {
  12.         AfxMessageBox("线程开始工作!");
  13.         while (true)
  14.         {
  15.             for (int i = 0; i < 10000; i++);
  16.             // 在任务处理过程中等待结束事件
  17.             DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
  18.             // 如果结束事件置位则立即终止任务的执行
  19.             if (dwRet2 == WAIT_OBJECT_0 + 1)
  20.                 break;
  21.         }
  22.     }
  23.     AfxMessageBox("线程退出!");
  24.     return 0;
  25. }
  26. void StartEvent()
  27. {
  28.     // 创建线程
  29.     for (int i = 0; i < 2; i++)
  30.         hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
  31.     // 开启线程
  32.     AfxBeginThread(ThreadProc14, NULL);
  33.     // 设置事件0(开启事件)
  34.     SetEvent(hEvents[0]);
  35. }
  36. void Endevent()
  37. {
  38.     // 设置事件1(结束事件)
  39.     SetEvent(hEvents[1]);
  40. }
  41. int main()
  42. {
  43.     StartEvent();
  44.     Sleep(1000);
  45.     Endevent();
  46.     Sleep(1000);
  47.     return 0;
  48. }

 

2.3、CEvent

MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、 ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原 CreateEvent()函数创建事件对象的职责,其函数原型为:

CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

  1. #include <afxwin.h>
  2. #include <afxmt.h>
  3. // 共享资源
  4. char g_cArray[10];
  5. // MFC事件类对象
  6. CEvent g_clsEvent;
  7. UINT ThreadProc22(LPVOID pParam)
  8. {
  9.     // 对共享资源进行写入操作
  10.     for (int i = 0; i < 10; i++)
  11.     {
  12.         g_cArray[i] = 'a';
  13.         Sleep(1);
  14.     }
  15.     // 事件置位
  16.     g_clsEvent.SetEvent();
  17.     return 0;
  18. }
  19. UINT ThreadProc23(LPVOID pParam)
  20. {
  21.     // 等待事件
  22.     g_clsEvent.Lock();
  23.     // 对共享资源进行写入操作
  24.     for (int i = 0; i < 10; i++)
  25.     {
  26.         g_cArray[10 - i - 1] = 'b';
  27.         Sleep(1);
  28.     }
  29.     return 0;
  30. }
  31. int main()
  32. {
  33.     // 启动线程
  34.     AfxBeginThread(ThreadProc22, NULL);
  35.     AfxBeginThread(ThreadProc23, NULL);
  36.     // 等待计算完毕
  37.     Sleep(300);
  38.     // 报告计算结果
  39.     CString sResult = CString(g_cArray);
  40.     AfxMessageBox(sResult);
  41.     return 0;
  42. }

第三部分Semaphore

3.1、信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
  1. #include <afxwin.h>
  2. // 信号量对象句柄
  3. HANDLE hSemaphore;
  4. UINT ThreadProc15(LPVOID pParam)
  5. {
  6.     // 试图进入信号量关口
  7.     WaitForSingleObject(hSemaphore, INFINITE);
  8.     // 线程任务处理
  9.     AfxMessageBox("线程一正在执行!");
  10.     // 释放信号量计数
  11.     ReleaseSemaphore(hSemaphore, 1, NULL);
  12.     return 0;
  13. }
  14. UINT ThreadProc16(LPVOID pParam)
  15. {
  16.     // 试图进入信号量关口
  17.     WaitForSingleObject(hSemaphore, INFINITE);
  18.     // 线程任务处理
  19.     AfxMessageBox("线程二正在执行!");
  20.     // 释放信号量计数
  21.     ReleaseSemaphore(hSemaphore, 1, NULL);
  22.     return 0;
  23. }
  24. UINT ThreadProc17(LPVOID pParam)
  25. {
  26.     // 试图进入信号量关口
  27.     WaitForSingleObject(hSemaphore, INFINITE);
  28.     // 线程任务处理
  29.     AfxMessageBox("线程三正在执行!");
  30.     // 释放信号量计数
  31.     ReleaseSemaphore(hSemaphore, 1, NULL);
  32.     return 0;
  33. }
  34. int main()
  35. {
  36.     // 创建信号量对象
  37.     hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
  38.     // 开启线程
  39.     AfxBeginThread(ThreadProc15, NULL);
  40.     Sleep(1000);
  41.     AfxBeginThread(ThreadProc16, NULL);
  42.     Sleep(2000);
  43.     AfxBeginThread(ThreadProc17, NULL);
  44.     Sleep(3000);
  45.     return 0;
  46. }

3.2、CSemaphore

  1. #include <afxwin.h>
  2. #include <afxmt.h>
  3. // MFC信号量类对象
  4. CSemaphore g_clsSemaphore(2, 2);
  5. UINT ThreadProc24(LPVOID pParam)
  6. {
  7.     // 试图进入信号量关口
  8.     g_clsSemaphore.Lock();
  9.     // 线程任务处理
  10.     AfxMessageBox("线程一正在执行!");
  11.     // 释放信号量计数
  12.     g_clsSemaphore.Unlock();
  13.     return 0;
  14. }
  15. UINT ThreadProc25(LPVOID pParam)
  16. {
  17.     // 试图进入信号量关口
  18.     g_clsSemaphore.Lock();
  19.     // 线程任务处理
  20.     AfxMessageBox("线程二正在执行!");
  21.     // 释放信号量计数
  22.     g_clsSemaphore.Unlock();
  23.     return 0;
  24. }
  25. UINT ThreadProc26(LPVOID pParam)
  26. {
  27.     // 试图进入信号量关口
  28.     g_clsSemaphore.Lock();
  29.     // 线程任务处理
  30.     AfxMessageBox("线程三正在执行!");
  31.     // 释放信号量计数
  32.     g_clsSemaphore.Unlock();
  33.     return 0;
  34. }
  35. int main()
  36. {
  37.     // 开启线程
  38.     AfxBeginThread(ThreadProc24, NULL);
  39.     Sleep(1000);
  40.     AfxBeginThread(ThreadProc25, NULL);
  41.     Sleep(2000);
  42.     AfxBeginThread(ThreadProc26, NULL);
  43.     Sleep(3000);
  44.     return 0;
  45. }

 

第四部分 Mutex

4.1、Mutex

  1. #include <afxwin.h>
  2. // 互斥对象
  3. HANDLE hMutex = NULL;
  4. char g_cArray[10];
  5. UINT ThreadProc18(LPVOID pParam)
  6. {
  7.     // 等待互斥对象通知
  8.     WaitForSingleObject(hMutex, INFINITE);
  9.     // 对共享资源进行写入操作
  10.     for (int i = 0; i < 10; i++)
  11.     {
  12.         g_cArray[i] = 'a';
  13.         Sleep(1);
  14.     }
  15.     // 释放互斥对象
  16.     ReleaseMutex(hMutex);
  17.     return 0;
  18. }
  19. UINT ThreadProc19(LPVOID pParam)
  20. {
  21.     // 等待互斥对象通知
  22.     WaitForSingleObject(hMutex, INFINITE);
  23.     // 对共享资源进行写入操作
  24.     for (int i = 0; i < 10; i++)
  25.     {
  26.         g_cArray[10 - i - 1] = 'b';
  27.         Sleep(1);
  28.     }
  29.     // 释放互斥对象
  30.     ReleaseMutex(hMutex);
  31.     return 0;
  32. }
  33. int main()
  34. {
  35.     // 创建互斥对象
  36.     hMutex = CreateMutex(NULL, FALSE, NULL);
  37.     // 启动线程
  38.     AfxBeginThread(ThreadProc18, NULL);
  39.     AfxBeginThread(ThreadProc19, NULL);
  40.     // 等待计算完毕
  41.     Sleep(300);
  42.     // 报告计算结果
  43.     CString sResult = CString(g_cArray);
  44.     AfxMessageBox(sResult);
  45.     return 0;

 

4.2、CMutex

  1. #include <afxwin.h>
  2. #include <afxmt.h>
  3. char g_cArray[10];
  4. // MFC互斥类对象
  5. CMutex g_clsMutex(FALSE, NULL);
  6. UINT ThreadProc27(LPVOID pParam)
  7. {
  8.     // 等待互斥对象通知
  9.     g_clsMutex.Lock();
  10.     // 对共享资源进行写入操作
  11.     for (int i = 0; i < 10; i++)
  12.     {
  13.         g_cArray[i] = 'a';
  14.         Sleep(1);
  15.     }
  16.     // 释放互斥对象
  17.     g_clsMutex.Unlock();
  18.     return 0;
  19. }
  20. UINT ThreadProc28(LPVOID pParam)
  21. {
  22.     // 等待互斥对象通知
  23.     g_clsMutex.Lock();
  24.     // 对共享资源进行写入操作
  25.     for (int i = 0; i < 10; i++)
  26.     {
  27.         g_cArray[10 - i - 1] = 'b';
  28.         Sleep(1);
  29.     }
  30.     // 释放互斥对象
  31.     g_clsMutex.Unlock();
  32.     return 0;
  33. }
  34. int main()
  35. {
  36.     // 启动线程
  37.     AfxBeginThread(ThreadProc27, NULL);
  38.     AfxBeginThread(ThreadProc28, NULL);
  39.     // 等待计算完毕
  40.     Sleep(300);
  41.     // 报告计算结果
  42.     CString sResult = CString(g_cArray);
  43.     AfxMessageBox(sResult);
  44.     return 0;
  45. }

 

采用线程的好处: 为什么使用多个线程而不使用多个进程?

一个进程可以产生多个线程,这些线程都共享该进程的地址空间,它们可以并行、异步地执行。

采用线程最主要的好处是:使同一个程序能有几个并行执行的路径,提高了执行速度;线程需要的系统开销比进程要小。应该说明的是,在 Windows95中,“多任务”是基于线程而不是基于进程。

为什么使用多个线程而不使用多个进程?
最重要的答案便是:线程价廉.线程启动比较快,退出比较快,对系统资源的冲击也比较小.而且线程彼此分享了大部分核心对象(如file handles)的拥有权.
 
多任务执行
多任务执行是指在同一台计算机系统的同一时刻运行多个程序。由于允许活动任务和后台任务同时运行,所以可以做到有一个任务在后台执行时,前台又能干另一件事。比如说,我们可以一边用图文处理程序编辑一个文件,一边让打印程序完成打印工作。这就极大地提高了工作效率,因为大多数用户都确实需要同时对几个不同的应用程序进行工作。
 
线程分类
内核线程:由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows   NT和2000/XP支持内核线程 。
用户线程:由应用进程利用线程库创建和管理,不以来于操作系统核心。不需要用户态/核心态切换,速度快。操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。   
轻量级进程:一个进程可以有多个LWP,每个LWP由一个单独的内核线程来支持。融合了前面两者的优点,是一个折中方案。

 

2     线程间通信 VS 进程间通信

关于线程间通信,涉及到同一进程内的所有线程通信不同进程的线程间通信;因为同一进程中的所有线程均可以访问所有的全局变量,因而全局变量成为同一进程间多线程通信的最简单方式。但如何访问全局变量是多线程程序必须考虑的一个问题。而对于不同进程间线程通信问题实质上就是进程间通信问题。
        因此本文将线程间通信特指同一进程中的所有线程通信问题,实质就是解决线程间的竞争问题。而进程间通信指在操作系统书中介绍的传统意义上的进程通信问题。
2.1     线程间通信
多线程程序的核心问题;竞争对(共享)资源的竞争关系.解决这种竞争关系的方法就是线程间通信问题,包括互斥同步
线程程同步:是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥:是指对于共享的操作系统资源(指的是广义的"资源",而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。(原子操作,互锁访问)
线程互斥是一种特殊的线程同步。
实际上,互斥和同步对应着线程间通信发生的两种情况:
(1)当有多个线程访问共享资源而不使资源被破坏时;
(2)当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。
在WIN32中,同步机制主要有以下几种:
(1)事件(Event);(内)核心对象,多用于overlapped。适用于不同进程的线程。
(2)信号量(semaphore);核心对象,无拥有者。适用于不同进程的线程。
(3)互斥量(mutex);内核对象(内)核心对象,排他性对象,甚至适用于不同进程的线程。
(4)临界区(Critical section)。局部对象,不是(内)核心对象,仅适用于同一个进程的不同线程。 
 2.2     进程间通信
几个系统中近程通信的方法:
unix:管道,消息队列,信号(signal),信号量(灯),共享存储,socket。
linux:同上(不知道*nix系列是否都是如此)  
vxworks:管道,消息队列,信号量(灯),信号,socket?  
windows:事件(Event),信号量(semaphore), 互斥量(mutex)

转载地址:http://fhntb.baihongyu.com/

你可能感兴趣的文章
四种获取Class对象的方法-Java反射机制
查看>>
eclipse用空格代替制表符
查看>>
Squid中文权威指南-第4章 快速配置向导
查看>>
Squid中文权威指南-第5章 运行Squid
查看>>
Squid中文权威指南-第6章 访问控制
查看>>
Squid中文权威指南-第7章 磁盘缓存基础
查看>>
Squid中文权威指南-第8章 高级磁盘缓存主题
查看>>
Squid中文权威指南-第9章 Cache拦截
查看>>
Squid中文权威指南-第10章 与其他Squid会话
查看>>
Squid中文权威指南-第11章 重定向器
查看>>
Squid中文权威指南-第12章 验证辅助器
查看>>
samba配置
查看>>
不要在linux上启用net.ipv4.tcp_tw_recycle参数
查看>>
UML建模——使用EA工具开发时序图实践及经验
查看>>
centos 7源码编译安装qemu-kvm和spice
查看>>
Gobject 学习总结
查看>>
git - 简明指南
查看>>
CentOS 6&7 安装使用多个GCC版本(GCC4.9,GCC5.3,GCC6.2)
查看>>
LD_PRELOAD作用
查看>>
mysql 5.7忘记密码及重新更改目录,无相关文件
查看>>