我们可以把进程看作是一个正在独立运行的独立的程序,在内存中有其完备的数据空间和代码空间。
线程是在某一个进程中单独运行的单元,也就是说,线程存在于进程之中。一个进程有多个或者单个线程组成,各个线程共享相同的代码和全局数据,但是各有其自己的堆栈。由于每一个线程有一个堆栈,所以局部变量对每一个线程来说是私有的。由于所有的线程共享相同的代码和全局数据,它们比进程更紧密,比单独的进程间更趋向于相互作用。
一个进程与线程的最重要的区别是:线程拥有自己的全局数据。线程存在于进程中,因此一个进程的全局变量有所有的线程共享。
我们编写一个程序,来说明一下,为什么使用多线程!
新建一个基于对话框的应用程序,在主对话框IDD_SINGLETHEAD_DIALOD中添加一个Button按钮,设置属性,使它的响应函数为延迟5秒,代码如下:
void CSingThreadDlg::OnFiveSecondDelag( ) { Sleep(5000); }
发现整个对话框处于死机的状态,就是因为单线程状态下,程序只能等待。这时候有必要采用多线程的方法。
一。怎样创建线程
创建多线程的函数有很多:(1)全局函数AfxBeginThread( ... );
(2) CreatThread( ... );
(3)_beginthread( )和_beginthreadex()函数
我们详细讲解第一个函数:
MSDN上给出如下定义:
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
各个参数的说明如下:
返回值: 一个指向新线程的线程对象
pfnThreadProc : 线程的入口函数,声明一定要如下: UINT MyThreadFunction( LPVOID pParam );
pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
nPriority : 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.
nStackSize : 指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈
dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值:
CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用: ResumeThread
0 : 创建线程后就开始运行.
lpSecurityAttrs : 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL ,那么新创建的线程就具有和主线程一样的安全性.
之所以有两个函数,是因为线程分为两种:用户界面线程、工作者线程(我们将在下一节中介绍)
二.怎样终止一个线程
终止线程的方法有三种:
(1)可以在自身内部调用AfxEndThread()来终止自身的运行;
(2)可以在线程的外部调用Terminate()来强行终止一个线程的运行,然后调用closehandle()来释放线程所占用的堆栈。Terminate()有资源泄漏,因此要慎用这个函数。
(3)设置一个全局变量,改变这个全局变量,通知线程的执行函数返回,则该线程终止。这种方式通过线程执行函数的返回来终止线程,是一种安全的方式。
三.编写线程函数
需要注意的是:线程函数必须为一个独立的函数,因而,线程函数通常为全局函数或者静态成员函数。如果该线程函数一定要定义为一个类的成员函数,那么一般要把这个函数声明为静态的。
下面我们用一个实例来说明线程和进程的概念:我们写一个简单的伪代码:
(1)首先建立一个基于单文档类的MFC程序,不要忘记要支持Winsock类。
(2)在菜单中添加一个菜单项,并分别添加三个子菜单:启动线程、结束线程、启动进程。
(3)为启动线程添加消息消息相应函数:
VOID Cview::OnStartThread( ) { // TODO: Add your command handler code here flag = false; //flag 为标志位,用于结束线程。类似于第三种结束线程的方式。 if(numThread<2) //numThread为允许开启的线程的数目,在这里,我们允许的数目为2个 { numThread++; AfxBeginThread(AddPicture,this);//AddPicture 为线程函数 }
}
(3)添加全局的线程函数(如前所述,必须为全局或者静态成员函数,这里我们用全局的函数)
1 UINT AddPicture(LPVOID p) 2 { 3 if (TRUE ==flag) 4 { 5 switch(numThread) 6 { 7 case 1: 8 { 9 AfxMessageBox("线程1启动");//执行功能1 10 break; 11 } 12 case 2: 13 { 14 AfxMessageBox("线程2启动");;//执行功能2 15 break; 16 } 17 default: 18 AfxEndThread(0);//结束进程 19 //return 0; 20 } 21 22 23 } 24 else 25 AfxEndThread(0); 26 return 0; 27 28 }
(4)为“结束线程”添加消息消息相应函数
void ampleView::IsStop() { flag = FALSE; numThread = 0; }
这样,我们就基本完成了多线程的基本功能,当你点击“运行线程”的菜单项时,就会在客户区出现“线程1运行”的对话框;当你再次点击时,会出现
“线程2运行”的对话框;当你点击“结束线程”时,就会终止整个线程的运行。
--------------------------------------------------------------------------------------------------------------------------------------------
下面我们再在上面程序的基础上,打开一个进程,用于加深进程和线程的理解:
再建立一个菜单项,名称为--开启进程
添加代码如下:
void Cview::OnCreatePress() { TCHAR currentPath[MAX_PATH]; GetModuleFileName(NULL,currentPath,MAX_PATH); PROCESS_INFORMATION pi;//进程信息 STARTUPINFO si = {sizeof(si} }; BOOL ret = CreateProcess(NULL,currentPath,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi); if(ret) { //父进程与子进程之间不需要信息的交换,因此可以关闭子进程的主线程句柄和子程序句柄 CloseHandle(pi.hThread); //关闭子进程语句 CloseHandle(pi.hProcess); } }
完成!当我们点击开启线程后,就会跳出一个和原界面相同的进程界面。