哲学家就餐问题——MFC演示程序

4 篇文章 0 订阅


     这个问题实际上就是多线程的调度问题,因为MFC里面已经给我们封装好了线程类,CWinThread类,因此我们要做的仅仅是简单的为每位哲学家分别创建进程。程序逻辑不是很复杂,但在用MFC来做哲学家就餐问题的演示问题时,就涉及到的图像绘制可能比较麻烦,因为你要给每位哲学家的当前状态给出相应的图示。    
 

      这个是我写的演示程序的主界面,左边使用图形显示哲学家就餐中的一些状态切换,右边用编辑框给每位哲学家配以文字说明。

     一、先说下演示程序的核心部分,CWinThread类和CCriticalSection临界区类

            MFC给我们封装好了线程类CWinThread供我们在程序里面去创建新的线程,其提供了两种线程,工作线程和用户线程(具体区别可以去查相应资料,这里就不多说了),本程序采用的是工作线程(工作线程就是在后台处理大量数据,并不需要响应消息请求,用户线程就是在线程运行的时候还要时刻捕获用户的消息请求)。

           工作线程创建很简单

           CWinThread *pThread;

            pThread->AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOIDpParam, int nPriority = THREAD_PRIORITY_NORMAL, UINTnStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTESlpSecurityAttrs = NULL );

           函数具体参数可以查MSDN,我们常用的就是前面两个参数,第一个是是指定我们创建的线程所执行的代码,第二个参数是我们要传递给线程的数据结构。

           本程序因为有六位哲学家,所以我创建了6个线程,并为其指定了执行函数         

	CWinThread *pthread[6];
        for(int i=0;i<6;i++)
            pthread[i]=AfxBeginThread(Eat,&imageinfo[i]);
          其中Eat是我指定线程执行的代码,因为本演示程序中六位哲学家做的事都是一样的,所以每位哲学家线程的执行代码也是一样的,imageinfo是我要从主程序中传递给线程的一些基本数据信息。          

struct ImageInfo
{
  int index;                                   //索引,表示当前的是几号哲学家线程在执行
  CPoint pt_chair;                         //椅子的坐标
  CPoint pt_chopstick_middle[2]; //
  CPoint pt_chopstick_start[2];     //叉子的坐标信息
  CPoint pt_chopstick_end[2];      //
  double radius_chair;                 //椅子半径
  double radius_table;                 //桌子的半径
  CWnd* pWnd;                          //主程序窗口的句柄
  CRect rect;   
  HWND edit;                             //右边相应编辑框的句柄
};
        
