打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
线程间的同步概述

线程间的同步概述

1.前言

前面几篇文章着重介绍了多线程的三种创建方式及多线程间的4种通信方式,并采用大量的实例演示,相信大家对线程的创建和使用有了一定的了解。若还不了解请复习下前面的文章,多动手写代码和调试,光看不练,假把式。

今天先请大家看看下面一个多线程程序,操作很简单,就是创建9个线程,并输出相应的线程编号(即报数)。主要代码如下:

  1. //声明线程处理函数  
  2. <strong><span style="color:#ff0000;">unsigned __stdcall</span></strong>ThreadFunc( void* pArguments);//工作线程函数  
  3. HANDLE m_handle[9];//线程句柄列表  
  4. CListBox m_List; //数据列表控件  
  5. /////////////////////////////////////////////////  
  6. int g_nCount= 0;//这个是<strong><span style="color:#ff0000;">全局变量</span></strong>,用于线程报数(计数)  
  7. //演示开始:创建线程  
  8. void CThreadProblem1Dlg::OnBnClickedButton1()  
  9. {  
  10.       // TODO: 在此添加控件通知处理程序代码  
  11.       GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);  
  12.       m_List.ResetContent();//清空列表  
  13.       g_nCount = 0; //重置报数,  
  14.       SetDlgItemInt(IDC_EDIT_NUM,++m_nNum); //显示操作的次数  
  15.       //创建多线程  
  16.       for (int i=0;i<9;i++)  
  17.       {  
  18.            m_handle[i] = (HANDLE)<strong><span style="color:#ff0000;">_beginthreadex</span></strong>(NULL,0, ThreadFunc,&m_List,0, NULL);   
  19.       }  
  20.       //WaitForMultipleObjects(10, handle, TRUE, INFINITE);  //在此处等待退出,将发现程序假死了。所以采用线程的方式等待  
  21.     _beginthreadex(NULL,0, WaitThread,<strong><span style="color:#ff0000;">this</span></strong>, 0, NULL);    //等待上述的个线程都退出  
  22. }  
  23. //工作线程函数  
  24. unsigned __stdcall ThreadFunc(void* pArguments)  
  25. {  
  26.       Sleep(100);//相关处理  
  27.       g_nCount++; //计数加  
  28.       CListBox *pList= (CListBox*)pArguments;  
  29.       CString str;  
  30.       str.Format("  子线程ID号为%4d 报数为:%d",GetCurrentThreadId(),g_nCount);  
  31.     pList->AddString(str);//输出  
  32.       Sleep(100);//相关处理  
  33.       return 0;   
  34. }  
  35. //线程函数:等待个演示线程都退出再使能开始按钮  
  36. unsigned __stdcall WaitThread(void* pArguments)  
  37. {  
  38.       CThreadProblem1Dlg *pMainDlg= (CThreadProblem1Dlg *)pArguments;  
  39.       <strong><span style="color:#ff0000;">WaitForMultipleObjects</span></strong>(9,pMainDlg->m_handle,TRUE, INFINITE);  //等待所有线程都结束  
  40.       EnableWindow(GetDlgItem(AfxGetApp()->m_pMainWnd->m_hWnd,IDC_BUTTON1),TRUE);//使能开始按钮  
  41.       return 0;  
  42. }  

当运行一次OnBnClickedButton1()函数,将显示下面的结果:

你一看,没错呀,就应该是这样的,没有错呀!多运行几次也是这样的。但我要肯定的告诉你,上面的程序是有严重的问题,而运行结果也欺骗了你。正是运行结果大大蒙骗了你的理智和大脑。你发现问题了吗?(提示:不是报数顺序的问题)

正是该错误有隐蔽性,你很难从结果中发现问题,除非你运气特别好,一运行就能重现问题,但作为程序员,你决不能仅靠运气,不可能你每次的运气都这么好。

多运行几次上面的程序,你有可能发现问题,现在我把该程序改进下,使其具有自动识别错误的智能,你一眼就能发现问题的。

