【python进阶篇】并发编程『上』

进程

进程是计算机中的程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

狭义定义:进程是正在运行的程序的实例。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动

同一个程序执行两次就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

进程调度

  • 先来先服务算法
  • 短作业优先调度算法
  • 时间片轮转法
  • 多级反馈队列

进程的并行和并发

并行:指两者同时执行。比如五条跑道,两人赛跑。也就是资源够用的情况(三个进程,四核的cpu)

并发:指资源有限的情况下,两者交替轮流使用资源。目的是提高使用效率。在一个时间点上的时候是只有一个程序在运行的,但是在一个时间段(时间片)来说是同时执行的。

区别:

并行是微观上的,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。

并发是宏观上的,在一个时间段上可以看出是同时执行的。比如一个服务器同时处理多个请求。


进程的状态

  • 就绪:当进程分配到出cpu之外的所有必要资源,只要获得处理机即可立即执行。
  • 运行:当程序已获得处理机,其程序正在处理机上执行的状态。
  • 阻塞:由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。

【python进阶篇】并发编程『上』_第1张图片


同步异步

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

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


进程的创建

但凡是硬件,都需要有操作系统去管理,只要有操作系统就有进程的概念,就需要有创建进程的方式。主要分成以下四种形式:

  • 系统初始化(运行在后台,并且只在需要时才唤醒的进程)
  • 一个进程在运行过程中开启了子进程(如nginx开启多进程)
  • 用户的交互请求,而创建一个进程(如双击pycharm)
  • 一个批处理作业的初始化(只在大型机的批处理系统中应用)

multiprocess包

创建进程我们可以导入multiprocessing的Process模块

from multiprocessing import Process

def func():
    print('我是烟花店店长')

if __name__ == '__main__':
    # 创建一个进程象
    p = Process(target=func)   # 不加括号
    # 开启进程
    p.start()

这是异步的,(p与py文件与pycharm)同时进行的。

import os
import time
from multiprocessing import Process

def func():
    for i in range(5):
        time.sleep(0.5)
        print('子进程',os.getpid(),os.getppid())

if __name__ == '__main__':
    print('主进程', os.getpid(), os.getppid())
    # 创建一个进程象
    p = Process(target=func)   # 不加括号
    # 开启进程
    p.start()
    for i in range(5):
        time.sleep(0.3)
        print('*'* i)
主进程 172264 157412

*
子进程 158992 172264
**
子进程 158992 172264
***
****
子进程 158992 172264
子进程 158992 172264
子进程 158992 172264

也可以给子进程进行传参操作

from multiprocessing import Process

def func(num):
    print(num)

if __name__ == '__main__':
    # 创建一个进程象
    p = Process(target=func, args=(100,))
    # 开启进程
    p.start()
from multiprocessing import Process

def func(num1,num2):
    print(num1+num2)

if __name__ == '__main__':
    # 创建一个进程象
    p = Process(target=func, args=(100, 200))  # 不加括号
    # 开启进程
    p.start()

进程之间的数据隔离问题(数据不共享)

from multiprocessing import Process

count = 100
def func():
    global count
    count -= 1
    print('子进程', count)

if __name__ == '__main__':
    # 创建一个进程象
    p = Process(target=func)  # 不加括号
    # 开启进程
    p.start()
    print('主进程', count)

数据互不影响:

主进程 100
子进程 99

如何开启多进程

from multiprocessing import Process
import time

def func(name):
    time.sleep(0.2)
    print('我是{}'.format(name))

if __name__ == '__main__':
    names = ['一花', '二乃', '三玖', '四叶', '五月']
    for i in range(5):
        p = Process(target=func, args=(names[i], ))
        p.start()
我是三玖我是一花

我是二乃
我是四叶
我是五月

子进程和父进程的关系

import os
from multiprocessing import Process
import time

# 子进程和父进程之间的关系
def func(arg):
    print('子进程%s:' % arg, os.getpid(), os.getppid())
    time.sleep(1)
    print('子进程end')

if __name__ == '__main__':
    for i in range(5):
        Process(target=func, args=(i, )).start()
    print('父进程******')
父进程******
子进程1: 163420 160596
子进程2: 173780 160596
子进程0: 139740 160596
子进程3: 166492 160596
子进程4: 161276 160596
子进程end
子进程end
子进程end
子进程end
子进程end

子进程和父进程的启动是异步的

父进程只通知操作系统启动子进程,接下来的操作由操作系统接手,父进程继续执行。

父进程的代码执行完毕后不会直接结束程序,因为父进程要负责回收子进程的资源。


join控制子进程

import random
from multiprocessing import Process
import time

def func(index):
    time.sleep(random.random())
    print('第%s个邮件已经发送完毕' % index)

if __name__ == '__main__':
    for i in range(5):
        p = Process(target=func, args=(i, ))
        p.start()
        p.join()  # 阻塞 直到p进程结束完毕就结束阻塞
    print('5个邮件发送完毕')