UINT Eat(LPVOID param)
{   
	while (true)                     //设置为无线循环,好让线程一直运行下去
	{

	ImageInfo *imageinfo=(ImageInfo*)param;
	CDC *pDC=imageinfo->pWnd->GetDC();               //获得主程序窗口的DC
       //char textbuff[1024];
	//::GetWindowText(imageinfo->edit,textbuff,1024);
	CString text;
	CString temp;
	bool caneat=FALSE;                                              //标识变量,验证当前哲学家线程是否可以就餐
	CBrush brush_think,brush_eat,brush_wait,*oldbrush;  //表示各种状态的画刷和画笔
	CPen whitepen,blackpen,backpen,*oldpen;

	whitepen.CreatePen(PS_SOLID,4,RGB(255,255,255));
	blackpen.CreatePen(PS_SOLID,4,RGB(0,0,0));
	backpen.CreatePen(PS_SOLID,4,RGB(240,240,240));
	
	
	brush_think.CreateSolidBrush(RGB(255,0,0));
	brush_eat.CreateSolidBrush(RGB(0,255,0));
	brush_wait.CreateSolidBrush(RGB(0,0,255));
	CRect rect; 
 
	

	//思考阶段
	temp.Format("开始思考!");	
	//text.Format(textbuff);
	//text=text+"\r\n"+temp;
	::SetWindowText(imageinfo->edit,(LPCTSTR)temp);	

	oldbrush=(CBrush*)pDC->SelectObject(&brush_think);	
	
	rect.left=imageinfo->pt_chair.x-imageinfo->radius_chair;
	rect.right=imageinfo->pt_chair.x+imageinfo->radius_chair;
	rect.top=imageinfo->pt_chair.y-imageinfo->radius_chair;
	rect.bottom=imageinfo->pt_chair.y+imageinfo->radius_chair;
	pDC->Ellipse(rect);
	//num.Format("%d",imageinfo->index+1);
	//pDC->DrawText(num,rect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
	pDC->SelectObject(oldbrush);

    g_cls.Lock();                  //g_cls为临界区变量,此处进入临界区
	if (chopsticks[imageinfo->index]==1&&chopsticks[(imageinfo->index+5)%6]==1)
	{		
		chopsticks[imageinfo->index]=0;
		chopsticks[(imageinfo->index+5)%6]=0;
		caneat=TRUE;
	}
    g_cls.Unlock();          //退出临界区
    Sleep(2000);
	if (FALSE==caneat)
	{           

       temp.Format("没有拿到两个叉子,无法吃饭!");  
       ::SetWindowText(imageinfo->edit,(LPCTSTR)temp);
	   Sleep(1000);
	   temp.Format("开始等待...");  
       ::SetWindowText(imageinfo->edit,(LPCTSTR)temp);

	   oldbrush=(CBrush*)pDC->SelectObject(&brush_wait);
	   pDC->Ellipse(rect);
	   pDC->SelectObject(oldbrush);
       //pDC->DrawText(num,rect,DT_CENTER|DT_SINGLELINE|DT_VCENTER);
	   Sleep(2000);
	}
	if (TRUE==caneat)
	{
	
		temp.Format("成功拿到两个叉子,开始吃饭!");  
        ::SetWindowText(imageinfo->edit,(LPCTSTR)temp);
		Sleep(1000);
    	temp.Format("吃饭...",imageinfo->index);
	//::GetWindowText(imageinfo->edit,textbuff,1024);
	//text.Format(textbuff);
	//text=text+"\r\n"+temp;
	    ::SetWindowText(imageinfo->edit,(LPCTSTR)temp);

	    //吃饭的状态,拿起筷子
        oldpen=(CPen*)pDC->SelectObject(&blackpen);
	    pDC->MoveTo(imageinfo->pt_chair);
	    pDC->LineTo(imageinfo->pt_chopstick_middle[0]);
	    pDC->MoveTo(imageinfo->pt_chair);
	    pDC->LineTo(imageinfo->pt_chopstick_middle[1]);
        pDC->SelectObject(oldpen);

    	oldpen=(CPen*)pDC->SelectObject(&whitepen);
	    pDC->MoveTo(imageinfo->pt_chopstick_middle[0]);
     	pDC->LineTo(imageinfo->pt_chopstick_end[0]);
	    pDC->MoveTo(imageinfo->pt_chopstick_middle[1]);
	    pDC->LineTo(imageinfo->pt_chopstick_end[1]);  	                 
	 	pDC->SelectObject(oldpen);
		
		oldbrush=(CBrush*)pDC->SelectObject(&brush_eat);
	    pDC->Ellipse(rect);
		pDC->SelectObject(oldbrush);

		//pDC->DrawText(num,rect,DT_VCENTER|DT_CENTER|DT_SINGLELINE);
		

	Sleep((imageinfo->index+1)*1000);
	
	//吃完饭,放下筷子
	oldpen=(CPen*)pDC->SelectObject(&blackpen);
    pDC->MoveTo(imageinfo->pt_chopstick_middle[0]);
	pDC->LineTo(imageinfo->pt_chopstick_end[0]);
	pDC->MoveTo(imageinfo->pt_chopstick_middle[1]);
	pDC->LineTo(imageinfo->pt_chopstick_end[1]);
    pDC->SelectObject(oldpen);

	oldpen=(CPen*)pDC->SelectObject(&backpen);
    pDC->MoveTo(imageinfo->pt_chair);
	pDC->LineTo(imageinfo->pt_chopstick_middle[0]);
	pDC->MoveTo(imageinfo->pt_chair);
	pDC->LineTo(imageinfo->pt_chopstick_middle[1]);
    pDC->SelectObject(oldpen);
	oldbrush=(CBrush*)pDC->SelectObject(&brush_eat);
	pDC->Ellipse(rect);
		pDC->SelectObject(oldbrush);

	g_cls.Lock();
	chopsticks[imageinfo->index]=1;
	chopsticks[(imageinfo->index+5)%6]=1;    
	g_cls.Unlock();
	temp.Format("吃完饭,放下叉子!");  
	::SetWindowText(imageinfo->edit,(LPCTSTR)temp);
	Sleep(1000);
	caneat=FALSE;	
	}  
     
	}
	//::SetWindowText(imageinfo->edit,"1");
    
	return TRUE;
}
       线程处理函数里面主要的就是一个临界区变量g_cls,其是MFC提供的一个同步互斥类CCriticalSection的一个对象,其管理控制线程对共享资源的访问,MFC总共提供了四种同步互斥类可以用以处理共享变量的问题(CMutex, CSemaphore CEvent,CCriticalSection,各有区别,具体可以去网上查找相关资料或者看MSDN )。

        临界区对象要和共享变量一起放在MFC框架代码的外面        

