Windows上多线程同步相关的MFC类

本文介绍在Windows环境下使用MFC类进行多线程同步的方法,包括使用同步类(如CSyncObject、CMutex等)和同步访问类(如CMultiLock、CSingleLock)进行线程间的数据访问控制。并通过代码示例展示了如何实现线程间资源的有序访问,以确保数据的一致性和完整性。
转载请标明出处: http://blog.youkuaiyun.com/zhangxingping

Windows上多线程同步相关的MFC类

相关类

MFC中提供的用于多线程程序中进行线程同步的类可以分为两类:同步类和同步访问类。

同步类

synchronization class

用于确保对资源完整性访问进行控制的类

CSyncObject

CSemaphore,

CMutex

CCriticalSection,

CEvent

同步访问类

Synchronization access class

用于获取受控资源访问权限的类

CMultiLock

CSingleLock

 

上面表格中的CSyncObject类为纯虚类,在实际中几乎不会直接使用它。它也是其它同步类的直接父类。

 

那么,在实际中应该如何判断到底需要使用上面的那个同步类了?一般遵循一下的原则:

1.        如果线程对资源的访问必须是在某个事件发生之后,那么可以使用CEvent。

2.        如果同一个程序中的多个线程可以在同一时刻访问资源,那么可以使用CSemaphore。

3.        如果多个程序可以访问资源,则可以使用CMutex;否则,使用CCriticalSection。

程序示例

CEvent

CEvent在线程必须等待某个事件发生后才能继续执行的情况下非常有用。它有两种工作模式:手动和自动。对于手动模式来说,事件的状态会一直保持到下一次调用SetEvent 或者 ResetEvent 对于自动模式来说,事件的状态会在唤醒至少一个线程之后重新恢复到不可用的状态。

 

下面的代码演示了CEvent的典型用法。程序中通过两个线程来分别输出20个a和b字符,但是我们期待这两个字符的输出是相间的,也就是不要出现连续的a或者连续的b。

Visual Studio创建的控制台程序,支持MFC:

  1. // CEventDemo.cpp :定义控制台应用程序的入口点。   
  2. //   
  3.    
  4. #include "stdafx.h"   
  5. #include "CEventDemo.h"   
  6.    
  7. #include "afxmt.h"   
  8.    
  9. #include <conio.h>   
  10.    
  11.    
  12. #ifdef _DEBUG   
  13. #define new DEBUG_NEW   
  14. #endif   
  15.    
  16.    
  17. // 唯一的应用程序对象   
  18.    
  19. CWinApp theApp;  
  20.    
  21. using namespacestd;  
  22.    
  23. CEvent TrigerB(false);//用来唤醒线程B,来输出字符b   
  24. CEvent TrigerA(true);//用来唤醒线程A,来输出字符a   
  25.    
  26. //线程A:用来输出字符a   
  27. UINT __cdecl ThreadA( LPVOIDpParam )  
  28. {  
  29.     forint count = 0; count < 20; count++)  
  30.     {  
  31.         WaitForSingleObject(TrigerA.m_hObject,INFINITE);//等待被唤醒   
  32.         cout<< " a " ;  
  33.         TrigerA.ResetEvent();//重置,防止该线程输出连续的a   
  34.         //TrigerA.PulseEvent();   
  35.         TrigerB.SetEvent();//唤醒线程B   
  36.     }  
  37.    
  38.     return 0;  
  39.    
  40. }  
  41.    
  42. //线程B:用来输出字符b   
  43. UINT __cdecl ThreadB( LPVOIDpParam )  
  44. {  
  45.    
  46.     forint count = 0; count < 20; count++)  
  47.     {  
  48.         WaitForSingleObject(TrigerB.m_hObject,INFINITE);//等待被唤醒   
  49.         cout<< " b " ;  
  50.         TrigerB.ResetEvent();//重置,防止该线程输出连续的b   
  51.         //TrigerB.PulseEvent();   
  52.         TrigerA.SetEvent();//唤醒线程A   
  53.     }  
  54.    
  55.     return 0;  
  56. }  
  57.    
  58. int _tmain(intargc,TCHAR*argv[],TCHAR*envp[])  
  59. {  
  60.     int nRetCode = 0;  
  61.    
  62.     // 初始化MFC并在失败时显示错误   
  63.     if (!AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0))  
  64.     {  
  65.         // TODO: 更改错误代码以符合您的需要   
  66.         _tprintf(_T("错误: MFC初始化失败\n"));  
  67.         nRetCode= 1;  
  68.     }  
  69.     else  
  70.     {  
  71.         // TODO: 在此处为应用程序的行为编写代码。   
  72.    
  73.         CWinThread* threadA = AfxBeginThread(ThreadB,NULL);  
  74.         CWinThread* threadB = AfxBeginThread(ThreadA,NULL);  
  75.    
  76.         WaitForSingleObject(threadB->m_hThread,INFINITE);  
  77.         WaitForSingleObject(threadA->m_hThread,INFINITE);  
  78.    
  79.         char c = getch();  
  80.     }  
  81.    
  82.     return nRetCode;  
  83. }  
