2021-08-19Python基础之进程、线程、协程

Python基础之进程、线程、协程

1、进程

  1. 概念;程序在一个数据集上的一次动态执行过程(运行中的程序,不运行就不叫进程)抽象概念
  2. 组成:
    1. 程序:编写的描述功能的代码
    2. 数据集:程序在执行过程中所需的资源
    3. 进程控制块:记录进程的外部特征,描述进程的执行变化过程,系统可以用它控制和管理进程,是系统感知进程存在的唯一标志
  3. 阐述:进程与进程之间占用的是独立的内存块,因此数据也是独立的
  4. 优点:同时利用多个cpu,能够进行多个操作
  5. 缺点:耗费资源(需重开辟内存空间)
  6. 打开一个程序至少会有一个进程,他是操作系统进行资源分配的基本单位
  7. j进程先执行主进程,再执行子进程,子进程间是无序的
  8. 控制台查看进程名字:tasklist
  9. 进程的状态:
    1. 就绪态:正在等待cpu执行
    2. 执行态:cpu正在执行其功能
    3. 等待态:等待某些条件满足
  10. 构造方法
#进程
Process(group,target,name.args.kwargs)
group:线程组,默认为None
target:要执行的方法(函数名)
name:进程名
args/kwargs: 要掺入方法的参数
  1. 实例方法
#实例方法
is_alive() 返回进程是否在运行
jion(timeout):(主进程等待子进程执行完)阻塞当前上下文环境中的进程,直到调用此方法的进程终止或达到指定的timeout(可选参数)
start():进程整备就绪,等待cpu调度 启动进程
run():start()调用run方法,如果实例进程时未制定target,start()执行默认run方法
terminate():不管任务是否完成,立即停止工作进程
  1. 属性:
daemon:和线程的setDaemon功能一样
name:当前进程的别名
pid:当前进程号
  1. 创建进程的方式
#方法一:调用模块创建进程
from multiprocessing import Process
import os
import time
def one():
    time.sleep(5)
    print(f'这是子进程one进程号{os.getpid()},父进程进程号为{os.getppid()}')
def two():
    print(f'这是子进程two进程号{os.getpid()},父进程进程号为{os.getppid()}')
if __name__ == '__main__':
    #创建进程
    p1=Process(target=one,name='python进程')#
    p2=Process(target=two)
    #启动进程
    p1.start()# 这是子进程one进程号1308,父进程进程号为8732
    print('这是p1阻塞join前的状态',p1.is_alive())# 这是p1阻塞join前的状态 True
    p1.join()
    print(f'这是one,two的进程名{p1.name,p2.name}')# 这是one,two的进程名('python进程', 'Process-2')
    print(f'p1的状态{p1.is_alive()},这是p2的状态{p2.is_alive()}')# 这是one,two的进程名('python进程', 'Process-2')
    p2.start()# 这是子进程two进程号7028,父进程进程号为8732


#方法二 通过继承类的方式
from multiprocessing import Process
import os
class Jc(Process):
    def run(self):
        print(f'这是子进程进程号{os.getpid()}')
p=Jc()#实例化对象
if __name__ == '__main__':
    print(p)# <Jc(Jc-1, initial)>
    p.start()# 这是子进程进程号1908 进程号是不固定的
    print('这是子进程的进程名',p.name)# 这是子进程的进程名 Jc-1
  1. 进程间的通信
    1. 进程间不共享全局变量
#进程间不共享全局变量
from multiprocessing import Process
li=[]
def write():
    for i in range(4):
        li.append(i)
    print('这是写入的内容:',li)
def read():
    print('这是读取到的内容:',li)
if __name__ == '__main__':
    p1=Process(target=write)
    p2=Process(target=read)
    p1.start()# 这是写入的内容: [0, 1, 2, 3]
    p1.join()
    p2.start()# 这是读取到的内容: []
	2. 进程间的通信
		1. 创建进程模块下的队列(Queue)
			1. 入队 q.put() 放入数据
			2. 出队 q.get() 取出数据
			3. 导入模块 from queue import Queue
			4. 初始化一个队列对象 q=Queue(3) #最多接受三条信息
			5. 队列个数 q.qsize()
			6. 判断队列是否为空 q.empty() True为空
			7. 进程里队列的使用
