并发编程(1)、多进程

操作系统基础

协调和管理和控制计算机硬件和软件资源的控制程序.
本身也是软件,但是不能随意更改,而且是一个大型,长寿且复杂的软件.
作用

  1. 封装好硬件复杂的接口,提供良好的抽象接口,运行应用程序只需要调用这些接口即可启动相应的硬件服务.例如启动暴风音影步骤:双击执行文件,获取应用软件在硬盘上的存储地址,操作系统会直接将硬盘上的数据读取到内存中,交给cpu运行;
  2. 管理、调度进程并且将多个进程对硬件的竞争变得有序.例如给每个进程分配运行时间,操作系统就收回权限分配给其他进程,若遇到io阻塞也会直接收取权限.

软件在操作系统上的执行流程

  1. 双击可执行文件;
  2. 获取软件在硬盘上的存储地址;
  3. 操作系统将数据读进内存;
  4. 交给cpu执行.

操作系统与普通软件的区别

  1. 主要区别是:你不想用暴风影音了你可以选择用迅雷播放器或者干脆自己写一个,但是你无法写一个属于操作系统一部分的程序(时钟中断处理程序),操作系统由硬件保护,不能被用户修改;
  2. 操作系统与用户程序的差异并不在于二者所处的地位。特别地,操作系统是一个大型、复杂、长寿的软件.

操作系统演化

第一代计算机:由真空管和穿孔卡片组成.程序员在墙上的机时表预约一段时间,然后程序员拿着他的插件版到机房里,将自己的插件板连接到计算机里,这几个小时内他独享整个计算机资源,后面的一批人都得等着(两万多个真空管经常会有被烧坏的情况出现)。后来出现了穿孔卡片,可以将程序写在卡片上,然后读入机而不用插件板.
优点: 程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug可以立刻处理).
缺点: 浪费计算机资源,一个时间段内只有一个人用;同一时刻只有一个程序在内存中,被cpu调用执行,比方说10个程序的执行,是串行的.

第二代计算机:批处理系统,把一堆人的输入攒成一大波输入,然后顺序计算(这是有问题的,但是第二代计算也没有解决).
优点: 批处理,节省了机时.
缺点: 1.整个流程需要人参与控制,将磁带搬来搬去;2.计算的过程仍然是顺序计算, 即串行;3.程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率,无法及时调试程序)

第三代计算机:多个联机终端 + 多道技术(针对单核
多道技术: 多个程序,为了解决多个程序竞争或者说共享同一个资源cpu的有序调度问题,解决方式为多路复用,复用分为时间上的复用和空间上的复用.
时间上的复用:当一个程序遇到IO阻塞, cpu会切换到其他的程序就行运行.
空间上的复用: 每个进程都有独立的内存空间,互不干涉.


进程理论

正在执行的一个过程或者一个任务,负责执行的则是cpu.
进程与程序的区别:程序仅仅指的是一堆代码,进程指的是程序的运行过程.例如做蛋糕的食谱就是程序,糕点师就是相当于处理器,做蛋糕的各种原材料就是输入的数据,进程就是指厨师阅读食谱,取来各种原料,以及烘培蛋糕等一系列动作的总和.

并发与并行

并发: 是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发;
并行: 同时运行,只有具备多个cpu才能实现并行.

进程状态

阻塞状态: 进程遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作.
就绪状态: 是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU.
运行状态

进程间通信

主要有管道和队列两种方式.

cs架构管道通讯

socket多进程示例

# server服务器
import socket
from multiprocessing import Process
import os
def run1(conn):
    """接收数据和返回数据"""
    print('子进程<%s>' % os.getpid())
    while True:
        data = conn.recv(1024)
        if not data: break
        res = '来自服务端<%s>的反馈:%s' %(os.getpid(), data)
        conn.send(res.encode('utf-8'))
    conn.close()

def server(ip, port):
    """启动服务器模版,并生成服务器,等待连接,
    有客户端连接变生成conn对象并启动对应的子进程开始通讯"""
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((ip, port))
    server.listen(5)
    while True:
        print('待连接 <%s>' % os.getpid())
        conn, addr = server.accept()
        # 开启子进程服务,可以同时服务多个客户端,
        # 将套接字对象作为参数传入到调动的函数中,
        # 注意必须以元祖的形式传入
        print('+++++++++')
        p = Process(target=run1, args=(conn,))
        p.start()
        
if __name__ == '__main__':
    print('主进程<%s>' % os.getpid())
    server('127.0.0.1', 8810)

# 此处要非常注意,必须放在main函数内部,否则会再次调用, 见下图
# print('<%s>============='% os.getpid())
# server('127.0.0.1', 8810)


# 客户端
import socket
import time

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8810))
while True:
    msg = input('enter>>> ')
    if not msg: continue
    client.send(msg.encode(('utf8')))
    data = client.recv(1024)
    print(data.decode('utf-8'))


