python学习——多进程和协程,单例模式

单例模式

  • 又叫做单子模式,是一种常见的软件件设计模式
  • 在应用这个模式的时候,必须保证只有一个实例存在
  • 常用语于多进程
  • 常用于资源共享,例如日志文件,配置文件和控制访问资源的场景下,例如,线程池,数据库连接池等
  • 避免由于资源操作时导致的性能问题或者方便资源之间的相互通信

进程的定义和状态

  • 一个程序(py文件)运行之后,代码+用到的资源成为进程
  • 进程是操作系统分配资源的基本单元,不仅可以通过线程完成多任务,进程也是可以的
  • 进程和线程之间的区别:
    • 1.操作系统会给进程分配一块独立的内存地址,来存储进程的相关资源,但是线程没有
    • 2.线程可以看做是进程的子单位,一个进程可以分为多个线程,同属于一个进程组的线程,共享同一块内存地址中的数据
    • 3.进程之间全程变量不共享,线程是会共享的
  • 进程的状态:
    • 就绪状态:运行的条件都满足了,正在等待CPU执行
    • 执行状态:CPU正在执行其功能
    • 等待状态:等在某些条件满足,例如一个程序sleep了,就是处于等待状态

所需模块及参数解释

  • 需要用到模块:multiprocessing
  • 导入模块 :import multiprocessing 或者from multiprocessing import Process
  • multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来创建一个进程对象,这个兑现可以理解为一个单独的进程,可以执行另外的事情
  • 如果是第一种导入,下面需要使用multiprocessing.Process来调用线程,第二种直接使用Process即可
  • Process方法中,需要传入参数,参数分别为:
    • group:指定进程组,大多数情况下用不到
    • target:表示进程调用的对象,也就是目标函数
    • name:为进程起的名字
    • args:为目标函数传递的实参,类型是元祖
    • kwargs:为目标函数传递的关键词参数,目标是字典

进程实例常用的方法和属性

方法

    • start():启动子进程实例(创建子进程)
    • is_alive():判断进程的子进程是否还活着
    • join()是否等待子进程执行结果,或者等待你多少秒
    • terminate()不管任务是否完成,立即终止子进程

属性

  • name:当前进程的别名,默认为Process-N,N是从1开始递增的整数
  • pid:当前进程的pid(进程号)
import multiprocessing
import requests
import time

def res_1():
    url = "https://www.baidu.com"
    payload = {}
    requests.request("GET", url, data=payload)
    time.sleep(1)


if __name__ == '__main__':
    data_1= []
    for i in range(3):
        a = multiprocessing.Process(target=res_1)
        a.start()
        data_1.append(a)
    print("进程是否还活着:{}".format(a.is_alive()))
    for a in data_1:
        a.join()
        print("进程的名字:{}".format(a.name))
        print("进程的pid:{}".format(a.pid))
        a.terminate()
        print("终止子进程之后,进程是否还活着:{}".format(a.is_alive()))

    from multiprocessing import Process
    data_2 = []
    for i in range(3):
        a = Process(target=res_1)
        a.start()
        data_2.append(a)
    print("进程是否还活着:{}".format(a.is_alive()))
    for a in data_2:
        a.join()# 等待其他子进程的结果
        print("进程的名字:{}".format(a.name))
        print("进程的pid:{}".format(a.pid))
    print("运行完之后,进程是否还活着:{}".format(a.is_alive()))

注意事项

  • 使用进程时,不添加if __name__ == '__main__':就会报错,原因:
    • 如果写在外面,python开启多进程后,执行进程创建就相当于在另外一个py文件中,使用import将方法/类导入,导入的时候自动执行,后面执行start操作,就相当于重新创建进程后执行,进入了循环创建、无线递归的状态,因此报错
  • 运行时是在进程下起一个子线程来运行,不同进程之间的子线程,不会共享全局变量

多进程之间的相互通讯

  • 因为多进程之间是不共享全局变量的,那么针对需要两个进程都需要处理的内容,多进程之间就需要进行相互通讯
  • 进程进行相互通讯的方式有:队列、进程池
  • 上面说的通讯队列,和queue.Queue不同,queue.Queue是进程内非阻塞队列
  • 但是进程通信所需要的multiprocessing.Queue,是跨进程的通信队列
  • 多进程前者是各自私有,后者是子进程共有
  • Process之间有时候需要通信,操作系统就提供了许多机制来实现进程之间的通信

队列:Queue

  • multiprocessing.Queue可以实现多进程之间的数据传递,Queue本身就是一个消息队列
  • Queue的工作原理是:在父进程中创建两个子进程,一个往Queue中填写数据,一个从Queue中读取数据
  • 注意点:进程之间的Queue要单做参数传入,因为进程之间不共享全局变量
import multiprocessing
import requests
import time
from multiprocessing import Queue

def res_1(q):
    # 判断队列中的元素个数,大于0就从队列中获取
    while q.qsize() > 0:
        url = q.get()# 从元素中获取
        payload = {}
        requests.request("GET", url, data=payload)
        print("第一个进程执行完毕***********************")
        time.sleep(1)

def res_2(q):
    while q.qsize() > 0:
        url = q.get()
        payload = {}
        requests.request("GET", url, data=payload)
        print("第二个进程执行完毕***********************")
        time.sleep(1)


if __name__ == '__main__':
    q = Queue()
    for i in range(10):
        q.put("https://www.baidu.com")
    data = []
    for i in range(3):
        a = multiprocessing.Process(target=res_1,args=(q,))
        a.start()
        print("进程是否还活着:{}".format(a.is_alive()))
        print("进程的名字:{}".format(a.name))
        print("进程的pid:{}".format(a.pid))
        data.append(a)
    for a in data:
        a.join() # 等在其他子进程的结果
        a.terminate() # 进程终止
    print("终止子进程之后,进程是否还活着:{}".format(a.is_alive()))