#进程中队列的使用
from multiprocessing import Process,Queue
import time
li=['齐天大圣','爱你一万年','月光宝盒']
def write(q1):
    for i in li:
        q1.put(i)#放入数据
        time.sleep(0.2)
    print('写入的数据个数为:',q1.qsize())
def read(q2):
    while True:
        if q2.empty():
            break
        else:
            print('读取到的数据为',q2.get())
if __name__ == '__main__':
    q =Queue()#创建队列队象 不限长度
    p1=Process(target=write,args=(q,))
    p2=Process(target=read,args=(q,))
    p1.start()# 写入的数据个数为: 3
    p1.join()
    p2.start()# 读取到的数据为 齐天大圣 读取到的数据为 爱你一万年 读取到的数据为 月光宝盒
		2. 管道(Pipe)
			1. Pipe常用于两个子进程间的通信,两个进程分别位于管道的两端
			2.Pipe方法返回(c1,c2)代表一个管道两个极端,Pipe方法有duplex参数,为	Ture(默认值),则是双工模式,及c1与c2均可收发,若为False,则c1只负责接收,c2只负责发送,send()和recv()分别是消息的发送与接收
			3.在双工模式下,可以调用c1.send()发送消息,c1.recv()接收消息,若没有消息接收,recv方法则一直阻塞,拖管道关闭,则recv()会抛出EoFError异常
			4.p=Pipe() 创建对象
			5.p.send() 发送消息
			6.p.recv() 接收消息
			7.process.terminate() 可强制终止进程(防止死循环)
#进程通信之管道
from multiprocessing import Process,Pipe
import time
li=['一花异世界','一叶一菩提','大自在','观自在']
def write(p):
    for i in li:
        p.send(i)
        print('发送消息为:',i)
        time.sleep(0.2)
def read(p):
    while True:
        print('接收消息为',p.recv())
if __name__ == '__main__':
    p=Pipe()#创建对象
    p1=Process(target=write,args=(p[0],))
    p2=Process(target=read,args=(p[1],))
    p1.start()
    p2.start()
    p1.join()
    p2.terminate()#强行终止进程,(死循环)

结果:
发送消息为: 一花异世界
接收消息为 一花异世界
发送消息为: 一叶一菩提
接收消息为 一叶一菩提
发送消息为: 大自在
接收消息为 大自在
发送消息为: 观自在
接收消息为 观自在
		3. 队列(Queue)与管道(Pipe)都实现了进程间的通信,有一定的数据共享功能,但他们会阻塞进程,以下两种数据共享不会阻塞进程,并且都是多进程安全的Manager() 和 锁Lock
			1. Manager()
				1. python中进程间共享数据,处理基本的queue,pipe和value+array外还提供了更高层次的封装,使用multiprocess.Manager可以简单的使用这些高级接口
				2. Manager()返回的manager对象控制了一个serve进程,此进程包含的python对象可以被其他的进程通过proxies来访问,从而达到多进程间数据通信且安全
				3. Manager支持的类型有lis,dict,Namespace,Lock,RLock,Semaphore,BoundeSemaphore.Condition,Event,Queue,Value和Array
				4. Manager的dict、list使用
#Manager之dict list
from multiprocessing import Process,Manager
import time,os
li=['时间','空间','人物']
def write(d,l):
    for i in range(len(li)):
        d[i]=li[i]#给生成的字典添加元素
        l.append(i)#给生成的列表添加元素
        time.sleep(0.2)
    print(f'进程号{os.getpid()}写入数据为',d,l)

def read(d,l):
    print(f'进程号{os.getpid()}读取的数据为',d)
    time.sleep(0.2)
    print(f'进程号{os.getpid()}读取到的列表数据为',l)