队列Queue

多进程安全的队列,实现多进程之间的数据传递.
相关方法
maxsize: 是队列中允许最大项数,省略则无大小限制,存放的是消息而非大数据,且用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小;
q.put(): 用于插入数据;
q.get():从队列读取并删除一个元素,在存储数据时候若队列已经满再存入数据就会出现进程阻塞状态;
q.full():判断队列是否为存满数据;
q.empty():判断队列是否为空状态,在取出数据时候,若队列为空时,取数据也会出现进程阻塞状态.

from multiprocessing import Process, Queue
q = Queue(maxsize=3)

q.put(1)
q.put(2)
q.put(3)
# q.put(3)
print(q.full())  # 满了
# q.put(4) #再放就阻塞住了

print(q.get())
print(q.get())
print(q.get())
# print(q.get())
print(q.empty())  # 空了
# print(q.get()) #再取就阻塞住了

queue的升级版本:JoinableQueue
方法
q.task_done(): 使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常, 即get()必须与task_done()一一对应.
q.join(): 生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止.

# 生产消费者案例

from multiprocessing import JoinableQueue, Process
import os
import time

def consumer(q):
    """逐个取出序列的元素"""
    print('消费者进程<%s>' % os.getpid())
    while True:
        # 正常情况下,清空完成序列后此进程会在此处一直阻塞,
        print(q.get())
        q.task_done()


def producer(q, sequence):
    """循环遍历并将序列的元素放进队列中"""
    print('生产者进程<%s>' % os.getpid())
    # 模拟生产
    for item in sequence:
        q.put(item)
        time.sleep(1)


if __name__ == '__main__':
    sequence = [1, 2, 3, 4]
    q = JoinableQueue()
    pro_p = Process(target=producer, args=(q, sequence))
    con_p = Process(target=consumer, args=(q,))
    # 设置消费者进程为守护进程, 主进程结束也断开消费者进程
    con_p.daemon = True
    pro_p.start()
    con_p.start()
    print('阻塞====')
    pro_p.join()  # 阻塞直到生产者装货完毕
    q.join()  # 一直阻塞直到消费者清空元素序列
    print('主进程代码执行完毕')

Process模块

from multiprocessing import Process
开启子进程p = Process(target=task, kwargs={'name':'子进程1'})

参数target 表示调用对象,即子进程要执行的任务;kwargs 传输到子进程的参数;

常用方法
p.start(): 启动子进程,并调用该子进程中的p.run()
p.run(): 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法;
p.terminate(): 强制终止进程p,不会进行任何清理操作.如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁;
p.is_alive(): 如果p仍然运行,返回True;

# 示例
from multiprocessing import Process
import time

def task(name):
    print('%s starts running' % name)
    time.sleep(3)
    print('%s finish' % name)

if __name__ == '__main__':
    p = Process(target=task, kwargs={'name':'子进程1'})
    p.start()
    print('主进程')

join方法: 在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源.
p.join(): 主进程会一直等待子进程p运行完毕,否则一直阻塞.
os.getpid(): 查看当前进程编号;
os.getppid(): 查看父进程编号;

# 示例
def task():
    print('子进程<%s> 父进程<%s>' % (os.getpid(), os.getppid()))
    time.sleep(4)
    print("子进程<%s> finish"% os.getpid())

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()
    print('主进程<%s> 父进程<%s>' % (os.getpid(), os.getppid()))


# 自定义Process类
from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        """方法名称必须为run"""
        print('子进程<%s> 启动' % self.name)
        time.sleep(3)
        print('子进程<%s> 结束' % self.name)


if __name__ == '__main__':
    p = MyProcess('子进程1')
    p.start()   # 触发类的run()方法
    p.join()    #  阻塞,直到子进程程序执行完毕
    print('主进程!')

守护进程

若子进程被设置为守护进程,在主进程代码执行完之后会立即断开结束该守护进程,不论有没有执行完.
创建方法:在创建子进程后设置p.daemon=True
子进程启动后就不能设置为守护进程.

from multiprocessing import Process
import time
import os

def task(name):
    print('子进程%s<%s> starts' % (name, os.getpid()))
    time.sleep(2)
    print('子进程%s<%s> ends' % (name, os.getpid()))

if __name__ == '__main__':
    p = Process(target=task, args=('egon',))
    p.daemon = True
    p.start()
    # p.join()
    # time.sleep(1)
    # 只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
    print('主进程<%s>' % os.getpid())