第1个邮件已经发送完毕
第2个邮件已经发送完毕
第3个邮件已经发送完毕
第4个邮件已经发送完毕
5个邮件发送完毕

But 这里的子进程是同步的了(效率太低)


import random
from multiprocessing import Process
import time

def func(index):
    time.sleep(random.random())
    print('第%s个邮件已经发送完毕' % index)

if __name__ == '__main__':
    # 等列表添加进后,再最终进行主进程的打印操作
    p_li = []
    for i in range(5):
        p = Process(target=func, args=(i, ))
        p.start()
        p_li.append(p)
    for p in p_li:
        p.join()  # 阻塞 直到p进程结束完毕就结束阻塞
    print('5个邮件发送完毕')
第1个邮件已经发送完毕
第2个邮件已经发送完毕
第3个邮件已经发送完毕
第0个邮件已经发送完毕
第4个邮件已经发送完毕
5个邮件发送完毕

这样运行速度就变快了


守护进程

from multiprocessing import Process
import time

def func():
    print('子进程 start')
    time.sleep(2)
    print('子进程 end')

if __name__ == '__main__':
    p = Process(target=func)
    # 设置p为守护进程(必须在start()方法之前)
    p.daemon = True
    p .start()
    time.sleep(2)
    print('主进程')
子进程 start
主进程

守护进程会随着主进程代码的执行完毕而结束


from multiprocessing import Process
import time

def func1():
    count = 1
    while True:
        time.sleep(0.5)
        print('*' * count)
        count += 1

def func2():
    print('func2 start')
    time.sleep(3)
    print('func2 end')

if __name__ == '__main__':
    # 守护进程
    p1 = Process(target=func1)
    p1.daemon = True
    p1 .start()
    # 子进程
    p2 = Process(target=func2)
    p2.start()

    time.sleep(2)
    print('主进程')
func2 start
*
**
***
主进程
func2 end

如果主进程的代码已经执行完毕,但是子进程还没有执行完,守护进程也不会继续执行

守护进程:每隔一段时间就向机器汇报自己的状态(报活)


同步控制

查票/买票

import time
import json
from multiprocessing import Process

# def search(person):
#     with open('a') as f:
#         dic = json.load(f)   # load打开文件
#     time.sleep(0.2)
#     print('{}查询余票:'.format(person), dic['count'])

def get_ticket(person):
    with open('a') as f:
        dic = json.load(f)
    time.sleep(0.2)
    if dic['count'] > 0:
        print('%s买到票了' % person)
        dic['count'] -= 1
        time.sleep(0.2)
        with open('a', 'w')as f:
            json.dump(dic, f)  # 文件对象
    else:
        print('%s没有买到票' % person)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=get_ticket, args=('person%s' % i,))
        p.start()

但是,由结果显示,都可以买到票(异步)


Lock锁

import time
import json
from multiprocessing import Process, Lock  # 进程锁(互斥锁)

# def search(person):
#     with open('a') as f:
#         dic = json.load(f)   # load打开文件
#     time.sleep(0.2)
#     print('{}查询余票:'.format(person), dic['count'])

def get_ticket(person, lock):
    # 加锁
    lock.acquire()
    with open('a') as f:
        dic = json.load(f)
    time.sleep(0.2)
    if dic['count'] > 0:
        print('%s买到票了' % person)
        dic['count'] -= 1
        time.sleep(0.2)
        with open('a', 'w')as f:
            json.dump(dic, f)  # 文件对象
    else:
        print('%s没有买到票' % person)
    # 释放锁
    lock.release()

if __name__ == '__main__':
    # 创建锁对象
    lock = Lock()

    for i in range(10):
        p = Process(target=get_ticket, args=('person%s' % i, lock))
        p.start()

加锁是为了保证数据安全。在异步的情况下,多个进程有可能同时修改同一份资源


信号量

import time
from multiprocessing import Process, Semaphore

def library(person, chair):
    # 加锁
    chair.acquire()
    print('%s走进图书馆' % person)
    time.sleep(3)
    print('%s走出图书馆' % person)
    # 释放锁
    chair.release()

if __name__ == '__main__':
    # 创建一个信号量
    chair = Semaphore(10)
    for i in range(20):
        p = Process(target=library, args=('person%s' % i, chair))
        p.start()

这样就有多把锁了,可以提高效率。

多进程虽然会提高效率但是会占用资源,提高负担


事件Event

import time
from multiprocessing import Process, Event

def traffic_light(e):
    print('\033[31m红灯亮\033[0m')
    # flag = False
    while True:
        if e.is_set():   # 查看事件状态(True/False)
            time.sleep(1)
            e.clear()   # 设置事件状态为False
            print('\033[31m红灯亮\033[0m')
        else:
            time.sleep(1)
            e.set()   # 设置事件状态为True
            print('\033[32m绿灯亮\033[0m')