if __name__ == '__main__':
    d=Manager().dict()#生成一个字典,可进程间使用
    l=Manager().list()#生成一个列表
    p1=Process(target=write,args=(d,l))#创建子进程
    p2=Process(target=read,args=(d,l))
    p1.start()# 进程号18924写入数据为 {0: '时间', 1: '空间', 2: '人物'} [0, 1, 2]
    p1.join()#没有阻塞时,可能进程并发,竞争资源会报错
    p2.start()# 进程号4060读取的数据为 {0: '时间', 1: '空间', 2: '人物'} 进程号4060读取到的列表数据为 [0, 1, 2]
    p2.join()
				5. Manager之共享内存的使用
					1. 共享内存有两个结构,一个是Value,一个是Array,这两个结构内部都实现了锁机制,因此多进程是安全的
					2. Vlaue和Array都需要设置其中存放值的类型,d是double,i是int,具体关系在python标准库的sharedctypes模块中查看
					3. Value和Array,这些值在主进程中管理,很分散。
#manager之共享内存hjn
from multiprocessing import Process,Manager
import os,time
def write(douN,intN):
    douN.value+=2
    for i in range(4):
        intN[i]=i+10

def read(douN,intN):
    print('读取到的数据为',douN,intN)
if __name__ == '__main__':
    with Manager() as manager:
        douN=manager.Value("i",10)
        intN=manager.Array("d",range(9))#此处参数是一个可迭代的序列
        p1=Process(target=write,args=(douN,intN))
        p2=Process(target=read,args=(douN,intN))
        p1.start()
        p1.join()
        p2.start()# 读取到的数据为 Value('i', 12) array('d', [10.0, 11.0, 12.0, 13.0, 4.0, 5.0, 6.0, 7.0, 8.0])
        p2.join()
	3. 进程同步
		1. 锁是为了确保数据的一致性
#进程同步之锁(Lock)
from multiprocessing import Process,Lock
import time
def run(i,lock):
     # with lock:#自动获取锁及释放锁
        if lock.acquire(1):#加锁 1 表示独占
            time.sleep(1)
            print(i)
        lock.release()#解锁
if __name__ == '__main__':
     lock=Lock()
     for i in range(4):
         p=Process(target=run,args=(i,lock,))
         p.start()
  1. 进程池
-	1. 当需要创建的子进程不多时可以用process动态生成多个进程,但若是上百个上千个目标,就可以用到进程池了
-	2. 初始化Pool时,可以指定一个最大的进程数,当有新的请求提交到Pool时,若池还没满,那么就会创建新的进程来执行该请求
-	3. 若进程池中达到最大值该请求就会等待,直到池中有进程结束,才会用到之前的进程来执行新的任务
-	4.概念:
-		定义一个池子,在里面放固定数量的进程,有需求就拿一个执行该任务,处理完进程并不会关闭,而是将这个进程放回进程池中,继续等待任务
-	5. 方法:
-		p.apply() 每个任务是排队进行,类似于串行失去意义
-		p.apply_async()异步非阻塞,不用等待当前进程执行,随时根据系统调度来进行进程切换,若用异步提交任务,等进程池中的任务执行完需用get收集结果
-		p.close() 关闭进程
-		p.join() 主进程阻塞,等待所有工作进程退出,只能在close() 后使用
#进程池
from multiprocessing import Process,Pool
import time,os
def run(i):
    print(f'{os.getpid()}进程:',i)
    time.sleep(0.2)
    return i**2
if __name__ == '__main__':
    #定义进程池
    p=Pool(3)#进程数量为3
    #进程列表
    p_li=[]
    #执行任务 6个任务
    for i in range(6):
        #p.apply_asunc(目标任务,传递的值) 异步运行  根据进程池中的进程数量,每次最多有3个进程异步执行
        #异步执行,执行完一个就释放一个进程,这个进程就去接下一个任务,不用等待,不管其他进程状态
        res=p.apply_async(run,args=(i,))
        p_li.append(res)
    #关闭进程池
    p.close()
    #等待所有子进程执行完
    p.join()
    #使用get收集apply_async的结果
    for i in p_li:
        print(f'{os.getpid()}执行结果为',i.get())