改进点:添加结果检测功能,若正常,其线程的报数应该为1-9,有可能顺序有变,但总和为45=1+2+3+4+5+6+7+8+9。程序将一直循环到程序退出。若不等于45就退出循环,表示有问题,即让程序一直运行,直到有错误为止。前面的OnBnClickedButton1()函数和ThreadFunc()函数保存不变,WaitThread()函数添加一个判断语句,改进程序如下:

  1. //线程函数:等待个演示线程都退出再使能开始按钮  
  2. unsigned __stdcall WaitThread(void* pArguments)  
  3. {  
  4.       CThreadProblem1Dlg *pMainDlg= (CThreadProblem1Dlg *)pArguments;  
  5.       WaitForMultipleObjects(9, pMainDlg->m_handle, TRUE,INFINITE); //等待所有线程都结束  
  6.       EnableWindow(GetDlgItem(AfxGetApp()->m_pMainWnd->m_hWnd,IDC_BUTTON1),TRUE);//使能开始按钮  
  7.      <span style="color:#ff0000;"><strong> if(pMainDlg->m_bAuto&& !pMainDlg->IsError())  
  8.       {//若自动使能,则继续下轮操作  
  9.            pMainDlg->OnBnClickedButton1();  
  10.       }</strong></span>  
  11.       return 0;  
  12. }  
  13. //添加IsError()函数,用以判断结果是否正确。  
  14. // 自动判断每次运行结果是否正确  
  15. bool CThreadProblem1Dlg::IsError(void)  
  16. {  
  17.       int nValue[9]={0};  
  18.       int nResult = 0;  
  19.       CString szText;  
  20.       for(int i=0;i<9;i++)  
  21.       {//得到各个线程的报数  
  22.            m_List.GetText(i,szText);  
  23.         szText = szText.Right(1);  
  24.            nValue[i] = atoi(szText);  
  25.            nResult += nValue[i];  
  26.       }  
  27.       //判断是否有相同的值出现  
  28.       if (nResult != <span style="color:#ff0000;"><strong>45</strong></span>)  
  29.       {//有错误  
  30.            return true;  
  31.       }  
  32.       return false;  
  33. }  

再运行上面的程序,选中“自动判断”,程序将很快不停的运行,但很快将又停下来,运行结果如下图所示,有可能你的结果和我的不一样,但类型差不多的。

工程源码下载:http://download.csdn.net/detail/cbnotes/5015914

现在你发现问题了吗?对了,报数出现相同数了(见上图出现两个“2”)。

你可能要问,怎么会这样呢?

这就是多线程最容易出现的问题,也是多线程编程的难点和核心。再说说上面程序,创建了9个线程,这9个线程是同时运行的(即并行运行),它们都要修改变量全局g_nCountg_nCount++;),就有可能两个或多个线程同时读取到g_nCount,而当前的g_nCount已经被其它线程修改,即输出的不是线程当前的值。这和单线程的顺序执行是有很大不同的。

那有什么方法解决上面的问题吗?当然有,这就是在江湖中大名鼎鼎的线程同步技术,而且系统提供了多种线程同步的技术/方法。

2.什么是同步

“同步”不是指平常所说的两件事情同时进行。它的目的是使多个线程之间协调工作,而且常常是避免两个线程同时进行某些操作,比如同时访问同一个共享资源。一般来说,同步是通过暂时将会发生冲突操作的某个线程暂停执行(称为阻塞线程),然后等待不会冲突时再继续执行。

3.需要同步的情况

3.1、多个线程同时访问同一对象时

MFC对象在对象级不是线程安全的,只有在类级才是。如:两个线程可以安全地使用两个不同的CString对象,但同时使用同一个CString对象就可能产生问题。如果必须使用同一个对象,那么应该采取适当的同步化措施。

3.2、多个线程之间需要协调运行

例如,如果第二个线程需要等待第一个线程完成到某一步时才能运行,那么该线程应该暂时挂起以减少对CPU的占用时间,提高程序的执行效率。当第一个线程完成了相应的步骤后,应该发出某种信号来激活第二个线程。

4.Windows中的4种线程同步技术

4.1、Events(事件)——CEvent

作为标志在线程之间传递信号。简单地说,类似一个布尔型变量的开关作用。

4.2、Critical Sections(临界段)——CCriticalSection

在进程中作为关键字以获得对共享资源的访问

4.3、Mutexes(互斥量)——CMutex

与临界段的工作方式相似,只是该对象可以用于多进程中的线程同步,而不是用于单进程中

4.4、Semaphores(信号量)——CSemaphore

在给定的限制条件下,允许多个进程同时访问共享资源

关于这四种同步方法/技术我将一个一个给大家介绍和实例演示,欢迎大家继续关注。

=====================================================

转载请标明出处,谢谢。http://blog.csdn.net/cbNotes

=====================================================

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
类成员函数作为多线程的入口函数的实现方法
Windows进程、线程之间同步方式概述
临界区,互斥量,信号量,事件的区别
delphi 不规则窗体与桌面宠物
线程函数的设计以及线程同步要点(MsgWaitForMultipleObjects等)
利用Thunk技术将Win32回调函数转换为C成员函数
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服