// CEventDemo.cpp :定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
#include "CEventDemo.h"
 
#include "afxmt.h"
 
#include <conio.h>
 
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
 
// 唯一的应用程序对象
 
CWinApp theApp;
 
using namespacestd;
 
CEvent TrigerB(false);//用来唤醒线程B,来输出字符b
CEvent TrigerA(true);//用来唤醒线程A,来输出字符a
 
//线程A:用来输出字符a
UINT __cdecl ThreadA( LPVOIDpParam )
{
    for( int count = 0; count < 20; count++)
    {
        WaitForSingleObject(TrigerA.m_hObject,INFINITE);//等待被唤醒
        cout<< " a " ;
        TrigerA.ResetEvent();//重置,防止该线程输出连续的a
        //TrigerA.PulseEvent();
        TrigerB.SetEvent();//唤醒线程B
    }
 
    return 0;
 
}
 
//线程B:用来输出字符b
UINT __cdecl ThreadB( LPVOIDpParam )
{
 
    for( int count = 0; count < 20; count++)
    {
        WaitForSingleObject(TrigerB.m_hObject,INFINITE);//等待被唤醒
        cout<< " b " ;
        TrigerB.ResetEvent();//重置,防止该线程输出连续的b
        //TrigerB.PulseEvent();
        TrigerA.SetEvent();//唤醒线程A
    }
 
    return 0;
}
 
int _tmain(intargc,TCHAR*argv[],TCHAR*envp[])
{
    int nRetCode = 0;
 
    // 初始化MFC并在失败时显示错误
    if (!AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0))
    {
        // TODO: 更改错误代码以符合您的需要
        _tprintf(_T("错误: MFC初始化失败\n"));
        nRetCode= 1;
    }
    else
    {
        // TODO: 在此处为应用程序的行为编写代码。
 
        CWinThread* threadA = AfxBeginThread(ThreadB,NULL);
        CWinThread* threadB = AfxBeginThread(ThreadA,NULL);
 
        WaitForSingleObject(threadB->m_hThread,INFINITE);
        WaitForSingleObject(threadA->m_hThread,INFINITE);
 
        char c = getch();
    }
 
    return nRetCode;
}

CCriticalSection

CCriticalSection主要用于资源在同一个时刻只允许一个线程访问的情况;也就是主要用于临界区临界资源的访问。

 

示例程序如下,Visual Studio创建的控制台程序,支持MFC:

  1. //CCriticalSectionDemo.cpp : 定义控制台应用程序的入口点。   
  2. //   
  3.    
  4. #include "stdafx.h"   
  5. #include "CCriticalSectionDemo.h"   
  6. #include "afxmt.h"   
  7.    
  8. #include <conio.h>   
  9. #include <windows.h>   
  10.    
  11. #ifdef _DEBUG   
  12. #define new DEBUG_NEW   
  13. #endif   
  14.    
  15.    
  16. // 唯一的应用程序对象   
  17.    
  18. CWinApp theApp;  
  19.    
  20. using namespacestd;  
  21.    
  22. const intsize= 10;  
  23. const inttotalCount= 10;  
  24.    
  25. int data[size] = { 0 };  
  26.    
  27. CCriticalSection cs;  
  28.    
  29. //线程A:用来输出数组中的全部数值;输出次   
  30. UINT __cdecl PrintThread( LPVOIDpParam )  
  31. {  
  32.     for ( int count = 0; count < totalCount;count++)  
  33.     {  
  34.         cs.Lock();//每次输出之前,先申请访问临界区   
  35.    
  36.         forint index = 0; index < size;index++)  
  37.         {  
  38.             cout<< data[index]<< " ";  
  39.             Sleep(200);  
  40.         }  
  41.         cout<< endl;  
  42.    
  43.         cs.Unlock();//资源访问完毕后,释放之   
  44.    
  45.          
  46.     }  
  47.    
  48.     return 0;  
  49.    
  50. }  
  51.    
  52. //线程B:用来修改数组中的全部数值   
  53. UINT __cdecl ModifyThread( LPVOIDpParam )  
  54. {  
  55.      
  56.     for ( int count = 0; count < totalCount;count++)  
  57.     {  
  58.         cs.Lock();//每次修改资源之前,申请先   
  59.    
  60.         forint index = 0; index < size;index++)  
  61.         {  
  62.              data[index] =count;  
  63.              Sleep(200);  
  64.         }  
  65.          
  66.         cs.Unlock();//资源使用完毕,释放之   
  67.     }  
  68.    
  69.     return 0;  
  70. }  
  71.    
  72.    
  73. int _tmain(intargc,TCHAR*argv[],TCHAR*envp[])  
  74. {  
  75.     int nRetCode = 0;  
  76.    
  77.     // 初始化MFC并在失败时显示错误   
  78.     if (!AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0))  
  79.     {  
  80.         // TODO: 更改错误代码以符合您的需要   
  81.         _tprintf(_T("错误: MFC初始化失败\n"));  
  82.         nRetCode= 1;  
  83.     }  
  84.     else  
  85.     {  
  86.         // TODO: 在此处为应用程序的行为编写代码。   
  87.    
  88.         CWinThread* pPrintThread = AfxBeginThread(PrintThread,NULL);  
  89.         CWinThread* pModifyThread = AfxBeginThread(ModifyThread,NULL);  
  90.    
  91.         WaitForSingleObject(pPrintThread->m_hThread,INFINITE);  
  92.         WaitForSingleObject(pModifyThread->m_hThread,INFINITE);  
  93.    
  94.    
  95.         char c = getch();  
  96.     }  
  97.    
  98.     return nRetCode;  
  99. }  