#结果:
5180进程: 0
19428进程: 1
19488进程: 2
5180进程: 3
19428进程: 4
19488进程: 5
1844执行结果为 0
1844执行结果为 1
1844执行结果为 4
1844执行结果为 9
1844执行结果为 16
1844执行结果为 25

Pool的执行流程:有三个阶段:

1. 一个进程池接受很多任务,然后分开执行任务,
2. 不再接受任务了
3. 等所有任务完成,回到进程池

close() 停止接收新任务,若还有任务就会抛出异常
join() 等待所有任务完成,必须在close() 后调用 否则会抛出异常
terminate 非正常终止,内存不够用时,垃圾回收调用此方法

2、线程

  1. 概念:

    1. 线程是应用程序中工作的最小单元,又称为微进程
    2. 是cpu调度的基本单位,每个进程至少有一个线程,这个线程通常就是主线程
    3. 程序启动会有一个主线程,自己创建的线程称为子线程,多线程可以完成多任务
    4. 一个进程默认有一个线程,进程里可以创建多个线程,线程是依附在进程里面的
    5. 线程里面是资源共享的
  2. 优点:共享内存,IO操作时,创造并发操作

  3. 多线程类似于同时执行多个不同的程序,多线程运行有如下优点:

    1. 使用线程可以将占据长时间的程序任务放到后台去处理
    2. 程序的运行速度可能加快
    3. 在一些等待任务上实现用户输入,文件读写,网络收发数据等,线程就比较有用了,在这种情况下我们可以释放一些珍贵的资源如内存占用
  • . 线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行入口,顺序执行序列和程序的出口。但线程不能独立执行,必须依存在进程
  • . 每个线程都有自己的一组cpu寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的cpu寄存器的状态
  • . 指令指针和堆栈指针寄存器是线程上下文中的两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存

线程可以被抢占(中断)
在其他线程正在运行时,线程可以暂缓搁置(也称睡眠)—这就是线程的退让

  1. 线程可分为:
    1. 内核线程:由操作系统内核创建和撤销
    2. 用户线程:不需要内核的支持而在用户程序中实现的线程。
  2. Python中常用的线程模块
    1. _thread
    2. threading(推荐使用)
    3. thread 模块已废弃,可以用threading代替,
  3. Python中使用线程的方式:
    1. 函数或或者用类来包装线程对象
  4. threading 中提供的方法:
    1. threading.currentThread(): 返回当前线程的变量
    2. threading.enumerate(): 返回一个正在运行的线程的list(不包括启动前和终止后的线程)
    3. threading.activeCount(): 返回正在运行的线程数量,与len(threading.enurmerate())有同效
    4. run(): 用以表示线程活动的方法
    5. start(): 启动线程活动
    6. join(time): 阻塞线程至线程中止,time可选参数
    7. setDaemon(True):守护主线程,跟随主线程退(必须放在start()上方)
    8. isAlive(): 返回线程是否活动
    9. getName(): 返回线程名
    10. setName():设置线程名
  5. 创建线程的方式:
    1. 通过模块方式
#单线程
import time
def fn1():
    print('这是单线程1')
    time.sleep(1)
    print('这是单线程任务1')
def fn2():
    print('这是单线程2')
    time.sleep(0.5)
    print('这是单线程任务2')
if __name__ == '__main__':
    fn1() #这是单线程1 这是单线程任务1
    fn2() #这是单线程2  这是单线程任务2
#多线程
import time
#导入线程模块
import threading
def fa(a):
    print('这是线程1','参数为',a)
    print('当前线程的名字为:',threading.current_thread().name)
    time.sleep(1)
    print('这是任务1')
def fb():
    print('这是线程2')
    time.sleep(0.2)
    print('这是任务2')
if __name__ == '__main__':
    #创建线程对象
    t1=threading.Thread(target=fa,args=('多线程传参',))
    t2=threading.Thread(target=fb)
    #修改子线程的名字
    t1.setName('python')
    #获取子线程的名字
    print('线程一的名字',t1.getName())
    #守护线程 主线程结束子线程跟着结束
    t1.setDaemon(True)
    t2.setDaemon(True)
    #启动子线程
    t1.start()
    t2.start()
    #阻塞线程join()
    t1.join()
    t2.join()
    print('这是主线程')
    #获取获取当前线程的名字
    print('当前线程的名字:',threading.current_thread().name)# 当前线程的名字: MainThread
