线程同步

  在多线程的情况下,如果存在多个线程要使用同一个资源的情况时,这需要在线程之间进行协调(同步)才能是程序完成预定的工作,而不会出现灾难性的冲突。为了解决多线程之间的同步问题,MFC把对线程之间进行同步的一些基本操作封装在类CSyncObject中,为了适应在各种不同的情况下同步的需要,MFC又以类CSyncObject为基类派生了四种同步类,即事件,临界段,互斥体,和信号计数器,分别为:CEvents,CCriticalSections,CMutexes,CSemphores。这些类的声明都在头文件"afxmt.h"中。

1、事件同步类:

CEvent( BOOL bInitiallyOwn = FALSE, //用来指定事件对象初始状态是否为发信状态

   BOOL bManualReset = FALSE, //用来指定创建事件对象是自动事件还是手工事件对象

  LPCTSTR lpszName = NULL, //用来定义事件对象的名称

  LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );//为指向一个LPSECURITY_ATTRIBUTES结构的指针

CEvent类提供了三种对事件对象进行操控的方法:

BOOL SetEvent( );//设置事件为发信状态,并释放其他正在等待的线程

BOOL PulseEvent( );//设置事件为发信状态,并释放其他正在等待的线程,然后把时间设置为未发信状态

BOOL ResetEvent( );//设置事件为未发信状态

详细理解通过一个例子来了解工作原理:

例子:设计一个应用程序,当用户在程序窗口上按下鼠标左键时,会创建和启动两个线程,这两个线程被启动后,各自显示一个信息框,表明线程已被启动,随即被时间对象的Lock函数把线程挂起,当用户在程序窗口按下鼠标右键时,启动另一个线程,在该线程中把时间对象设置为“发信”状态,从而启动了第一个被挂起的线程。

实现:(1)用MFC创建一个单文档应用程序

(2)为了使用事件对象,在应用程序的头文件中包含afxmt.h

#include"afxmt.h"

(3)在程序的视图类的实现文件(.cpp文件)中定义一个全局事件对象

CEvent  eventObj;

(4)在程序的视图类实现文件中编写如下线程函数:


//线程函数1
UINT MessageThread1(LPVOID pParam)
{
 char* pMessage="Thread1 is started";//线程一对话框显示的字符串
 CWnd* pMainWnd=AfxGetMainWnd();//获取主窗口的指针,在这里主窗口的视图类的主窗口
 ::MessageBox(pMainWnd->m_hWnd,//M每一个窗口类当中有一个句柄的数据成员
  pMessage,//显示的内容
  "Thread Message",//对话框的标题
  MB_OK
  );

 eventObj.Lock();//将线程挂起,处于等待状态

 pMessage="Thread1 is unblocked";//线程一对话框显示的字符串
 //CWnd* pMainWnd=AfxGetMainWnd();//获取主窗口的指针,在这里主窗口的视图类的主窗口
 ::MessageBox(pMainWnd->m_hWnd,//M每一个窗口类当中有一个句柄的数据成员
  pMessage,//显示的内容
  "Thread Message",//对话框的标题
  MB_OK
  );

 eventObj.Lock();//将线程挂起,处于再次等待状态

 pMessage="Thread1 is unblocked again";//线程一对话框显示的字符串
 //CWnd* pMainWnd=AfxGetMainWnd();//获取主窗口的指针,在这里主窗口的视图类的主窗口
 ::MessageBox(pMainWnd->m_hWnd,//M每一个窗口类当中有一个句柄的数据成员
  pMessage,//显示的内容
  "Thread Message",//对话框的标题
  MB_OK
  );
 return 0;
}


//线程函数2
UINT MessageThread2(LPVOID pParam)
{
 char * pMessage="Thread2 is started";//线程一对话框显示的字符串
 CWnd* pMainWnd=AfxGetMainWnd();//获取主窗口的指针,在这里主窗口的视图类的主窗口
 ::MessageBox(pMainWnd->m_hWnd,//M每一个窗口类当中有一个句柄的数据成员
  pMessage,//显示的内容
  "Thread Message",//对话框的标题
  MB_OK
  );

 eventObj.Lock();//将线程挂起,处于等待状态

 pMessage="Thread2 is unblocked";//线程一对话框显示的字符串
 //CWnd* pMainWnd=AfxGetMainWnd();//获取主窗口的指针,在这里主窗口的视图类的主窗口
 ::MessageBox(pMainWnd->m_hWnd,//M每一个窗口类当中有一个句柄的数据成员
  pMessage,//显示的内容
  "Thread Message",//对话框的标题
  MB_OK
  );
 return 0;
}

//线程函数3
UINT MessageThread3(LPVOID pParam)
{
 eventObj.SetEvent();//将线程设置为事件设置为发信状态
 return 0;
}

(5)鼠标消息响应函数如下


// CThreadSyncView 消息处理程序

void CThreadSyncView::OnLButtonDown(UINT nFlags, CPoint point)
{
 // TODO: 在此添加消息处理程序代码和/或调用默认值
 AfxBeginThread(MessageThread1,"Thread is stated");
 AfxBeginThread(MessageThread2,"Thread is stated");

 CView::OnLButtonDown(nFlags, point);
}


