Python——进程&线程&协程

进程:

进程是计算机中最小的资源分配单位;

通俗来说就是写了一段代码,交给操作系统运行,操作系统分给一个进程进行实施,进程要进行硬件的分配工作从而完成这段代码。

进程是正在运行的程序的实例。是系统进行资源分配和调度的基本单位,在早期面向进程涉及的计算机结构中,进程是程序的基本执行实体,在当代面向线程涉及的计算机结构中,进程是线程的容器。

特点:

1. 操作系统中的唯一标识符(PID),不是固定的值,在一次运行当中是不变的,重启后会发生改变。

2 在linux中,每个进程都由父进程启动,起一个子进程相当于复制一个父进程。

3. linux中发根进程为1

4. 数据隔离,每个进程单独运行,相互隔离。

5. 创建,销毁,切换时间开销大。

父进程与子进程的关系:

1. 主进程是运行的进程,当有子进程那么就是父进程。当主进程创建完子进程后,子进程会复制或import主进程内容,然后两个进程就没有关系了。

2. 通过一个操作系统中的从属关系文件或者网络,获取父进程和子进程的关系的。

3. 父进程代码运行完成后,不会结束,需要等待子进程结束后,主进程才会结束任务。

4. 父进程负责回收子进程的资源,如果没有父进程没有回收资源,那么子进程将会成为一个僵尸进程。

5. 如果需要做一个子进程的守护进程,那么就在创建进程中P.daemon=True即可,守护进程是随着主进程的代码结束而结束的。

程序和进程之间的区别:

程序只是一个文件

进程是这个文件被CPU运行起来了

进程的调度:

要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。

    • 先来先服务(FCFS)算法:是一种最简单的调度算法,该算法即可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙性作业,而不利于I/O繁忙性作业。
    • 短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
    • 时间片轮转法,分给每个进程一个固定的时间轴,CPU的处理时间也是固定的,如果有i/o那就处理下一个。

在轮转法中,加入到就绪队列的进程有3种情况: 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。

  • 多级反馈队列:公认较好的进程调度的算法。