互斥锁

进程之间数据不共享,但是共享同一套文件系统,所以同时修改同一个文件时候可能会出现问题,这时候就需要互斥锁.

from multiprocessing import Process, Lock
import os,time

def work(lock):
    lock.acquire()
    print('子进程<%s> 开始' %os.getpid())
    time.sleep(2)
    print('子进程<%s> 结束' %os.getpid())
    lock.release()

if __name__ == '__main__':
    lock = Lock()
    for i in range(3):
        p=Process(target=work, args=(lock,))
        p.start()

模拟购票系统

from multiprocessing import Process, Lock
import time, json

def search(name):
    """查询剩余的票数"""
    dic = json.load(open('db.json'))
    time.sleep(1)
    print('\033[43m%s 查到剩余票数%s\033[0m' % (name, dic['count']))

def get_ticket(name):
    """购票"""
    dic = json.load(open('db.json'))
    # 模拟读数据的网络延迟
    time.sleep(1)
    if dic['count'] <= 0:
        print('\033[46m%s 对不起,抢票失败!\033[0m' % name)
    else:
        dic['count'] -= 1
        time.sleep(1)  # 模拟写数据的网络延迟
        json.dump(dic, open('db.json', 'w'))
        print('\033[46m%s 购票成功!\033[0m' % name)

def task(name, mutex):
    """购票流程"""
    search(name)
    mutex.acquire()
    get_ticket(name)
    mutex.release()

if __name__ == '__main__':
    # 票数信息写进文件
    # json.dump({'count': 8}, open('db.json', 'w'))

    # 主进程,模拟并发10个客户端抢票
    mutex = Lock()
    for i in range(10):
        name = '<路人%s>' % i
        p = Process(target=task, args=(name, mutex))
        p.start()

生产消费者模式

单生产者及消费者模式:

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

def producer(q, name, food):
    """生产者"""
    for i in range(3):
        print('生产中')
        time.sleep(3)
        res = '%s%s' % (food, i)
        q.put(res)
        print('\033[45m%s 生产了 %s\033[0m' %(name, res))

def consumer(q, name):
    """消费者"""
    while True:
        print('in consumer')
        res = q.get()
        if res == None:  # 判断是否生产完毕
            print('序列已经清空!')
            break
        time.sleep(2)
        print('\033[43m%s 吃 %s\033[0m' % (name, res))

if __name__ == '__main__':
    q = Queue()  # 生成容器对象
    # 生产者们
    p1 = Process(target=producer,args=(q, 'egon', '包子'))
    # 消费者们
    c1 = Process(target=consumer,args=(q, 'alex'))
    # 开始
    p1.start()
    c1.start()
    p1.join()
    q.put(None)  # 当生产进程执行完毕后再放进None表示结束
    print('主进程执行结束====')

消费者生产者模式终极版本

from multiprocessing import Process, JoinableQueue
import time

def producer(q, name, food):
    """生产者"""
    for i in range(3):
        time.sleep(1)
        res = '%s%s' %(food, i)
        q.put(res)
        print('\033[45m%s 生产了 %s\033[0m' %(name,res))
    q.join()  # 当q为空的时候才会结束当前的生产者进程


def consumer(q, name):
    """消费者"""
    while True:
        res = q.get()
        if res == "_a_":  # 判断是否生产完毕
            print('%s结束消费'%name)
            break
        time.sleep(2)
        print('\033[43m%s 吃 %s\033[0m' % (name, res))
        # 每取走一个数据都会向生产者反馈信息, 直到消费完 q 中的所有产品
        q.task_done()


if __name__ == '__main__':
    q = JoinableQueue()  # 生成容器对象
    # 生产者进程
    p1 = Process(target=producer, args=(q, 'egon', '包子'))
    p2 = Process(target=producer, args=(q, 'kate', '袜子'))
    p3 = Process(target=producer, args=(q, 'steven', 'iphone'))
    # 消费者进程
    c1 = Process(target=consumer, args=(q, 'alex'))
    c2 = Process(target=consumer, args=(q, 'frank'))
    # 设置消费者进程为守护进程,当主程序执行完毕会自动回收
    c1.daemon = True
    c2.daemon = True
    # 生产者开始生产
    p1.start()
    p2.start()
    p3.start()

    # 消费者开始消费
    c1.start()
    c2.start()

    # 等待生产者进程结束
    print('=======')
    p1.join()
    p3.join()
    p2.join()

    # 此时队列一定为空
    print('主进程')

猜你喜欢

转载自www.cnblogs.com/fqh202/p/9483769.html