进程池

  • 当需要创建的子进程数量不多的时候,可以使用multiprocessing.Process,动态生成多个进程
  • 但是如果有上百甚至上千个目标,到后东去创建进程的工作量就巨大
  • 此时就可以用到multiprocessing模块中的pool方法

pool

  • 初始化pool时,可以指定一个最大进程数
  • 当有新的请求提交到pool中,如果进程池没有满,就会创建一个新的进程用来执行该请求
  • 但是如果进程池中的进程数已经达到最大值,那么该请求就会等待
  • 直到池中有进程结束,才会用之前的进程来执行新的任务

pool常用的方法

  • apply_async():使用非阻塞的方式调用func
    • 并行执行,堵塞方式必须等待上一个进程退出才会执行下一个进程
    • args为传递给func中的参数,元组形式
    • kwds为传递给func的关键词参数,字典形式
  • close():关闭pool,使其不再接受新的任务
  • terminate():不管任务是否完成,立即终止
  • join():主进程堵塞,等在子进程的退出,必须在close或者terminate之后使用
from multiprocessing import Pool
import os,time,random

def worker(msg):
    t_start = time.time()
    print("{}开始执行,进程号是{}".format(msg,os.getpid()))
    #random随机生成0-1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print("{}执行完毕,执行时间{}".format(msg,t_stop-t_start))

if __name__ == '__main__':

    po = Pool(3)
    for i in range(0,10):
        # Pool().apply_async(要调用的目标,传参为元组)
        # 每次循环将会有空闲的子进程去调用目标
        po.apply_async(func=worker,args=(i,))
    print("-------start--------")
    po.close()  # 关闭进程池,关闭之后po不再接受新的请求
    po.join()  # 等待进程池中所有的子进程完成,必须放在close之后
    print("--------end--------")

协程gevent

  • 线程之下可以创建协程,协程需要用到第三方库:gevent
  • 协程存在于线程之中,线程默认不会等待协程执行
  • 用户态(存在用户空间的数据)的轻量级线程(微线程)
  • 协程能保留上一次状态
  • 在单线程实现并发,串行操作,函数间来回切换执行
  • 遇到IO操作就切换,IO 操作完了再回来

协程实例常用的方法

  • spawn():开启协程,第一个参数为协程要执行的任务
  • join():让线程等待协程执行
  • gevent.sleep():协程之间切换的条件,耗时等待的情况下才会切换,自动切换
  • joinall():joinall可以用在单人舞等在,也可以用在批量任务等待,只能将列表中的所有等待的时间都执行了才能结束,可用于添加所有协程函数
  • 程序运行,首先要考虑协程,然后再考虑线程和进程
  • 补丁,所需下面两行代码
    • from gevent import monkey
    • monkey.patch_all()
    • 在多进程和多线程的情况下,不建议用补丁

练习

  • 10000个请求,使用开启2个进程,进程中开启4个线程,线程中开启5个协程来处理
  • 一共30个携程
import time
from threading import Thread
from multiprocessing import Process,Queue
import gevent  # 协程模块
import requests

# 计算时间的装饰器函数
def count_time(func):
    """ 计算函数运行时间的装饰器 """
    def wapper(*args,**kwargs):
        print("-----start-----")
        start_t = time.time()
        func(*args,**kwargs)
        end_t = time.time()
        print("-----end--------")
        print("耗时:{}".format(end_t - start_t))
    return wapper

def green_work(q,gname):
    """
    每个协程的工作函数
    :param q:
    :param gname:
    :return:
    """
    count = 0
    while not q.empty():
        url = q.get(timeout=0.1)
        payload = {}
        requests.request("GET", url, data=payload)
        gevent.sleep(0.1)
        count +=1
        time.sleep(1)
    print("---------协程{}执行了{}任务-----".format(gname,count))


def thread_work(q,tname):
    """
    每个此案城的执行任务函数,在线程中开启5个协程
    :param q: 进程、协程、线程的请求队列
    :param tname: 协程所属线程的名字
    :return:
    """
    g_list = []
    for i in range(5):
        gname = "{}-g-{}".format(tname,i)
        print("协程创建--------{}".format(gname))
        g = gevent.spawn(green_work,q,gname)
        g_list.append(g)
    gevent.joinall(g_list)

def process_work(q,pname):
    """
    每个进程执行的任务函数,在该进程中开启3个线程
    创建一个线程
    :param q: 进程之间通讯的任务队列
    :param name:线程所属进程的名字
    :return:
    """
    # 创建三个线程,并执行
    thread_list = []
    for i in range(3):
        tname = "{}-th-{}".format(pname,i)
        print("创建线程{}".format(tname))
        t = Thread(target=thread_work,args=(q,tname))
        thread_list.append(t)
        t.start()

    # 让主线程堵塞,等在子进程执行完毕
    for t in thread_list:
        t.join()


@count_time
def main():

    # 创建10000个请求队列
    q = Queue() # 进程、线程和协程共享一个请求队列
    for i in range(1000):# 10000
        q.put("https://www.baidu.com")
    # 开启两个进程处理
    print("队列创建完毕,数量:{}".format(q.qsize()))
    pro_list = []
    # 创建2个进程
    for i in range(2):
        pname = "pro-{}".format(i)
        print("创建进程{}".format(pname))
        p = Process(target=process_work,args=(q,pname))
        p.start()
        pro_list.append(p)
    # 让主进程等杂子进程执行结束再往下进行
    for p in pro_list:
        p.join()

if __name__ == '__main__':
    main()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值