lesson14:多线程

来源:互联网 发布:淘宝卖家软件有哪些 编辑:程序博客网 时间:2024/06/06 09:16


1:进程
进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。
进程由两个部分组成:

  1、操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。

  2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。


进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。
单个进程可能包含若干个线程,这些线程都“同时” 执行进程地址空间中的代码。
每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,

系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个地址空间是4GB。

每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0x12345678,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0x12345678。当进程A中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。
4GB是虚拟的地址空间,只是内存地址的一个范围。在你能成功地访问数据而不会出现非法访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。
4GB虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓冲、非页面内存池的分配和进程页面表等使用,而用户方式分区使用的地址空间约为2GB,这个分区是进程的私有地址空间所在的地方。一个进程不能读取、写入、或者以任何方式访问驻留在该分区中的另一个进程的数据。对于所有应用程序来说,该分区是维护进程的大部分数据的地方。


2:线程
线程由两个部分组成:

  1、线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。

  2、线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。
线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄进程中的所有内存在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。
线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。
因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。

操作系统为每一个运行线程安排一定的CPU时间 ——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。
如果计算机拥有多个CPU,线程就能真正意义上同时运行了。


#include <windows.h>#include <iostream.h>DWORD WINAPI Fun1Proc(  LPVOID lpParameter   // thread data);DWORD WINAPI Fun2Proc(  LPVOID lpParameter   // thread data);int tickets=100;void main(){HANDLE hThread1;HANDLE hThread2;hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);CloseHandle(hThread1);CloseHandle(hThread2);Sleep(4000);}DWORD WINAPI Fun1Proc(  LPVOID lpParameter   // thread data){while(TRUE){if(tickets>0)cout<<"thread1 sell ticket : "<<tickets--<<endl;elsebreak;}return 0;}DWORD WINAPI Fun2Proc( LPVOID lpParameter   // thread data){while(TRUE){if(tickets>0)cout<<"thread2 sell ticket : "<<tickets--<<endl;elsebreak;}return 0;}


如何避免销售0这张票?

用线程同步可以避免,线程的同步有4中方法
临界区(CCriticalSection)
②事件(CEvent)
③互斥量(CMutex)
④信号量(CSemaphore)


互斥对象(就像一把钥匙):createmutex()创建互斥对象,并返回该互斥对象的句柄,(保护全局变量的一种有效方法)
互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量,一个线程ID和一个计数器。
ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
hMutex=CreateMutex(NULL,TRUE,"tickets");没有线程拥有该互斥对象时,该互斥对象为有信号状态
WaitForSingleObject(hMutex,INFINITE);  两种情况能等到互斥对象
ReleaseMutex(hMutex);  //线程ID设为0;线程设为以通知状态,就是有信号状态
哪个线程拥有互斥对象,才能使用该互斥对象的函数(所以,谁拥有,谁才能释放);


hMutex=CreateMutex(NULL,TRUE,"tickets");  //计数器加1
WaitForSingleObject(hMutex,INFINITE);     //计数器再加1
ReleaseMutex(hMutex);   //计数器减1
ReleaseMutex(hMutex);//再减1;
只有当计数器为0时,别的线程才可以使用


线程是靠cpu来运行的,cpu要运行一个线程(不说别的)最起码就是要占用cpu时间,象Windows这样的多任务操作系统,可以允许多个线程同时运行,所谓的同时运行并不是真正的同时运行,而是轮流运行不同的线程,因为cpu速度很快,如果线程不是很多,就会给用户有所有线程在同时运行的错觉。举个例子,系统中有10个线程要运行,如果要求在1秒内所有的线程都运行一遍,则每个线程可运行时间为10分之一秒,也就是如果一个线程已经运行了10分之一秒,系统会停止该线程(或称为挂起该线程),运行下一个线程,当又轮到挂起的线程运行时,系统会从该线程停止的地方运行,这种线程挂起是由系统进行的,即所谓的线程调度。有时候,我们的线程暂时没有数据处理,我们也可以通过一些API来使自己的线程挂起,当系统检测到线程被用户挂起时,就算轮到该线程系统也不会运行该线程,而是直接去运行下一个线程,这种情况下,除非用户使该线程退出挂起状态,否则系统不会运行该线程。从这个意义上来讲,一个线程挂起将会给其他线程赢得更多的运行时间(或机会),也就节约了CPU的时间资源。








































































































称为主线程。此后,该线程可以创建其他的线程。
0 0
原创粉丝点击