//全局变量
CCriticalSection g_cls;//临界区对象
int chopsticks[6];
        在g_cls.Lock()和g_cls.Unlock()之间的变量将限制为同一时刻只能让一位线程访问。

 二、演示程序的图形绘制部分

         这部分主要就是计算坐标有点麻烦,绘图用CDC自带的一些绘图函数绘制而成。

void CPhilosophy_EatingDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
	
	//获得显示框的大小rect
	CRect rect;
	GetDlgItem(IDC_STATIC_TABLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);
     /***************************************************************/
	//计算这个rect的中心坐标,求出桌子中心的坐标,以及其外接矩形的坐标//
	 /***************************************************************/
    
	//1.计算桌子中心的坐标
	
	pt_table.x=(rect.right+rect.left)/2;
	pt_table.y=(rect.bottom+rect.top)/2;
    
	//2.计算桌子外接矩形的大小
    
	radius_table=(rect.right-rect.left)/3;//我们去显示框长度的1/3做为桌子的半径
	
	rect.left=pt_table.x-radius_table;
	rect.right=pt_table.x+radius_table;
	rect.top=pt_table.y-radius_table;
	rect.bottom=pt_table.y+radius_table;
     
    CDC *pDC=GetDC();	
	pDC->Ellipse(rect);	

	//计算每个椅子的中心坐标
	double pi=3.1415926535;
	double sin_temp,cos_temp;
	double x=pi/3;
	for (int i=0;i<6;i++)	{   
		
		sin_temp=sin(i*x);
		cos_temp=cos(i*x);
        pt_chair[i].x=pt_table.x-(3*radius_table)/2.0*sin_temp;
		pt_chair[i].y=pt_table.y-(3*radius_table)/2.0*cos_temp;
	}
	radius_chair=radius_table/4.0;
    
    //计算每个椅子的外接矩形,并画出椅子
	for (i=0;i<6;i++)
	{
		rect.left=pt_chair[i].x-radius_chair;
		rect.right=pt_chair[i].x+radius_chair;
		rect.top=pt_chair[i].y-radius_chair;
		rect.bottom=pt_chair[i].y+radius_chair;
		pDC->Ellipse(rect);
		CString num;
		num.Format("%d",i+1);
		pDC->DrawText(num,&rect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);//单行居中
	}
    
    
	//计算叉子的起始坐标和终止
	for (i=0;i<6;i++)
    {   
        

		pt_chopstick_start[i].x=pt_table.x-radius_table*sin(i*x+pi/6);
		pt_chopstick_start[i].y=pt_table.y-radius_table*cos(i*x+pi/6);

		pt_chopstick_end[i].x=pt_table.x-25*sin(i*x+pi/6);
		pt_chopstick_end[i].y=pt_table.y-25*cos(i*x+pi/6);

    }
    



    //画筷子
	CPen pen(PS_SOLID,4,RGB(0,0,0));
	pDC->SelectObject(&pen);
    for (i=0;i<6;i++)
    {
        pDC->MoveTo(pt_chopstick_start[i]);
		pDC->LineTo(pt_chopstick_end[i]);
    }  
}
              上述代码中的相关变量在CPhilosophy_EatingDlg中声明:

        

class CPhilosophy_EatingDlg : public CDialog
{
// Construction
public:
	CPhilosophy_EatingDlg(CWnd* pParent = NULL);	// standard constructor
public:
	ImageInfo imageinfo[6];
	CWinThread *pthread[6];
	CPoint pt_table;
	CPoint pt_chair[6];
	double radius_table;
	double radius_chair;
	CPoint pt_chopstick_start[6];
	CPoint pt_chopstick_end[6];
	//CPoint pt_chopstick_start[6];

	BOOL m_continue;

 

进程运行时图形变化的绘制已经在进程函数Eat中了,大家可以自己看下,不是很复杂。  

   三、两个按钮的代码

