并发编程-进程

操作系统的发展史

输入输出设备>>>:IO操作即(input和output)

  • 手工操作穿孔卡片
  • 批处理(磁带)
  • 脱机批处理系统

一步步的优化,其实都是在提高计算机CPU利用率的问题(问题在于时串行并且没有空间上的复用)

多道技术的产生

解决cpu在执行程序,遇到io时,不干活的情况

串行:一个程序完完整整的运行完毕,才能运行下一个程序

并发:看上去像同时运行

多道技术:

  • 空间上的复用(多个程序共一套硬件设备,它是多道技术实现时间上的复用的基础,不然还要去硬盘读数据)
  • 时间上的复用(单个cpu的电脑上,起多个应用程序。cpu快速切换,给人的感觉是同时运行)
  • 一个任务占用cpu时间过长或被操作系统强行剥夺走cpu的执行权限(比起串行效率反而降低)
  • 一个任务执行过程中遇到io操作,也会被操作系统强行剥夺走cpu的执行权限(比起串行效率提高)

并发编程之进程

定义:程序是一堆代码,当这一堆代码被执行的过程就是一个进程的产生,进程是一个实体,每一个进程都有自己的独立的内存空间

同步和异步:

​ 同步:提交任务后在原地等待任务的返回结果,此期间不做任何操作。

​ 异步:提交任务后,不等待任务的返回结果,继续执行后面的代码。

阻塞与非阻塞:

​ 阻塞:遇到IO操作 >>>会处于阻塞状态

​ 非阻塞:就绪或者运行状态 >>>就绪态,运行态

SocketServer模块介绍(让tcp也能支持并发)

并发

# TCP socketserver使用
import socketserver
class MyTcpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)  # 对于tcp,self.request相当于conn对象
                if len(data) == 0:break
                print(data)
                self.request.send(data.upper())
            except ConnectionResetError:
                break
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyTcpServer)
    server.serve_forever()

# UDP socketserver使用
import socketserver


class MyUdpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            data, sock = self.request
            print(data)
            sock.sendto(data.upper(), self.client_address)


if __name__ == '__main__':
    server = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyUdpServer)
    server.serve_forever()

开启进程的两种方式:

'''
创建进程的两种方法
一
    from multiprocessing import Process
    import time
    def task(name):
        print('%s is running'%name)
        time.sleep(3)
        print('%s is over'%name)
    if __name__ == '__main__':
        p1 = Process(target=task,args=('一个姑娘',))
        p1.start()
        print('一个少年')

二
    from multiprocessing import Process
    import time
    class MyProcess(Process):
        def __init__(self,name):
            super().__init__()
            self.name = name
        def run(self):
            print('%s is running'%self.name)
            time.sleep(3)
            print('%s is over'%self.name)
    if __name__ == '__main__':
        obj = MyProcess('一个姑娘')
        obj.start()
        print('一个帅气的少年')
'''
进程对象的 join 方法:
'''
使用join方法让主进程等待子进程执行完毕,在执行主进程,join只会让主进程等待,不会对子进程有任何影响
from multiprocessing import Process
import time
def task(name):
    print('我曾有幸遇见这样一个%s '%name)
    time.sleep(3)
    print('遗憾也只是遇见这样一个%s!'%name)
if __name__ == '__main__':
    p1 = Process(target=task,args=('美丽姑娘',))
    p1.start()
    p1.join()
    print('I wish her happiness!')

同时创建多个进程使用 join 方法:
from multiprocessing import Process
import time
def task(name,n):
    print('%s is running'%name)
    time.sleep(n)
    print('%s is over'%name)
if __name__ == '__main__':
    l = []
    for i in range(3):
        p = Process(target=task,args=('一个姑娘',i))
        p.start()
        l.append(p)
        for i in l:
            i.join()
        print('一个少年')

'''

补充:进程对象之间是完全隔离开的,互相不会产生干预,除非人为干预!

进程对象的其它相关方法了解:
'''
from multiprocessing import Process
import time
import os
def task():
    print('%s is running'%os.getpid())  #os模块的getpid方法可以查看到此进程在电脑上的PID编号
    time.sleep(3)
    print('%s is over'%os.getppid()) # getppid可以查到此进程的父进程PID编号
if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    p1.terminate()    # 结束子进程
    time.sleep(0.3)    
    print(p1.is_alive())    #  判断子进程是否以结束,返回布尔值
    print('一个少年')
'''
进程对象之互斥锁:

详解:解决多个进程操作同一份数据,造成数据不安全的情况。

