在传统操作系统中,拥有资源和独立调度的基本单位都是进程。引入线程后,线程是独立调度的基本单位,进程是拥有资源的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同的进程中进程的切换,则会引起线程的切换。
现在的浏览器为了增强稳定性,使用户有个好的上网体验,每打开一个网页就做为独立进程,当浏览器浏览的某个网页由于某种问题不得不退出的时候,不会影响到你同时打开的其它网页,因为其它网页在其它进程内存空间里,不在同空间。否则,如果不管打开多少网页,只有一个进程,那所有进程都在同一内存空间,那一个网页崩溃的时候,会导致你同时打开的所有网页关掉,那用户体验会非常糟糕的
当页面刷新的时候这个程序的cpu使用率会突然增加,因为这是不同进程之间有了线程切换
但是页面加载完之后cpu使用率还和原先一样,说明没有了线程,只是进程之间相互切换导致
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
进程在执行过程中拥有独立的内存单元,而该进程的多个线程共享内存,从而极大地提高了程序的运行效率。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
在很多现代操作系统中,一个进程的(虚)地址空间大小为4G,分为系统(内核?)空间和用户空间两部分,系统空间为所有进程共享,而用户空间是独立的,一般WINDOWS进程的用户空间为2G。
一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB?),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
进程中创建线程的限制
默认情况下,一个线程的栈要预留1M的内存空间,而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。
- #include "stdafx.h"
- #include <windows.h>
- #include <process.h>
- #include <assert.h>
-
- volatile bool gbExitThread = false;
- HANDLE ghDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
-
- UINT WINAPI SubThread(void* ptr)
- {
- int nThreadID = *((int*)ptr);
- nThreadID++;
- SetEvent(ghDataEvent);
- printf("%d线程启动/n", nThreadID);
- int i = 0;
- while(!gbExitThread)
- {
- Sleep(10000);
- }
-
- return 0;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- int nThreadCount = 10000;
- HANDLE* phaThread = new HANDLE[nThreadCount];
- int nErr = 0;
- for(int i = 0; i < nThreadCount; i++)
- {
- phaThread[i] = (HANDLE)_beginthreadex(NULL, 0, SubThread, &i, 0, NULL);
-
- if(phaThread[i] == 0)
- {
- nErr = GetLastError();
- if(nErr == 8)
- {
- printf("开启线程失败,存储空间不足!/n");
- }
- else
- {
- printf("开启线程失败,错误号%d/n", nErr);
- }
-
- break;
- }
-
- WaitForSingleObject(ghDataEvent, INFINITE);
- }
-
- return 0;
- }
如何突破2000个限制?
你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程。 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程。
即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制。比方说你的机器装了64GB物理内存,但每个进程的内存空间还是4GB,其中用户态可用的还是2GB。
如果是同一台机器内的话,能起多少线程也是受内存限制的。每个线程对象都要站用非页面内存,而非页面内存也是有限的,当非页面内存被耗尽时,也就无法创建线程了。
如果物理内存非常大,同一台机器内可以跑的线程数目的限制值会越来越大。