def car(e, i):
    if not e.is_set():   # not False
        print('car %s 在等待' % i)
        e.wait()   # 根据事件状态判断是否阻塞,若True 不阻塞,若False 阻塞
    print('car %s 通过了' % i)

if __name__ == '__main__':
    # 事件
    e = Event()  # flag属性,默认是false
    # 守护进程(红绿灯循环)
    p1 = Process(target=traffic_light, args=(e,))
    p1.daemon = True
    p1.start()

    p_li = []
    for i in range(10):
        time.sleep(0.5)
        p2 = Process(target=car, args=(e, i))
        p2.start()
        p_li.append(p2)

    for p in p_li:
        p.join()
红灯亮
car 0 在等待
绿灯亮
car 0 通过了
car 1 通过了
car 2 通过了
红灯亮
car 3 在等待
car 4 在等待
绿灯亮
car 3 通过了
car 4 通过了
car 5 通过了
car 6 通过了
红灯亮
car 7 在等待
car 8 在等待
绿灯亮
car 7 通过了
car 8 通过了
car 9 通过了

进程间的通信

关于队列

import time
from multiprocessing import Process, Queue

# maxsize设置队列中数据的上限,小于或等于0,则不限制
# 容器中的大于这个数据阻塞,直到队列中的数据被消除
q = Queue(maxsize=0)    # 创建队列

# 写入队列数据
q.put(0)
q.put(1)
q.put(2)
q.put(3)

# 删除队列,并返回数据
print(q.get())    # 0
print(q.get())    # 1

print(q.qsize())   # 2

生产者消费者模型

from multiprocessing import Process, Queue
import time
import random

# 消费者
def consumer(q, name):
    # 处理数据
    while True:
        food = q.get()
        if food is None:
            break
        print('{}吃了一个{}'.format(name, food))

# 生产者
def producer(q, name, food):
    for i in range(5):
        time.sleep(random.uniform(0.2, 0.9))
        print('{}生产了{}{}'.format(name, food, i))
        q.put(food+str(i))


if __name__ == '__main__':
    q = Queue()
    c1 = Process(target=consumer, args=(q, '一花'))
    c2 = Process(target=consumer, args=(q, '三玖'))
    c1.start()
    c2.start()

    p1 = Process(target=producer, args=(q, '二乃', '软糖'))
    p2 = Process(target=producer, args=(q, '四叶', '巧克力'))
    p1.start()
    p2.start()
  
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)
二乃生产了软糖0
一花吃了一个软糖0
四叶生产了巧克力0
三玖吃了一个巧克力0
二乃生产了软糖1
一花吃了一个软糖1
四叶生产了巧克力1
三玖吃了一个巧克力1
二乃生产了软糖2
一花吃了一个软糖2
二乃生产了软糖3
三玖吃了一个软糖3
四叶生产了巧克力2
一花吃了一个巧克力2
四叶生产了巧克力3
三玖吃了一个巧克力3
二乃生产了软糖4
一花吃了一个软糖4
四叶生产了巧克力4
三玖吃了一个巧克力4

进程池

优点:提高性能、运行效率,简化了进程管理的复杂性,可以自动平衡负载确保各进程之间负载大致均衡。

缺点:进程池的实现较为复杂,进程池需要使用系统资源,如果池中的进程数量过多,可能会导致系统资源紧张,影响系统性能。

程序有两种:

  • 计算密集型:充分利用cpu。多进程可以充分利用多核。
    • (适合开启多进程,但不适合开启很多进程)
  • IO密集型:大部分的时间在阻塞队列,而不是在运行状态。
    • (根本不适合开启多进程)
信号量
# 500件衣服 (任务)
# 500个人  (进程)
# 只有4台机器 (cpu)  (抢这四台机器)
# 抢到的这四个人做完衣服后才会将机器让出

多进程
# 500件衣服 (任务)
# 500个人  (进程)
# 只有4台机器 (cpu)  (抢这四台机器)
# 抢到的这四个人做了一会儿后就将机器让出

进程池
# 500件衣服
# 4个人
# 只有4台机器
# 没有进程的调度(切换)
import time
from multiprocessing import Pool, Process

def func(num):
    print('制作了{}件衣服'.format(num))

if __name__ == '__main__':
    start = time.time()
    # 创建一个进程池
    p = Pool(4)
    for i in range(500):
        # 异步提交任务到一个子进程中
        p.apply_async(func, args=(i,))
    p.close()  # 关闭进程池(用户不能再提交任务)
    p.join()
    time1 = time.time() - start
    print('==================')

    # 多进程
    start1 = time.time()
    p_li = []
    for i in range(500):
        p = Process(target=func, args=(i,))
        p.start()
        p_li.append(p)
    for p in p_li:
        p.join()
    time2 = time.time() - start1

    print('进程池做500个任务需要{}'.format(time1))   # 0.1612236499786377
    print('多进程做500个任务需要{}'.format(time2))   # 6.426870107650757

据此,明显看出进程池效率是很高的。

 

 

 

你可能感兴趣的:(python,pycharm)