最近有了很多想法,想把我用过的东西都吃透,这样才不会变成所谓的“样样通样样松”。我是新手,老鸟请飘过,当然,这篇小心得如果有什么毛病,还请指出来。先行谢过!

其实我本来想把博客当作自己的日记,记录下学习的点点滴滴,写下的就代表是学会的东西,人家说好记性不如烂笔头嘛。

一直以来对多线程这块就迷迷糊糊的,用得不太多,即便是用了,也是把以前写的代码拿出来,稍微修改一下,就适应了新的需求。也看过一些资料,但都没实践过,所以就马马虎虎地,能够适应工作需求就得过且过。其实这种思想是非常错误的。做技术一定要踏实,否则就无法成长。

闲话少说书归正传,接下来的几篇就把多线程的东西学习、总结一下。先上一段万金油:
线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,那就是程序本身。

自从有了线程,这个世界就变得吵起来了,线程的周期、调度与优先级、资源共享、线程同步、守护线程、死锁、信号量。。后面咱们再慢慢研究这些东西吧,今天先来简单明白线程到底是什么,怎么应用在编程中。

老惯例,上个程序吧。建立一个标准的基于对话框的MFC程序,拖一个edit控件和一个button在上面,资源命名分别为ID_EDIT_NUMBER和ID_BUTTON_START。给edit控件关联一个变量CEdit * m_editNumber。弄差不多这个样子就行。

 先写一个线程函数,告诉电脑在这个线程里要做什么。

 
  
  1. DWORD _stdcall ThreadProc(LPVOID lpParameter) 
  2.     CMultithreadTestDlg * dlg = (CMultithreadTestDlg*) lpParameter; 
  3.     CString szCounter; 
  4.  
  5.     for(int i = 0; i < 10000; i++) 
  6.     { 
  7.         szCounter.Format(_T("%d"), i); 
  8.         dlg->m_editNumber.SetWindowTextW(szCounter); 
  9.         szCounter.ReleaseBuffer(); 
  10.     } 
  11.  
  12.     return 0; 

很简单,就是让edit控件显示不停增加的数字。 

接下来就要想办法启动这个线程。

在CMultithreadTestDlg中添加一个成员变量HANDLE m_hThread。双击start按钮,编写按钮的单击事件。

 
  
  1. void CMultithreadTestDlg::OnBnClickedButtonStart() 
  2.     // TODO: Add your control notification handler code here 
  3.     m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, NULL); 

CreateThread函数的原型如下:

 
  
  1. HANDLE WINAPI CreateThread( 
  2.   __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes, 
  3.   __in       SIZE_T dwStackSize, 
  4.   __in       LPTHREAD_START_ROUTINE lpStartAddress, 
  5.   __in_opt   LPVOID lpParameter, 
  6.   __in       DWORD dwCreationFlags, 
  7.   __out_opt  LPDWORD lpThreadId 
  8. ); 

第一个参数是安全属性,指向一个LPSECURITY_ATTRIBUTES类型的结构体,一般设为NULL;

第二个参数是线程的堆栈大小,如果不是内存特别紧张的话,就设为0,表示windows将动态调整堆栈的大小;

第三个参数是指向线程函数的指针,其实就是函数名。函数名随便起,但是在声名函数时必须要遵守形式

 
  
  1. DWORD WINAPI ThreadProc(LPVOID lpParameter)  

否则就无法成功调用;

第四个参数是向线程函数传递的参数,不传递时就设为NULL;

第五个参数是线程标志,有两个可取值:

     1). CREATE_SUSPENDED,表示创建后立即挂起

     2). 0,表示正常创建,创建后立刻运行

第六个参数用来保存新建线程的ID,如果不需要处理线程ID的话,则可传入NULL。

返回值是线程的句柄。

运行时效果如下

这个程序其实是有风险的,风险有二:

    1). 在MFC程序中,应该尽量使用AfxBeginThread方法来创建线程。

    2). 如果我不停地按start,一会内存就用光了=。=

2的解决方法就不上了,无非是使用标志位,线程没跑完之前不再创建新的线程。

来说说AfxBeginThread。这是MFC中的比较安全的线程创建方法。函数原型如下:

 
  
  1. CWinThread* AfxBeginThread( 
  2.    AFX_THREADPROC pfnThreadProc, 
  3.    LPVOID pParam, 
  4.    int nPriority = THREAD_PRIORITY_NORMAL, 
  5.    UINT nStackSize = 0, 
  6.    DWORD dwCreateFlags = 0, 
  7.    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  
  8. ); 
  9. CWinThread* AfxBeginThread( 
  10.    CRuntimeClass* pThreadClass, 
  11.    int nPriority = THREAD_PRIORITY_NORMAL, 
  12.    UINT nStackSize = 0, 
  13.    DWORD dwCreateFlags = 0, 
  14.    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  
  15. ); 

有两个可以重载的函数,常用的是第一个。也能看出来,第一个函数与CreateThread()的参数其实是差不多的,只不过顺序不太一样。需要注意的是第二个重载函数,参数一是CRuntimeClass * pThreadClass,CRuntimeClass是个结构体,MSDN里的解释是“The RUNTIME_CLASS of an object derived from CWinThread.”为此我特意看了一下AfxBeginThread的源代码,其中有如下一行:

 
  
  1. ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread))); 

表明RUNTIME_CLASS是个宏定义。

 
  
  1. #define RUNTIME_CLASS(class_name) (class_name::GetThisClass()) 

也就是用这个宏将线程类指针转换为指向CRuntimeClass的对象指针。

那么新的线程创建语句就变为了:

 
  
  1. CWinThread * m_thread;  // m_thread为成员变量
  2. m_thread = AfxBeginThread(ThreadProc, this); 

而且需要将线程函数的声明修改一下:

 
  
  1. UINT ThreadProc(LPVOID lpParameter) 

线程执行的中间是可以暂停的,使用DWORD CWinThread::SuspendThread()函数即可。暂停后可以使用DWORD CWinThread::ResumeThread()函数使线程恢复运行。

这是基本用法,至于一些高级点儿的东西,明儿继续。

PS:正所谓懂得越多就发现懂得越少,今×××资料,又搜出好多没听过的东西=。=,全部记在本子上,逐个消灭之。。

PSS:下一个目标,看明白与这个网页相关联的东西。。http://en.wikipedia.org/wiki/Thread_(computing)

PSSS:这玩意儿真形象。。