Python-并发编程-进程

一、什么是进程

进程就是正在执行的程序,执行程序的过程可以说是一个任务,负责执行任务的则是CPU。

二、进程和程序的区别

程序没执行仅仅只是一堆代码,进程则指的是程序的运行过程。

三、并发与并行及串行

并发: 是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)

并行: 多个任务是真的在同时运行,只有多个cpu才有并行的概念
单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的)​ 有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,​ 一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术​ 而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行;

串行: 多个任务依次运行,一个运行完毕再运行下一个;

四、进程的创建

在UNIX中该系统调用是fork:
fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

在windows中该系统调用是CreateProcess:
CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

关于创建的子进程,UNIX和windows;

相同的是:
进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

不同的是:
在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

五、进程的终止

  1. 正常退出(如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出错退出(python a.py中a.py不存在)

  3. 严重错误(执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)

  4. 被其他进程杀死(如kill -9)

六、进程的状态

程序运行的三种状态:运行态,就绪态,阻塞态;
在这里插入图片描述
两种情况下会导致一个进程在逻辑上不能运行,

  1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作;
  2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。

优化程序效率的核心法则: 降低IO操作(硬盘IO、网络IO)

七、程序调用的方式

同步调用:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。

异步调用:
异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。

阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

非阻塞:
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。

八、创建进程

1. multiprocessing模块

Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

2. process类

在windows中Process()必须放到 # if __name__ == '__main__':下
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 
这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。

multiprocessing模块中用来创建进程的类;

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1 需要使用关键字的方式来指定参数
2 args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组
4 kwargs表示调用对象的字典
5 name为子进程的名称

方法介绍:

  1. p.run(): 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
  2. p.start(): 启动进程,并调用该子进程中的p.run()
  3. p.terminate(): 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  4. p.is_alive(): 如果p仍然运行,返回True
  5. p.join([timeout]): 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

属性介绍:

1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

其他方法:

windows:tasklist |findstr 进程id号
mac,Linux:ps aux | grep 进程id号

进程对象:t=Process(target=task, )或者是在进程内current_process()

t.pid或者current_process().pid   获取进程id号

导入os模块:
os.getpid() 获取进程id号
os.getppid() 获取父进程id号,子进程中获取父进程id,等于父进程的id

3. 创建进程的两种方式

方式一:

from multiprocessing import Process
import time


def task(n):
    print('我是子进程')
    time.sleep(n)
    print('子进程结束')


if __name__ == '__main__':
    # args=(), kwargs={}
    # t=Process(task,args=(1,))
    t = Process(target=task, kwargs={
    
    'n': 1})
    t.start()  # 通知操作系统,开启进程,执行task函数
    print('主')

方式二:

from multiprocessing import Process
import time


class Task(Process):
    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self):
        print('我是子进程')
        time.sleep(self.n)
        print('子进程结束')


if __name__ == '__main__':
    t = Task(1)
    # t.run(1)  # 不是调用t.run(),而是调用t.start()
    t.start()
    print('主')
 

4. join方法

join的使用:等待子进程执行完成,才会继续运行主进程的代码;


from multiprocessing import Process
import time


def task(n):
    print('我是子进程')
    time.sleep(n)
    print('子进程结束')


if __name__ == '__main__':
    ctime = time.time()
    t = Process(target=task, kwargs={
    
    'n': 1})
    t2 = Process(target=task, kwargs={
    
    'n': 2})
    t.start()
    t2.start()
    t.join()  # 等待t子进程执行完成
    t2.join()  # 等待t2子进程执行完成
    print('主')
    ctime2 = time.time()
    print(ctime2 - ctime)

5. 进程间的数据相互隔离

from multiprocessing import Process
import time

age = 18


def task(n):
    global age  # 局部修改全局
    age = 99
    print('我是子进程')
    time.sleep(n)
    print('子进程结束')
    print(age)


if __name__ == '__main__':
    t = Process(target=task, kwargs={
    
    'n': 1})
    t.start()
    t.join()  # 等待t子进程执行完成
    print('主')
    print(age) # 数据没有变,主进程中打印age和子进程的age没有半毛钱关系,数据是隔离的

