进程的概念起源于操做系统,是操做系统最核心的概念。html
进程是对正在运行程序的一个抽象,操做系统的其余全部内容都是围绕进程的概念展开的。因此想要真正了解进程,必须事先了解操做系统,点击进入。 python
进程是操做系统提供的最古老也是最重要的抽象概念之一。即便能够利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。linux
理论基础:nginx
一 操做系统的做用:程序员
1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口web
2:管理、调度进程,而且将多个进程对硬件的竞争变得有序算法
二 多道技术:shell
1.产生背景:针对单核,实现并发(如今的主机通常是多核,那么每一个核都会利用多道技术,可是核与核之间没有使用多道技术切换这么一说,一个程序io阻塞,会等到io结束再从新调度)编程
2.时间上的复用(复用一个cpu的时间片)+空间上的复用(如内存中同时有多道程序)json
2.1-2.4 (重点部分)
2.5-2.9(了解部分)
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。
举例(单核+多道,实现多个进程的并发执行):
egon在一个时间段内有不少任务要作:python备课的任务,写书的任务,交女友的任务,王者荣耀上分的任务,
但egon同一时刻只能作一个任务(cpu同一时间只能干一个活),如何才能玩出多个任务并发执行的效果?
egon备一会课,再去跟李杰的女友聊聊天,再去打一会王者荣耀....这就保证了每一个任务都在进行中.
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。
举例:
想象一位有一手好厨艺的计算机科学家egon正在为他的女儿元昊烘制生日蛋糕。
他有作生日蛋糕的食谱,
厨房里有所需的原料:面粉、鸡蛋、韭菜,蒜泥等。
在这个比喻中:
作蛋糕的食谱就是程序(即用适当形式描述的算法)
计算机科学家就是处理器(cpu)
而作蛋糕的各类原料就是输入数据。
进程就是厨师阅读食谱、取来各类原料以及烘制蛋糕等一系列动做的总和。
如今假设计算机科学家egon的儿子alex哭着跑了进来,说:XXXXXXXXXXXXXX。
科学家egon想了想,处理儿子alex蛰伤的任务比给女儿元昊作蛋糕的任务更重要,因而计算机科学家就记录下他照着食谱作到哪儿了(保存进程的当前状态),而后拿出一本急救手册,按照其中的指示处理蛰伤。这里,咱们看处处理机从一个进程(作蛋糕)切换到另外一个高优先级的进程(实施医疗救治),每一个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完以后,这位计算机科学家又回来作蛋糕,从他离开时的那一步继续作下去。
须要强调的是:同一个程序执行两次,那也是两个进程,好比打开暴风影音,虽然都是同一个软件,可是一个能够播放苍井空,一个能够播放饭岛爱。
不管是并行仍是并发,在用户看来都是“同事”运行的,无论是进程仍是线程,都只是一个任务而已,真是干活的是cpu,cpu来作这些任务,而一个cpu同一时刻只能执行一个任务。
一 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就能够实现并发,(并行也属于并发)。
二 并行:同时运行,只有具有多个cpu才能实现并行
单核下,能够利用多道技术,多个核,每一个核也均可以利用多道技术(多道技术是针对单核而言的)
有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,
一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术
而一旦任务1的I/O结束了,操做系统会从新调用它(需知进程的调度、分配给哪一个cpu运行,由操做系统说了算),可能被分配给四个cpu中的任意一个去执行
全部现代计算机常常会在同一时间作不少件事,一个用户的PC(不管是单cpu仍是多cpu),均可以同时运行多个任务(一个任务能够理解为一个进程)。
启动一个进程来杀毒(360软件)
启动一个进程来看电影(暴风影音)
启动一个进程来聊天(腾讯QQ)
全部的这些进程都需被管理,因而一个支持多进程的多道程序系统是相当重要的
多道技术:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另一个,使每一个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却能够运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操做系统的真正硬件并行(多个cpu共享同一个物理内存)。
同步执行:一个进程在执行某个任务时,另一个进程必须等待其执行完毕,才能继续执行。
异步执行:一个进程在执行某个任务时,另一个进程无需等待其执行完毕,就能够继续执行,当有消息返回时,系统会通知后者进行处理,这样能够提升执行效率。
举个例子,打电话时就是同步通讯,发短息时就是异步通讯。
但凡是硬件,都须要有操做系统去管理,只要有操做系统,就有进程的概念,就须要有建立进程的方式,一些操做系统只为一个应用程序设计,好比微波炉中的控制器,一旦启动微波炉,全部的进程都已经存在。
而对于通用系统(跑不少应用程序),须要有系统运行过程当中建立或撤销进程的能力,主要分为4中形式建立新的进程
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台而且只在须要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程当中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而建立一个新进程(如用户双击暴风影音)
4. 一个批处理做业的初始化(只在大型机的批处理系统中应用)
不管哪种,新进程的建立都是由一个已经存在的进程执行了一个用于建立进程的系统调用而建立的:
1. 在UNIX中该系统调用是:fork,fork会建立一个与父进程如出一辙的副本,两者有相同的存储映像、一样的环境字符串和一样的打开文件(在shell解释器进程中,执行一个命令就会建立一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的建立,也负责把正确的程序装入新进程。
关于建立的子进程,UNIX和windows
1.相同的是:进程建立后,父进程和子进程有各自不一样的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另一个进程。
2.不一样的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是能够有只读的共享内存区的。可是对于windows系统来讲,从一开始父进程与子进程的地址空间就是不一样的。
1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,python a.py中a.py不存在)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,能够捕捉异常,try...except...)
4. 被其余进程杀死(非自愿,如kill -9)
不管UNIX仍是windows,进程只有一个父进程,不一样的是:
1. 在UNIX中全部的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的全部成员。
2. 在windows中,没有进程层次的概念,全部的进程都是地位相同的,惟一相似于进程层次的暗示,是在建立进程时,父进程获得一个特别的令牌(称为句柄),该句柄能够用来控制子进程,可是父进程有权把该句柄传给其余子进程,这样就没有层次了。
tail -f access.log |grep '404'
执行程序tail,开启一个子进程,执行程序grep,开启另一个子进程,两个进程之间基于管道'|'通信,将tail的结果做为grep的输入。
进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都没法运行
其实在两种状况下会致使一个进程在逻辑上不能运行,
1. 进程挂起是自身缘由,遇到I/O阻塞,便要让出CPU让其余进程去执行,这样保证CPU一直在工做
2. 与进程无关,是操做系统层面,可能会由于一个进程占用时间过多,或者优先级等缘由,而调用其余的进程去使用CPU。
于是一个进程由三种状态
进程并发的实如今于,硬件中断一个正在运行的进程,把此时进程运行的全部状态保存下来,为此,操做系统维护一张表格,即进程表(process table),每一个进程占用一个进程表项(这些表项也称为进程控制块)
该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配情况、全部打开文件的状态、账号和调度信息,以及其余在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过同样。
3.1-3.4(重点部分)
3.5-3.10(了解部分)
在传统操做系统中,每一个进程有一个地址空间,并且默认就有一个控制线程。
线程顾名思义,就是一条流水线工做的过程,一条流水线必须属于一个车间,一个车间的工做过程是一个进程。
车间负责把资源整合到一块儿,是一个资源单位,而一个车间内至少有一个流水线。
流水线的工做须要电源,电源就至关于cpu。
因此,进程只是用来把资源集中到一块儿(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,至关于一个车间内有多条流水线,都共用一个车间的资源。
例如,北京地铁与上海地铁是不一样的进程,而北京地铁里的13号线是一个线程,北京地铁全部的线路共享北京地铁全部的资源,好比全部的乘客能够被全部线路拉。
建立进程的开销要远大于线程?
若是咱们的软件是一个工厂,该工厂有多条流水线,流水线工做须要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
建立一个进程,就是建立一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,因此建立开销小。
进程之间是竞争关系,线程之间是协做关系?
车间直接是竞争/抢电源的关系,竞争(不一样的进程直接是竞争关系,是不一样的程序员写的程序运行的,迅雷抢占其余进程的网速,360把其余进程当作病毒干死)
一个车间的不一样流水线式协同工做的关系(同一个进程的线程之间是合做关系,是同一个程序写的程序内开启动,迅雷内的线程是合做关系,不会本身干本身)
多线程指的是,在一个进程中开启多个线程,简单的讲:若是多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
1. 多线程共享一个进程的地址空间
2. 线程比进程更轻量级,线程比进程更容易建立可撤销,在许多操做系统中,建立一个线程比建立一个进程要快10-100倍,在有大量线程须要动态和快速修改时,这一特性颇有用
3. 若多个线程都是cpu密集型的,那么并不能得到性能上的加强,可是若是存在大量的计算和大量的I/O处理,拥有多个线程容许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,能够开启多个线程(比开进程开销要小的多)
开启一个字处理软件进程,该进程确定须要办不止一件事情,好比监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操做的都是同一块数据,于是不能用多进程。只能在一个进程里并发地开启三个线程,若是是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程
而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其余物理资源。
多线程的运行也多进程的运行相似,是cpu在多个线程之间的快速切换。
不一样的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,若是迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序建立,因此同一进程内的线程是合做关系,一个线程能够访问另一个线程的内存地址,你们都是共享的,一个线程干死了另一个线程的内存,那纯属程序员脑子有问题。
相似于进程,每一个线程也有本身的堆栈
不一样于进程,线程库没法利用时钟中断强制线程让出CPU,能够调用thread_yield运行线程自动放弃cpu,让另一个线程运行。
线程一般是有益的,可是带来了不小程序设计难度,线程的问题是:
1. 父进程有多个线程,那么开启的子线程是否须要一样多的线程
若是是,那么附近中某个线程被阻塞,那么copy到子进程后,copy版的线程也要被阻塞吗,想想nginx的多线程模式接收用户链接。
2. 在同一个进程中,若是一个线程关闭了问题,而另一个线程正准备往该文件内写内容呢?
若是一个线程注意到没有内存了,并开始分配更多的内存,在工做一半时,发生线程切换,新的线程也发现内存不够用了,又开始分配更多的内存,这样内存就被分配了屡次,这些问题都是多线程编程的典型问题,须要仔细思考和设计。
为了实现可移植的线程程序,IEEE在IEEE标准1003.1c中定义了线程标准,它定义的线程包叫Pthread。大部分UNIX系统都支持该标准,简单介绍以下
线程的实现能够分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操做系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。
用户级线程内核的切换由用户态程序本身控制内核切换,不须要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大致是这么作的。
在用户空间模拟操做系统对进程的调度,来调用一个进程中的线程,每一个进程中都会有一个运行时系统,用来调度线程。此时当该进程获取cpu时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行。
内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;能够很好的利用smp,即利用多核cpu。windows线程就是这样的。
一: 如下是用户级线程和内核级线程的区别:
二: 内核线程的优缺点
优势:
缺点:
三: 用户进程的优缺点
优势:
缺点:
用户级与内核级的多路复用,内核同一调度内核线程,每一个内核线程对应n个用户线程
豆瓣250电影爬虫多线程与单线程对比:
import requests import re import json import threading,time li=[] def getPage(url): res=requests.get(url) res.encoding=res.apparent_encoding return res.text def match(s): mov = re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>' '.*?<p.*?>.*?导演: (?P<br>.*?) .*?</p>.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S) ret = mov.finditer(s) for i in ret: yield { "id": i.group("id"), "title": i.group("title"), "br":i.group("br"), "rating_num": i.group("rating_num"), "comment_num": i.group("comment_num"), } def main(num): url='https://movie.douban.com/top250?start=%s&filter='%num ret=match(getPage(url)) print(ret) with open('movie','a',encoding='utf-8') as f: for obj in ret: print(obj) date=json.dumps(obj,ensure_ascii=False) f.write(date+'\n') if __name__ == '__main__': count=0 s=time.time() for i in range(10): main(count) count+=25 print(time.time()-s) #运行时间:5.575318813323975 单线程
import requests import re import json import threading,time li=[] def getPage(url): res=requests.get(url) res.encoding=res.apparent_encoding return res.text def match(s): mov = re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>' '.*?<p.*?>.*?导演: (?P<br>.*?) .*?</p>.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S) ret = mov.finditer(s) for i in ret: yield { "id": i.group("id"), "title": i.group("title"), "br":i.group("br"), "rating_num": i.group("rating_num"), "comment_num": i.group("comment_num"), } def main(num): url='https://movie.douban.com/top250?start=%s&filter='%num ret=match(getPage(url)) # print(ret) for i in ret: li.append(i) # print('cost time:',time.time()-s) if __name__ == '__main__': count=0 s=time.time() a={} for i in range(10): a[i]=threading.Thread(target=main,args=(count,)) a[i].start() count+=25 for i in range(10): a[i].join() li=sorted(li,key=lambda x:int(x['id'])) print(li) with open('movie','a',encoding='utf-8') as f: for obj in li: date=json.dumps(obj,ensure_ascii=False) f.write(date+'\n') print(time.time()-s) #运行时间:4.137236595153809 多线程