           开始和停止按钮的代码,没什么内容,我把代码贴下,大家可以参考~  

void CPhilosophy_EatingDlg::OnButtonStart() 
{
	// TODO: Add your control notification handler code here
	for (int m=0;m<6;m++)
	{
		chopsticks[m]=1;
	}

    
	for(int i=0;i<6;i++)
	{   
		switch(i)
		{
		case 0:
			 imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE1)->m_hWnd;
			 break;
		case 1:
			imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE2)->m_hWnd;
			break;
		case 2:
			imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE3)->m_hWnd;
			break;
		case 3:
			imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE4)->m_hWnd;
			break;
		case 4:
			imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE5)->m_hWnd;
			break;
		case 5:
            imageinfo[i].edit=GetDlgItem(IDC_EDIT_STATE6)->m_hWnd;
			break;
		default:
			break;
		}
		imageinfo[i].pWnd=this;
        
		imageinfo[i].index=i;
		imageinfo[i].pt_chair=pt_chair[i];
		imageinfo[i].pt_chopstick_middle[0]=pt_chopstick_start[i];
		imageinfo[i].pt_chopstick_middle[1]=pt_chopstick_start[(i+5)%6];
		imageinfo[i].radius_chair=radius_chair;
		imageinfo[i].radius_table=radius_table;
		imageinfo[i].pt_chopstick_end[0]=pt_chopstick_end[i];
		imageinfo[i].pt_chopstick_end[1]=pt_chopstick_end[(i+5)%6];
		pthread[i]=AfxBeginThread(Eat,&imageinfo[i]);
    }


	
}

void CPhilosophy_EatingDlg::OnButtonStop() 
{
	// TODO: Add your control notification handler code here	
	if (FALSE==m_continue)
	{
		GetDlgItem(IDC_BUTTON_STOP)->SetWindowText("继续演示");
		for (int i=0;i<6;i++)
		{
		pthread[i]->SuspendThread();
		}
		m_continue=TRUE;
		return;
	}
	else
	{   
		GetDlgItem(IDC_BUTTON_STOP)->SetWindowText("暂停演示");
		for (int i=0;i<6;i++)
		{
			pthread[i]->ResumeThread();
		}
		m_continue=FALSE;
		return;
	}
	

}
            主要的就是这么多了,大家结合着代码看看,有不明白的欢迎和我交流~~~

            贴几个程序运行的图片

            
           源代码下载:http://download.csdn.net/detail/txg703003659/4069141

            

           我把代码贴出来是希望能帮助一些人,如果有不明白的可以和我讨论,希望不要将本程序直接拷贝,谢了!


 

         

         

  • 22
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
哲学家就餐问题是一个经典的并发问题,它描述了五位哲学家共用一张桌子进餐的场景。每位哲学家需要交替地进行思考和进餐,而进餐需要使用两个叉子,每位哲学家身边有一只叉子,为了进餐,他需要同时拿起两只叉子。如果周围的哲学家同时也需要使用同侧的叉子,则会发生死锁。 解决这个问题的方法有很多,其中一种常见的方法是使用资源分级锁(Resource Hierarchy Solution)。具体实现时,可以给每个叉子编号,每位哲学家只能先拿起编号低的叉子,再拿起编号高的叉子,这样就不会出现死锁。同时也可以使用信号量(Semaphore)等并发控制工具来实现这个问题的解决。 以下是一个 Python 实现的哲学家就餐问题的例子: ```python import threading class Philosopher(threading.Thread): def __init__(self, left_fork, right_fork): threading.Thread.__init__(self) self.left_fork = left_fork self.right_fork = right_fork def run(self): while True: self.left_fork.acquire() locked = self.right_fork.acquire(False) if locked: break self.left_fork.release() else: return self.dine() self.left_fork.release() self.right_fork.release() def dine(self): print("{} is dining".format(threading.current_thread().getName())) def main(): forks = [threading.Lock() for n in range(5)] philosophers = [Philosopher(forks[n], forks[(n + 1) % 5]) for n in range(5)] for p in philosophers: p.start() if __name__ == "__main__": main() ``` 在这个例子中,我们创建了五个 Lock 对象来表示五个叉子,每个哲学家都需要先拿起左边的叉子,再拿起右边的叉子,然后就可以进餐了。如果右边的叉子已经被其他哲学家拿起了,则当前哲学家会先释放左边的叉子,等待其他哲学家释放右边的叉子后再重试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值