结果:
线程一的名字 python
这是线程1 参数为 多线程传参
当前线程的名字为: python
这是线程2
这是任务2
这是任务1
这是主线程
当前线程的名字: MainThread

	2.   通过创建类来创建线程(对__init__和run()方法进行重写) 
#线程类
import time
from threading import Thread
class Th(Thread):
    def __init__(self,n):
        self.n=n
        threading.Thread.__init__(self)
        print('这是线程1的参数',n,'当前线程是:',threading.current_thread().name)
    def run(self):
        print(f'这是线程{self.n}','当前线程是:',threading.current_thread().name)
        time.sleep(0.2)
        print(f'这是任务{self.n}')
if __name__ == '__main__':
    #创建线程对象
    t1=Th(1)
    t2=Th(2)
    # t1.run()
    t1.start()# start() 自动执行run方法
    t2.start()
结果:
这是线程1的参数 1 当前线程是: MainThread
这是线程1的参数 2 当前线程是: MainThread
这是线程1 当前线程是: Thread-1
这是线程2 当前线程是: Thread-2
这是任务2这是任务1

  1. 资源共享
#资源共享
import threading
li=[]
def write():
    for i in range(4):
        li.append(i)
    print(threading.current_thread().name,'写入的数据是',li)
def read():
    print(threading.current_thread().name,'读取的数据为',li)
if __name__ == '__main__':
    t1=threading.Thread(target=write)
    t2=threading.Thread(target=read)
    #启动子线程
    t1.start()
    t1.join()
    t2.start()


#资源竞争
a=0
b=1000000
from threading import Thread
def fa():
    for i in range(b):
        global a
        a+=1
    print('a的第一次值为',a)
def fb():
    for i in range(b):
        global a
        a+=1
    print('a的第二次值为',a)
if __name__ == '__main__':
    t1=Thread(target=fa)
    t2=Thread(target=fb)
    t1.start()# a的第一次值为 1000000
    t2.start()# a的第二次值为 1770975
结果:
Thread-1 写入的数据是 [0, 1, 2, 3]
Thread-2 读取的数据为 [0, 1, 2, 3]
  1. 线程同步
    1. 同步的方式:线程等待(join())/ 互斥锁
    2. 同步的概念:线程A写入,线程B读取线程A写入的值。线程A写入后线程B才能读取,因此线程A与线程B是同步关系
    3. 线程同步:主线程和创建的子线程之间各自执行完自己的代码直到结束
    4. 互斥锁
      1. 概念:保证多个线程访问共享数据不会出现报错,保证同一时刻只能有一个线程去操作
      2. 使用方法:threading模块里面定义了lock这个函数,通过调用这个函数可以获得一把锁
      3. acquire() 加锁 release() 解锁 两个方法成对出现
      4. 互斥锁的作用:保证同一时间只有一个线程去操作共享数据,不会出现错误
      5. 缺点:使用互斥锁 会影响代码的执行效率,多任务改成了单任务。 使用互斥锁没用好会出现死锁
# 线程同步--互斥锁
from threading import Thread,Lock
s = 0
b = 1000000
def fa():
    # with lock:
        lock.acquire()
        for i in range(b):
            global s
            s += 1
        print('这是s',threading.current_thread().name,s)
        lock.release()
def fb():
    # with lock:
        lock.acquire()
        for i in range(b):
            global s
            s+=1
        print('这是s',threading.current_thread().name,s)
        lock.release()
if __name__ == '__main__':
    lock= Lock()
    t1=Thread(target=fa)
    t2=Thread(target=fb)
    t1.start()# 这是s Thread-1 1000000
    t2.start()# 是s Thread-2 2000000