//CCriticalSectionDemo.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
#include "CCriticalSectionDemo.h"
#include "afxmt.h"
 
#include <conio.h>
#include <windows.h>
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
 
// 唯一的应用程序对象
 
CWinApp theApp;
 
using namespacestd;
 
const intsize= 10;
const inttotalCount= 10;
 
int data[size] = { 0 };
 
CCriticalSection cs;
 
//线程A:用来输出数组中的全部数值;输出次
UINT __cdecl PrintThread( LPVOIDpParam )
{
    for ( int count = 0; count < totalCount;count++)
    {
        cs.Lock();//每次输出之前,先申请访问临界区
 
        for( int index = 0; index < size;index++)
        {
            cout<< data[index]<< " ";
            Sleep(200);
        }
        cout<< endl;
 
        cs.Unlock();//资源访问完毕后,释放之
 
       
    }
 
    return 0;
 
}
 
//线程B:用来修改数组中的全部数值
UINT __cdecl ModifyThread( LPVOIDpParam )
{
   
    for ( int count = 0; count < totalCount;count++)
    {
        cs.Lock();//每次修改资源之前,申请先
 
        for( int index = 0; index < size;index++)
        {
             data[index] =count;
             Sleep(200);
        }
       
        cs.Unlock();//资源使用完毕,释放之
    }
 
    return 0;
}
 
 
int _tmain(intargc,TCHAR*argv[],TCHAR*envp[])
{
    int nRetCode = 0;
 
    // 初始化MFC并在失败时显示错误
    if (!AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0))
    {
        // TODO: 更改错误代码以符合您的需要
        _tprintf(_T("错误: MFC初始化失败\n"));
        nRetCode= 1;
    }
    else
    {
        // TODO: 在此处为应用程序的行为编写代码。
 
        CWinThread* pPrintThread = AfxBeginThread(PrintThread,NULL);
        CWinThread* pModifyThread = AfxBeginThread(ModifyThread,NULL);
 
        WaitForSingleObject(pPrintThread->m_hThread,INFINITE);
        WaitForSingleObject(pModifyThread->m_hThread,INFINITE);
 
 
        char c = getch();
    }
 
    return nRetCode;
}


 

上面程序的可能输出如下:

0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0

1 1 1 1 1 1 1 1 1 1

2 2 2 2 2 2 2 2 2 2

3 3 3 3 3 3 3 3 3 3

4 4 4 4 4 4 4 4 4 4

5 5 5 5 5 5 5 5 5 5

6 6 6 6 6 6 6 6 6 6

7 7 7 7 7 7 7 7 7 7

8 8 8 8 8 8 8 8 8 8

 

如果去掉程序中所有的Lock和Unlock的调用,可能的输出则如下:

0 0 0 0 0 0 0 0 0 0

1 1 1 1 1 1 0 1 1 1

2 2 2 2 2 2 2 2 2 2

3 3 3 3 3 3 3 3 3 3

4 4 4 4 4 4 4 4 4 4

5 5 5 5 5 5 5 5 5 5

6 6 6 6 6 6 6 6 6 6

6 6 7 7 7 7 7 7 7 7

8 8 8 8 8 8 8 8 7 7

9 9 9 9 9 9 9 9 9 9

注意其中的第二行,第八行和第九行的输出,这样的输出没有保证打印线程和修改线程对资源访问的完整性。也就是说出现了,修改线程和打印线程对资源访问的交叉。

 

增加了cs的Lock和Unlock操作之后,就避免了这种情况的发生。

注意:增加了cs的操作之后,打印的顺序并不能是在每次修改之后就打印修改后的数据,要达到输出:

0 0 0 0 0 0 0 0 0 0

1 1 1 1 1 1 1 1 1 1

2 2 2 2 2 2 2 2 2 2

3 3 3 3 3 3 3 3 3 3

4 4 4 4 4 4 4 4 4 4

5 5 5 5 5 5 5 5 5 5

6 6 6 6 6 6 6 6 6 6

7 7 7 7 7 7 7 7 7 7

8 8 8 8 8 8 8 8 8 8

9 9 9 9 9 9 9 9 9 9

则可以使用前面说到的CEvent,因为打印线程总是要在每次修改操作完成之后进行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值