void CThreadSyncView::OnRButtonDown(UINT nFlags, CPoint point)
{
 // TODO: 在此添加消息处理程序代码和/或调用默认值
 AfxBeginThread(MessageThread3,"Thread is unblocked");
 CView::OnRButtonDown(nFlags, point);
}

2、临界段同步类(CCriticalSectiond)

类CCriticalSectiond的对象叫做临界段,他只允许一个线程占有某个共享资源,因此这个类的对象是用来保护独占式共享资源的。

详细细节同样通过一个例子来理解:

例子:使用临界段写一个有两个线程的应用程序

实现:(1)用MFC创建一个单文档的应用程序

(2)在应用程序的头文件中添加afxmt.h头文件

#include<afxmt.h>

(3)在视图类的实现文件中定义一个临界段的对象

CCriticalSection criticalSection;

(4)在视图类实现文件中定义两个线程函数


//定义线程函数
UINT MessageThread1(LPVOID pParam)//线程1
{
 criticalSection.Lock();//设置临界段,此时一直执行该线程
 char * pMessage="Thread1 is started";
 CWnd * pMainWnd=AfxGetMainWnd();//这是线程函数获取视类的指针,而每一个视类的对象都包含一个该类的句柄成员
 ::MessageBox(pMainWnd->m_hWnd,//视类的句柄
  pMessage,//显示的内容
  "Thread message",//对话框的标题
  MB_OK);
 criticalSection.Unlock();//线程释放临界段
 return 0;
}
UINT MessageThread2(LPVOID pParam)//线程1
{
 criticalSection.Lock();//设置临界段,此时一直执行该线程
 char * pMessage="Thread2 is started";
 CWnd * pMainWnd=AfxGetMainWnd();//这是线程函数获取视类的指针,而每一个视类的对象都包含一个该类的句柄成员
 ::MessageBox(pMainWnd->m_hWnd,//视类的句柄
  pMessage,//显示的内容
  "Thread message",//对话框的标题
  MB_OK);
 criticalSection.Unlock();//线程释放临界段
 return 0;
}

(5)在视图类鼠标消息响应函数中编写如下代码


// CCriticalSectionView 消息处理程序
void CCriticalSectionView::OnLButtonDown(UINT nFlags, CPoint point)
{
 // TODO: 在此添加消息处理程序代码和/或调用默认值
 //单击左键就会启动两个线程
 AfxBeginThread(MessageThread1,"Thread is starting");//开启线程1
 AfxBeginThread(MessageThread2,"Thread is starting");//开启线程2

 CView::OnLButtonDown(nFlags, point);
}

3、互斥体类(CMutex)

互斥体是CMutex类的对象,也只允许一个线程占有某个共享资源,以保护独占式共享资源。因此互斥体的使用方法与临界段的使用方法极为相似,所不同的是临界段只能在同一个进程中对线程进行同步,而互斥体可以在不同的进程中进行线程同步控制。

CMutex( BOOL bInitiallyOwn = FALSE,//用来指定互斥体对象的初始状态时锁定的还是非锁定的

     LPCTSTR lpszName = NULL,//用来指定互斥体对象的名称

     LPSECURITY_ATTRIBUTES lpsaAttribute = NULL //一个指向 LPSECURITY_ATTRIBUTES结构体指针

     );

详细过程通过一个例子来理解:

例子:编写一个应用程序,实现进程间线程的同步

实现:(1)用MFC创建一个单文档的应用程序

(2)在应用程序的头文件中包含头文件afxmt.h

#include<afxmt.h>

(3)在视图类的实现文件中定义一个互斥体对象

CMutex mutexObj(FALSE,"mutex1");

(4)在视图类的实现文件中定义线程函数

//定义线程函数
UINT MessageThread1(LPVOID pParam)//线程函数1
{
 //线程一旦开启,立刻用互斥体对象来锁定
 mutexObj.Lock();//锁定线程
 char* pMessage="Thread1 is started";
 //线程开启后在视类窗口区内显示一个对话框,需要获取视类句柄,而线程是在视类添加的,因此只需要获取线程的主窗口的句柄
 CWnd* pMainCwnd=AfxGetMainWnd();//获取主窗口指针,该指针包含主窗口句柄的成员
 //显示对话框
 ::MessageBox(pMainCwnd->m_hWnd,//主窗口句柄
  pMessage,//显示的内容
  "Thread message",//对话框的标题
  MB_OK//对话框有ok按钮
  );
 //完了之后就互斥体解锁,让给其他线程来运行
 mutexObj.Unlock();
 return 0;
}