九、进程调度

  1. 先来先服务
  2. 短作业优先
  3. 时间片轮转
  4. 多级反馈队列

十、僵尸进程和孤儿进程

僵尸进程:

进程结束了,资源还没来得及回收

孤儿进程:
主进程挂了,子进程还没结束,它就会被专门的进程接管

十一、守护进程

主进程创建守护进程;

守护进程会在主进程代码执行结束后就终止
守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

from multiprocessing import Process,current_process
import time
import os

def task():
    print(os.getpid())
    print('子进程')
    time.sleep(200)
    print('子进程结束')


if __name__ == '__main__':
    t = Process(target=task, )
    # 守护进程:主进程一旦结束,子进程也结束
    t.daemon=True  # 一定要加在启动之前
    t.start()

    time.sleep(1)
    print('主进程结束')

十二、互斥锁

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理

# 同时只有一个人能拿到,必须释放,其他人才能再次获取到

from multiprocessing import Process, Lock
import json
import time
import random


def search():
    # 查票的函数
    # 打开文件,读出ticket_count
    with open('ticket', 'r', encoding='utf-8') as f:
        dic = json.load(f)
        print('余票还有:', dic.get('ticket_count'))


def buy():
    with open('ticket', 'r', encoding='utf-8') as f:
        dic = json.load(f)

    time.sleep(random.randint(1, 3))  # 模拟一下网络延迟
    if dic.get('ticket_count') > 0:
        # 能够买票
        dic['ticket_count'] -= 1
        # 保存到文件中去
        with open('ticket', 'w', encoding='utf-8') as f:
            json.dump(dic, f)
            print('买票成功')
    else:
        # 买票失败
        print('买票失败')


# 写一个函数,先查票,再买票

def task(mutex):
    search()
    # 买票过程要加锁
    # 买前加锁
    # mutex.acquire()
    # buy()  # 10个进程变成了串行执行
    # # 买后释放锁
    # mutex.release()
    with mutex: # 自动管理上下
        buy()


if __name__ == '__main__':
    # 锁的创建,在哪?主进程创建锁
    mutex = Lock()  # 创建一把锁
    # 模拟十个人买票(开10个进程)
    for i in range(10):
        t = Process(target=task, args=(mutex,))
        t.start()

十三、队列介绍

from multiprocessing import Queue

# 实例化得到要给对象

q=Queue(5)  # 默认很大,可以放很多,写了个5,只能放5个

# 往管道中放值
q.put(1)
q.put_nowait(100) # 能放就放 不能放就抛出异常

# 从管道中取值
print(q.get())
print(q.get(timeout=0.1))  # 等0.1s还没有值,就结束
print(q.get_nowait())  # 不等了,有就是有,没有就没有 抛出异常

print(q.empty())  # 看一下队列是不是空的
print(q.full())   # 看一下队列是不是满的 返回布尔值


# 总结:
'''
q=Queue(队列大小)
# 放值
q.put(asdf)
q.put_nowait(asdf)  # 队列满了,放不进去就不放了,报错

# 取值
q.get()  # 从队列头部取出一个值
q.get_nowait() # 从队列头部取值,没有就抛错


# 队列是否为空,是否满
print(q.empty())  # 看一下队列是不是空的
print(q.full())   # 看一下队列是不是满的
'''

十四、IPC机制(Inter-Process Communication,进程间通信)

from multiprocessing import Process, current_process, Queue
import time
import os


def task1(q):
    print('我是task1进程,我的id号是:%s'%os.getpid())
    q.put('lqz is handsome')


def task2(q):

    # res=q.get()
    # print('我是task2进程,我的id号是:%s'%os.getpid(),res)
    print('我是task2进程,我的id号是:%s'%os.getpid())


if __name__ == '__main__':
    q = Queue(5)

    t1 = Process(target=task1, args=(q,))
    t1.start()
    t2 = Process(target=task2, args=(q,))
    t2.start()

    print(q.get())

猜你喜欢

转载自blog.csdn.net/msmso/article/details/108131834
今日推荐