3、协程

  1. 协程介绍
    1. 对于协程来说,程序员就是神,你想让他执行到哪就执行到哪
    2. 协程:单线程的并发又称为微线程,纤程
    3. 协程的优点:
      1. 没有切换的消耗
      2. 没有锁的概念
    4. 协程是python中的另一个实现多任务的方式,只不过比线程更小,占用更小的执行单元(理解为需要的资源)
    5. 为什么说他是执行单元?因为他自带cpu的上下文,这样只要在合适的时机,可以把一个协程切换到另一个协程。只要这个协程中保存或恢复cpu上下文 那么程序是还可以运行的
    6. 协程使用场景:如果一个线程里的io操作(网络等待,文件操作等),比较多的话,协程就比较合适
    7. 协程适用于高并发处理
  2. greenlet
    1. 用C实现的协程模块
    2. 为了更好的使用协程完成多任务,python中的greenlet模块对其封装,从而使得任务变得更加简单
    3. 通过switch来实现任意的函数间的切换,属于手动操作,当遇到io操作时,程序会阻塞,不能进行自动切换
  3. gevent
    1. 遇到io操作,会自动切换,属于主动切换
    2. 在gevent中用到的主要模式是greenlet
    3. gevent,sowan(函数名) 创建协程对象
    4. join(): 阻塞,等待某个协程执行完毕
    5. joinall():参数是一个协程对象列表,会等待所有的协程都执行完毕在退出
    6. gevent 中自带sleep耗时函数,若没有遇到能识别的io操作,不会执行多任务切换,实现并发效果
  4. 程序补丁:猴子补丁功能,在模块运行时切换的功能
  5. 总结:
    1. 进程是资源分配的基本单位,线程是cpu调度的基本单位
    2. 对比:
      1. 进程:切换需要的资源最大,效率低
      2. 线程:切换需要的资源一般,效率一般
      3. 协程:切换需要的资源很小,效率很高
      4. 多线程:适合IO密集型操作(读写 如爬虫)
      5. 多进程:适合cpu密集型操作(科学计算,对视频高清解码)
#协程简单实现
def A():
    while True:
        yield '人要有精神'
def B():
    while True:
        yield '诸君,且听龙吟'
if __name__ == '__main__':
    a=A()
    b=B()
    while True:
        print(next(a))
        print(next(b))
诸君,且听龙吟
人要有精神
诸君,且听龙吟
#使用greenlet实现协程
from greenlet import greenlet#导入模块
def A():
    print('开始吃饭')
    b.switch()#切换到b中运行
    print('吃饱了!')
def B():
    print('开始睡觉')
    a.switch()#切换到A中运行
    print('睡醒了!')
if __name__ == '__main__':
    #创建协程对象
    a=greenlet(A)
    b=greenlet(B)
    a.switch()
    b.switch()
开始吃饭
开始睡觉
吃饱了!
睡醒了!
#使用gevent
import gevent
def A():
    print('开始睡觉')
    gevent.sleep(0.4)
    print('睡醒了!')
def B():
    print('开始学习')
    gevent.sleep(0.2)
    print('学协程')
if __name__ == '__main__':
    # 创建
    a=gevent.spawn(A)
    b=gevent.spawn(B)
    a.join()#运行
#当执行A/B 两个任务,当A、B遇到耗时任务时,gevent会让A继续执行。同时B也开始执行 当A完成耗时操作后,B对应的时间也完成了耗时任务
开始睡觉
开始学习
学协程
睡醒了!
#使用joinall
#给程序打补丁--猴子补丁
from gevent import monkey
import gevent,time
monkey.patch_all()#此处可以将time换成gevent能识别的睡眠 补丁需放在被补程序前方
def A(name):
    for i in range(4):
        # gevent.sleep(0.2)
        time.sleep(0.2)
        print(name,i)
gevent.joinall([# 等待执行完里面所有任务
    gevent.spawn(A,'协程1'),
    gevent.spawn(A,'协程2')

])
协程1 0
协程2 0
协程1 1
协程2 1
协程1 2
协程2 2
协程1 3
协程2 3



  • 花开成海,思念成灾。你再不来,我便要老去了
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值