UINT MessageThread2(LPVOID pParam)//线程函数2
{
 //线程一旦开启,立刻用互斥体对象来锁定
 mutexObj.Lock();//锁定线程
 char* pMessage="Thread2 is started";
 //线程开启后在视类窗口区内显示一个对话框,需要获取视类句柄,而线程是在视类添加的,因此只需要获取线程的主窗口的句柄
 CWnd* pMainCwnd=AfxGetMainWnd();//获取主窗口指针,该指针包含主窗口句柄的成员
 //显示对话框
 ::MessageBox(pMainCwnd->m_hWnd,//主窗口句柄
  pMessage,//显示的内容
  "Thread message",//对话框的标题
  MB_OK//对话框有ok按钮
  );
 //完了之后就互斥体解锁,让给其他线程来运行
 mutexObj.Unlock();
 return 0;
}

(5)在鼠标左键按下事件响应函数中添加如下代码

void CMutexView::OnLButtonDown(UINT nFlags, CPoint point)
{
 // TODO: 在此添加消息处理程序代码和/或调用默认值

 //左键单击是为了启动线程
 AfxBeginThread(MessageThread1,"thread1 is starting");
 AfxBeginThread(MessageThread2,"thread2 is starting");
 CView::OnLButtonDown(nFlags, point);
}

 4、信号量类(CSemaphore)

信号量类是CSemaphore的对象,该对象的作用是对访问某个共享资源的线程的数目进行控制。

信号量的构造函数:

CSemaphore( LONG lInitialCount = 1, //用来指定设定计数器的初始值

    LONG lMaxCount = 1, //用来设定计数器的最大计数值

    LPCTSTR pstrName = NULL,

    LPSECURITY_ATTRIBUTES lpsaAttributes = NULL

      );

详细过程通过一个例子来理解

例子:设计一个有四个线程的应用程序,理解信号量对象的使用

实现:(1)用MFC创建一个单文档的应用程序

(2)在应用程序的头文件中包含afxmt.h

#include<afxmt.h>

(3)在视图类的实现文件中定义一个信号量对象

CSemaphore semaphorObj(2,3);

(4)在视图类的实现文件中定义四个线程函数


//定义线程函数
UINT MessageThread1(LPVOID pParam)//线程函数1
{
 //一旦启动线程,则将该线程绑定相应的资源
 semaphoreObj.Lock();
 //用对话框的方式来提示
 char *pMessage="Thread1 is runing!!!";
 //获取线程的主窗口
 CWnd* pMainCWnd=AfxGetMainWnd();
 //弹出对话框
 ::MessageBox(
  pMainCWnd->m_hWnd,//主窗口句柄
  pMessage,//提示的内容
  "Thread message",//对话框的标题
  MB_OK
  );
 //提示完了之后就解锁
 semaphoreObj.Unlock();
 return 0;
}
UINT MessageThread2(LPVOID pParam)//线程函数1
{
 //一旦启动线程,则将该线程绑定相应的资源
 semaphoreObj.Lock();
 //用对话框的方式来提示
 char *pMessage="Thread2 is runing!!!";
 //获取线程的主窗口
 CWnd* pMainCWnd=AfxGetMainWnd();
 //弹出对话框
 ::MessageBox(
  pMainCWnd->m_hWnd,//主窗口句柄
  pMessage,//提示的内容
  "Thread message",//对话框的标题
  MB_OK
  );
 //提示完了之后就解锁
 semaphoreObj.Unlock();
 return 0;
}
UINT MessageThread3(LPVOID pParam)//线程函数1
{
 //一旦启动线程,则将该线程绑定相应的资源
 semaphoreObj.Lock();
 //用对话框的方式来提示
 char *pMessage="Thread3 is runing!!!";
 //获取线程的主窗口
 CWnd* pMainCWnd=AfxGetMainWnd();
 //弹出对话框
 ::MessageBox(
  pMainCWnd->m_hWnd,//主窗口句柄
  pMessage,//提示的内容
  "Thread message",//对话框的标题
  MB_OK
  );
 //提示完了之后就解锁
 semaphoreObj.Unlock();
 return 0;
}
UINT MessageThread4(LPVOID pParam)//线程函数1
{
 //一旦启动线程,则将该线程绑定相应的资源
 semaphoreObj.Lock();
 //用对话框的方式来提示
 char *pMessage="Thread4 is runing!!!";
 //获取线程的主窗口
 CWnd* pMainCWnd=AfxGetMainWnd();
 //弹出对话框
 ::MessageBox(
  pMainCWnd->m_hWnd,//主窗口句柄
  pMessage,//提示的内容
  "Thread message",//对话框的标题
  MB_OK
  );
 //提示完了之后就解锁
 semaphoreObj.Unlock();
 return 0;
}

(5)在鼠标左键按下的消息响应函数当中添加


// CSemaphoreView 消息处理程序

void CSemaphoreView::OnLButtonDown(UINT nFlags, CPoint point)
{
 // TODO: 在此添加消息处理程序代码和/或调用默认值
 //单击开启线程
 AfxBeginThread(MessageThread1,NULL);
 AfxBeginThread(MessageThread2,NULL);
 AfxBeginThread(MessageThread3,NULL);
 AfxBeginThread(MessageThread4,NULL);

 CView::OnLButtonDown(nFlags, point);
}

原文地址:https://www.cnblogs.com/ljy2013/p/3558453.html