进程、线程、多线程
- 一、概念
- (1)进程
- 狭义定义:进程就是一段程序的执行过程。
- 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
- 简单的来讲进程的概念主要有两点:
- 第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
- 第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
- 进程状态:就绪、运行和阻塞。就绪状态其实就是获取了出cpu外的所有资源,只要处理器分配资源就可以马上执行。就绪状态有排队序列什么的,排队原则不再赘述。运行态就是获得了处理器分配的资源,程序开始执行。阻塞态,当程序条件不够时候,需要等待条件满足时候才能执行,如等待i/o操作时候,此刻的状态就叫阻塞态。
- (2)线程
- (3)进程与线程的区别:
- 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
- 1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
- 2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
- 3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
- (4)多线程:
- 多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。
- 多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。
- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。
- 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进 程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也 较为广泛。
- 多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程, 操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常 活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
- 二、建立线程
- 使用CreateThread()建立线程
- 1、函数原型
- 该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄
- 函数原型:HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
- DWORD dwStackSize,
- LPTHREAD_START_ROUTINE lpStartAddress,
- LPVOID lpParameter,
- DWORD dwCreationFlags,
- LPDWORD lpThreadId);
- 各参数说明:
- lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
- dwStackSize:指定了线程的堆栈深度,一般都设置为0;
- lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;
- lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
- dwCreationFlags: 控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线 程后,该线程处于挂起状态,并不马上执行,直至函数 ResumeThread被调用;
- lpThreadId:该参数返回所创建线程的ID;
- 返回值:如果创建成功则返回线程的句柄,否则返回 NULL。
- 2、DWORD SuspendThread(HANDLE hThread);
- 3、 DWORD ResumeThread(HANDLE hThread);
- 4、VOID ExitThread(DWORD dwExitCode);
- 5、 BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
- 6、PostThreadMessage()
- MFC多线程,AfxBeginThread()
- 函数原型
- (1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
- LPVOID pParam,
- nPriority=THREAD_PRIORITY_NORMAL,
- UINT nStackSize=0,
- DWORD dwCreateFlags=0,
- LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
- 参数含义:
- PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam); 请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
- pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
- nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
- nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果 nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
- dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
- lpSecurityAttrs:线程的安全属性指针,一般为 NULL;
- (2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
- int nPriority=THREAD_PRIORITY_NORMAL,
- UINT nStackSize=0,
- DWORD dwCreateFlags=0,
- LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
- 参数含义:
- pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其 它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,同主线程的机制几乎一样。
- m_hThread:当前线程的句柄;
- m_nThreadID:当前线程的ID;
- m_pMainWnd:指向应用程序主窗口的指针
- 下面我们对CWinThread类的数据成员及常用函数进行简要说明。
- BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
- UINT nStackSize=0,
- LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
- 该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。
- 一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
- virtual BOOL CWinThread::InitInstance();
- virtual int CWinThread::ExitInstance();
- 线程的退出
- 三、线程间通讯
- 四、线程的同步
- 虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行 是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现操作错误;又例如,对于银行系统的计算机来 说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能 在读的时候只有一部分数据被更新过。
- 使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:
- 1)临界区(CCriticalSection)
- 2)事件(CEvent)
- 3)互斥量(CMutex)
- 4)信号量(CSemaphore)
- 通过这些类,我们可以比较容易地做到线程同步。
- 五、线程的优点/缺点:
- 线程的优点:
- 1、创建一个新线程的代价要比创建一个新进程小得多
- 2、与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 3、线程占用的资源要比进程少很多
- 4、能充分利用多处理器的可并行数量
- 5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- 7、I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
- 线程的缺点:
- 1、性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 2、健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 3、缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 4、编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。