补充重点:锁千万不要随意使用,是牺牲效率来保证数据的安全,将并发或者并行转成串行的输出。锁一定要在主进程中创建,给子进程使用,通常在对数据操作的部分进行加锁,一把锁不能同时被多个人拥有,没有抢到锁的人只能等待锁释放。
'''
模拟抢票中使用互斥锁

from multiprocessing import Process,Lock
import time
import json
import random
def select(i):
    with open('info','r',encoding='utf-8')as f:
        data = json.load(f)   
    print('姑娘查询余票数:%s'%data.get('yu'))
def buy(i):
    # 买票也要先查询余票
    with open('info','r',encoding='utf-8')as f:
        data = json.load(f)
    time.sleep(random.randint(1,3))  #  模拟网络延迟
    if data.get('yu') > 0:
        data['yu'] -= 1
        with open('info','w',encoding='utf-8')as f:    # 更新数据
            json.dump(data,f)
        print('姑娘%s抢票成功!'%i)
    else:
        print('姑娘%s查询余票为0'%i)
def run(i,mutex):
    select(i)
    mutex.acquire()    # 抢锁
    buy(i)
    mutex.release()    # 释放锁
if __name__ == '__main__':
    mutex = Lock()    # 定义锁
    for i in range(100):
        p = Process(target=run,args=(i,mutex))
        p.start()

'''
p.daemon :守护进程(必须在开启之前设置守护进程):如果父进程死,子进程p也死了
p.join:父进程等p执行完了才运行主进程,是父进程阻塞在原地,而p仍然在后台运行。

terminate:强制关闭。(确保p里面没有其他子进程的时候关闭,如果里面有子进程,你去用这个方法强制关闭了就会产生僵尸进程(打个比方:如果你老子挂了,你还没挂,那么就没人给你收尸了,啊哈哈))

is_alive:关闭进程的时候,不会立即关闭,所以is_alive立刻查看的结果可能还是存活

p.join():父进程在等p的结束,是父进程阻塞在原地,而p仍然在后台运行

p.name:查看名字

p.pid :查看id

我们可以简单介绍一下僵尸进程:

子进程运行完成,但是父进程迟迟没有进行回收,此时子进程实际上并没有退出,其仍然占用着系统资源,这样的⼦进程称为僵尸进程

因为僵尸进程的资源一直未被回收,造成了系统资源的浪费,过多的僵尸进程将造成系统性能下降,所以应避免出现僵⼫进程。

from multiprocessing import Process
import os
import time
def work():
    print('%s is working'%os.getpid())
    time.sleep(3)
if __name__ == '__main__':
    p1 =Process(target=work)
    p2 =Process(target=work)
    p3 =Process(target=work)
    # p1.daemon = True
    # p2.daemon = True #守护进程(守护他爹)
    # p3.daemon = True  #主进程死了子进程也死了(就不会执行子进程了)
    p1.start()
    p2.start()
    p3.start()

    p3.join()
    p2.join()
    p1.join() #多个join就是在等花费时间最长的那个运行完就执行主程序了
    print('主程序')

# -了解方法---------------
#     p1.terminate()  #强制关闭进程
#     time.sleep(3)
#     print(p1.is_alive())  #看是不是还活着
#     print(p1.name) #查看进程名字
#     print(p1.pid) #查看id号
#     print('主程序')
使用队列实现进程中的通信:
1.队列:队列类似于一条管道,元素先进先出
需要注意的一点是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态
2.队列分类
队列有很多种,但都依赖与模块queue
queue.Queue()  #先进先出
queue.LifoQueue() #后进先出
queue.PriorityQueue() #优先级队列
queue.deque() #双线队列
队列实现进程中通信
from multiprocessing import Process,Queue
def put_info(q):
    q.put('hello world!')
def get_ingo(q):
    print(q.get())
if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=put_info,args=(q,))
    g1 = Process(target=get_ingo,args=(q,))
    p1.start()
    g1.start()
 

了解方法:

q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.

q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)

q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

生产者和消费者模型:

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

基于队列实现生产者消费者模型:

生产者消费者模型
from multiprocessing import Process,JoinableQueue
import time,random
def producer(name,wawa,q):   # 定义生产者
    for i in range(8):
        data = '%s制造出第%s个%s!'%(name,i,wawa)
        time.sleep(random.randint(1,2))
        print(data)
        q.put(data)

def consumer(name,q):   # 定义使用者
    while True:
        data = q.get()
        if data is None:break
        time.sleep(random.randint(1,2))
        print('%s使用了%s'%(name,data))
        q.task_done()
if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer,args=('人间清凉','林志玲版娃娃',q))
    c1 = Process(target=consumer,args=('会写代码的大人物',q))
    p1.start()
    c1.daemon = True
    c1.start()
    p1.join()
    q.join()
    print('主!!!!')

猜你喜欢

转载自www.cnblogs.com/zhouyixian/p/10833317.html