(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

并行与并发:

并行:两个程序,两个CPU,每个程序分别占用一个CPU,自己执行自己的。看起来是同时执行,实际在每个时间点上都在各自执行着。

并发:两个程序,一个CPU,每个程序交替在一个CPU上执行,看起来在同时执行,但是实际上任然是串行。

 

进程的三种状态:

#程序在开始运行之后,并不是立即开始执行代码,而是会进入就绪状态,等待操作系统调度开始运行。
import time
print('程序开始运行')       #程序运行状态
name=input('====>')      #在这里遇到等待用户输入操作,程序进入阻塞状态

#用户输入之后进程并不是立即运行,而是进入就绪状态,等待操作系统的调度继续运行。
print(name)   #运行
time.sleep(1)   #阻塞
#就绪
print('程序结束运行')    #运行
#结束

同步和异步:

所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。

所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

同步/异步/阻塞/非阻塞

同步阻塞:

公路上开车前面施工堵了,就只能在车里等着开通。期间什么都不做。

异步阻塞:

等待拥堵期间,在车里玩玩手机,看看视频啥的,就是不能离开车。

同步非阻塞:

可以离开车,车边上逛逛,撩撩妹子,WC啥的,但要一直盯着公路看看通没通。一边撩妹一边看堵不堵。

异步非阻塞:

跟旁边大卡车的司机说一声,坐的高看前面的路通了就叫一下。那么我就可以认真的撩妹和WC了。。

windows创建进程:

1. Process实例化后,.start()来创建一个进程,并告诉系统,创建完进程后请执行Process内的target所指定的函数。

2. 由于是进程之间数据无法交互的问题,那么windows会以import的方式来将父进程的代码导入到子进程中,并运行。

3. 如果此时没有__name__判断语句,那么将会继续执行Process语句进行子进程的创建。反复创建直到报错。

IOS Linux操作系统创建进程:

 是使用fork来将父进程代码运算后得出的值进行直接的拷贝(将内存地址直接拷贝),而不是在子进程中再运行一遍。

使用multiprocessing模块来创建一个进程。

from multiprocessing import Process
import time
import os


def func():
    print('A',os.getpid())
    time.sleep(1)
    print ('B',os.getpid())


if __name__== '__main__':                 #windows中必须要写,如果不写将会报错
    P= Process(target=func)               #准备创建一个进程,并将func函数加入其中
    P.start()                              #将func函数丢给系统,让系统自行创建一个进程,并运行其中的代码。
    print('C',os.getpid())                 #自己将继续往下执行自己进程中的事情。
    exit()
    print('D',os.getpid())

'''
C 17344
A 9400
B 9400
在P.start时,程序会将func代码直接给系统,让系统创建一个进程然后执行func中的代码。而不去管执行的怎么样,自己则继续往下执行。
因为CD中间有一个退出语句,那么就代表直接关闭该进程,而导致不会继续该进程下的D输出。
'''

线程:

特点:

1. 计算机中能被CPU调度的最小单位,只负责执行代码。

2. 在同一个进程中,可以有多个线程。

3. 是进程中的一部分,每个进程中至少有一个线程。

4. 线程创建的开销远远小于进程(创建,销毁,切换)基本是100倍的进程创建效率。

5. 在线程当中,不能从主线程结束一个子线程。

6. 线程当中的文件是共享的,但是不要在子线程当中,随意修改全局变量。

7. 守护线程一直等到所有非守护线程都结束以后才结束,除了守护主线程的代码之外,也会守护子线程。

8. 设置成守护线程的子线程不能被join

8. cpython中,线程本身不能利用多核,所以即便开启了多线程,也只能轮流在一个CPU上执行。

因为GIL锁导致cpython解释器,不能实现多线程利用多核。

什么时候开启池,什么时候开启线程

开池:

1. 有大量任务要求达到一定的并发数。

2. 爬虫的时候

3. 可预见时间大小的

开线程:

1. 开一个线程做一个事

2. 网络编程,因为有大量的IO操作,socketserver就用的原生线程

3. 不确定用时的,可能会占用很多的时间。

GLI(全局解释器锁)

cpython和pypy中都存在特殊的垃圾回收机制,保证整个python程序中,同一时刻只能由一个线程被CPU执行。所以导致GIL锁不能进行并行处理,可以实现并发。

使用多线程并不影响高IO型的操作,只会对高计算型的程序有效率上的影响,遇到高计算可以使用多进程+多线程或使用分布式来解决。

使用thread来进行线程的创建:

import time
from threading import Thread,current_thread,enumerate,active_count

def func(i1,i2):
    i=i1+ i2
    time.sleep(0.5)
    print (current_thread())                  #查看线程的ID并以对象形式显示,适用于函数使用,
for i in range (5):
    t= Thread(target=func,args=(i,i* 2))             #传值和进程一样,需要使用元组形式进行传输。
    t.start()                          #启动也是需要使用start
    print(t.ident)                       #可以查看所创建的线程ID,可以用在面向对象中使用。
    t.daemon=True #创建守护线程。
print(enumerate())                       #可以看到正在运行的主线程和子线程。以对象法师呈现在列表当中。
print(active_count())                      #可以查看子线程的个数,包括主线程。



#以class的方式来创建线程。
class TestThread(Thread):
    def __init__(self,ip):
        super(TestThread, self).__init__()
        self.ip = ip
    def run(self):
        print('running......',self.ip)

if __name__ == '__main__':
    t1 = TestThread('10.0.0.1')
    t1.start()
    print('end')

协程:

特点:

1. 由我们自己写的Python代码控制切换的。

2. 操作系统不可见。

3. 能够在一个线程下的多个任务之间来回切换,那么每一个任务都是一个协程。

线程与协程对比:

线程需要操作系统进行切换,需要给操作系统一定的压力,并且一来一回需要消耗一定的开销。

协程是自己代码进行的切换,不需要占用操作系统的开销,开销很小。

 

由于是Python自行切换,所以需要我们使用模块来对IO操作进行切换,下面有两种进行协程切换的模块:

1. gevent模块

  • 需要下载的第三方模块
  • 可以解决很多常见的IO操作,只要不是自己写的原生代码,那么一些模块中带有的IO操作都会涉及到里面。

2. asyncio模块

  • 内置模块
  • 使用yield来进行切换
  • aiohttp也是使用该模块进行的线程切换

模块相关命令:Python——gevent&asyncio模块(线程模块)

 

 

posted @ 2021-01-27 16:56  新兵蛋Z  阅读(140)  评论(0编辑  收藏  举报