三程与三器

一、进程

1.1 什么是进程?

1.一个运行起来的程序就是一个进程.进程是资源分配的最小单位( 内存、cpu、网络、io)
2.当我们双击图标,打开程序的时候,实际上就是通过I/O操作(读写),硬盘中的代码读取到内存条里.内存条就是我们所指的资源(程序分配了内存资源,就变成了进程.
3.进程之间是相互隔离的, 具有独立的内存空间, 所以进程之间不能直接通信.

  3.进程的三大特性
  独立性:进程是独立存在的实体,拥有自己独立的资源,拥有自己的私有地址,在没有经过进程本身允许的情况下,一个用户的其他进程不能访问其他进程地址空间
  动态性:程序只是一个静态的指令集和,而进程是一个正在系统中活动的指令集和,进程在程序中加入了时间的概念,拥有了自己的生命周期和各种不同的状态
  并发性:多个进程可以在单个处理器上并发执行,不会互相影响

1.2 进程如何通信?

- 同一程序下进程通信
  - 进程queue(父子进程通信)
  - pipe(同一程序下两个进程通信)
  - managers(同一程序下多个进程通信)
- Java项目和python项目如何通信
  - RabbitMQ、redis等(不同程序间通信)

1.3 为什么需要进程池?

# 1.为什么需要进程池?
当任务量很大时, 一次性开启太多进程, 会消耗大量的资源, 导致服务器压力过大. 这时可以借助进程池来进行处理. .在进程池中一次性开启指定数量的进程, 当有任务打来时, 进程领取任务执行, 未被执行的任务等待进程空闲下来, 被领取执行. 这样可以大大减小服务器的压力, 一般开启的进程数可以参考服务器的CPU数量, 一般为cup数据量到其1.5倍数量.

# 2.进程池中的方法
	1)apply: 多个进程异步执行,一个一个的执行
	2)apply_async: 多个进程同步执行,同时执行多个进程

实现进程池:
from multiprocessing import Pool
实现线程池
from multiprocessing.pool import ThreadPool

# 3.代码实现
from  multiprocessing import Process,Pool
import time,os
def foo(i):
    time.sleep(2)
    print("in the process",os.getpid()) #打印子进程的pid
    return i+100

def call(arg):
    print('-->exec done:',arg,os.getpid())

if __name__ == '__main__':
    pool = Pool(3)                      #进程池最多允许5个进程放入进程池
    print("主进程pid:",os.getpid())     #打印父进程的pid
    for i in range(10):
        #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=foo, args=(i,),callback=call)

        #用法2 串行 启动进程不在用Process而是直接用pool.apply()
        # pool.apply(func=foo, args=(i,))

    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

1.4 僵尸进程

  • 1)僵尸进程定义

      1. 僵尸进程产生的原因就是父进程产生子进程后,子进程先于父进程退出
      1. 但是父进程由于种种原因,并没有处理子进程发送的退出信号,那么这个子\就会成为僵尸进程。
  • 2)用python写一个僵尸进程

    #!/usr/bin/env python
    #coding=utf8

    import os, sys, time
    #产生子进程
    pid = os.fork()

    if pid == 0:
    #子进程退出
    sys.exit(0)
    #父进程休息30秒
    time.sleep(30)

    先产生一个子进程,子进程退出,父进程休息30秒,那就会产生一个僵尸进程

  1. ps -ef| grep defunct 在linux下查看僵尸进程

    [root@linux-node4 ~]# ps -ef| grep defunct
    root 110401 96083 0 19:11 pts/2 00:00:00 python defunct.py
    root 110402 110401 0 19:11 pts/2 00:00:00 [python]
    root 110406 96105 0 19:11 pts/3 00:00:00 grep --color=auto defunct


1.5 Python中使用过的进程模块?

1.5.1 multiprocessing

  • multiprocessing是一个 使用类似于 线程模块的API ,支持产生进程的包。

  • 多处理包 提供 本地和远程 并发,通过 使用 子进程 而不是线程 有效地侧向执行全局解释器锁。

  • 因此,多处理模块允许程序员充分利用给定机器上的多个处理器。 它可以在Unix和Windows上运行。

  • 进程池抓取页面

    -- coding: utf-8 --

    import requests
    from multiprocessing import Pool

    def fetch_request(url):
    result = requests.get(url)
    print(result.text)

    def call(arg):
    print(’–>exec done:’,“测试进程池执行后回调功能”)

    url_list = [
    ‘https://www.baidu.com’,
    ‘https://www.google.com/’, #google页面会卡住,知道页面超时后这个进程才结束
    ‘http://dig.chouti.com/’, #chouti页面内容会直接返回,不会等待Google页面的返回
    ]

    if name == ‘main’:
    pool = Pool(10) # 创建线程池
    for url in url_list:
    #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
    pool.apply_async(func=fetch_request, args=(url,),callback=call)
    print(‘end’)
    pool.close() #关闭pool
    pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

1.5.2 concurrent.futures

1.简介

- 1、Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码
- 2、但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。
- 3、但从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,
- 4、实现了对threading和multiprocessing的进一步抽象,对编写线程池/进程池提供了直接的支持。

2、Executor和Future

- `1. Executor`
- concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。
- 但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用
- 我们可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
- `2. Future`
- Future你可以把它理解为一个在未来完成的操作,这是异步编程的基础,
- 传统编程模式下比如我们操作queue.get的时候,在等待返回结果之前会产生阻塞,cpu不能让出来做其他事情
- 而Future的引入帮助我们在等待的这段时间可以完成其他的操作。

3、concurrent.futures.ProcessPoolExecutor 抓取网页

import requests
from concurrent.futures import ProcessPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

if __name__ == '__main__':
    pool = ProcessPoolExecutor(10)        # 创建线程池
    for url in url_list:
        pool.submit(fetch_request,url)    # 去线程池中获取一个进程,进程去执行fetch_request方法
    pool.shutdown(wait = True)
    # shutdown相当于一个开关,它会读取程序中所设定的进程总数,直至每开启一个进程,它读取设定的总数就会减一,直至为0时便会打印主线程

二、线程

1.1 什么是线程

  • 1)线程是操作系统调度的最小单位
  • 2)线程是进程正真的执行者,是一些指令的集合(进程资源的拥有者)
  • 3)同一个进程下的多个线程共享内存空间,数据直接访问(数据共享)
  • 4)为了保证数据安全,必须使用线程锁

说明:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和

import threading
import time

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

1.2 GIL锁和线程锁

  • GIL全局解释器锁

    • 在python全局解释器下,保证同一时间只有一个线程运行
    • 防止多个线程都修改数据
  • 线程锁(互斥锁)

    • GIL锁只能保证同一时间只能有一个线程对某个资源操作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以操作了
    • 线程锁本质把线程中的数据加了一把互斥锁
      • 加上线程锁之后所有其他线程,读都不能读这个数据
    • 有了GIL全局解释器锁为什么还需要线程锁
      • 因为cpu是分时使用的
  • 在有GIL的情况下执行 count = count + 1 会出错原因解析,用线程锁解决方法

    1)第一步:count = 0 count初始值为0

    2)第二步:线程1要执行对count加1的操作首先申请GIL全局解释器锁

    3)第三步:调用操作系统原生线程在操作系统中执行

    4)第四步:count加1还未执行完毕,时间到了被要求释放GIL

    5)第五步:线程1释放了GIL后线程2此时也要对count进行操作,此时线程1还未执行完,所以count还是0

    6)第六步:线程2此时拿到count = 0后也要对count进行加1操作,假如线程2执行很快,一次就完成了

    count加1的操作,那么count此时就从0变成了1

    7)第七步:线程2执行完加1后就赋值count=1并释放GIL

    8)第八步:线程2执行完后cpu又交给了线程1,线程1根据上下文继续执行count加1操作,先拿到GIL

    锁,完成加1操作,由于线程1先拿到的数据count=0,执行完加1后结果还是1

    9)第九步:线程1将count=1在次赋值给count并释放GIL锁,此时连个线程都对数据加1,但是值最终是1

1.3 死锁定义

  • 两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

1.4 join()和setDaemon()

1.4.1 join()

  • 实现所有线程都执行结束后再执行主线程

    import threading
    import time
    start_time = time.time()

    def sayhi(num): #定义每个线程要运行的函数
    print(“running on number:%s” %num)
    time.sleep(3)

    t_objs = [] #将进程实例对象存储在这个列表中
    for i in range(50):
    t = threading.Thread(target=sayhi,args=(‘t-%s’%i,))
    t.start() #启动一个线程,程序不会阻塞
    t_objs.append(t)
    print(threading.active_count()) #打印当前活跃进程数量
    for t in t_objs: #利用for循环等待上面50个进程全部结束
    t.join() #阻塞某个程序
    print(threading.current_thread()) #打印执行这个命令进程

    print("----------------all threads has finished…")
    print(threading.active_count())
    print(‘cost time:’,time.time() - start_time)

1.4.2 setDaemon()

  • 守护线程,主线程退出时,需要子线程随主线程退出

    import threading
    import time
    start_time = time.time()

    def sayhi(num): #定义每个线程要运行的函数
    print(“running on number:%s” %num)
    time.sleep(3)
    for i in range(50):
    t = threading.Thread(target=sayhi,args=(‘t-%s’%i,))
    t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置
    t.start() #启动一个线程,程序不会阻塞
    print(‘cost time:’,time.time() - start_time)

1.5 Python中使用过的线程模块?

1.5.1 threading

  • Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。

  • thread和threading模块允许程序员创建和管理线程

    import threading
    import time

    def sayhi(num): #定义每个线程要运行的函数
    print(“running on number:%s” %num)
    time.sleep(3)

    for i in range(50):
    t = threading.Thread(target=sayhi,args=(‘t-%s’%i,))
    t.start()

三、协程

1.1 什么是协程

  • 1)协程又称微线程,纤程,本质是一个单线程
  • 2)协程能在单线程下处理高并发,因为遇到IO自动切换
    • 线程遇到I/O操作会等待、阻塞,协程遇到I/O会自动切换(剩下的只有CPU操作)
    • 线程的状态保存在CPU的寄存器和栈里而协程拥有自己的空间,所以无需上下文切换的开销,所以快
  • 3)为甚么协程能够遇到I/O自动切换
    • greenlet是C语言写的一个模块,遇到IO手动切换
    • 协程有一个gevent模块(封装了greenlet模块),遇到I/O自动切换
  • 4)协程拥有自己的空间,所以无需上下文切换的开销

1.2 协程优缺点

  • 协程缺点
    • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上
    • 协程如果阻塞掉,整个程序都阻塞
  • 协程最大的优点
    • 不仅是处理高并发(单线程下处理高并发)
    • 特别节省资源(协程本质是一个单线程,当然节省资源)

1.3 协程遇到I/O切换,那活只谁干的?

  • 简单说法
    • 协程遇到I/O后自动切换,但是会保持一个socket连接,交给系统内核去处理工作
    • epoll()就工作内核中,他维护了一个链表,来存放所有的socket连接
    • 当内核处理完成后就会回调一个函数,以socket文件描述符为key,结果为value存放到字典中
    • 此时这个列表还是在内核中,需要将这个字典拷贝到用户空间(用户进程中)
  • 本质
    • 1.epoll()中内核则维护一个链表,epoll_wait直接检查链表是不是空就知道是否有文件描述符准备好了。
    • 2.在内核实现中epoll是根据每个sockfd上面的与设备驱动程序建立起来的回调函数实现的。
    • 3.某个sockfd上的事件发生时,与它对应的回调函数就会被调用,来把这个sockfd加入链表,其他处于“空闲的”状态的则不会。
    • 4.epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术,避免了复制大量文件描述符带来的开销
    • 内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作

1.4 Python中协程的模块

- greenlet:遇到I/O`手动切换`,是一个C模块
- gevent:对greenlet封装,遇到I/O`自动切换`(`借助C语言库greenlet`)
- asyncio:和gevent一样,也是实现协程的一个模块(`python自己实现`)

1.5 协程

  • 特点 :gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理

    import gevent
    from gevent import monkey
    monkey.patch_all(select=False) # 注意,这个导包顺序不要变
    import requests

    这些请求谁先回来就先处理谁

    def fetch_async(method, url, req_kwargs):
    response = requests.request(method=method, url=url, `req_kwargs)
    print(response.url, response.content)

    ##### 发送请求

    gevent.joinall([
    gevent.spawn(fetch_async, method=‘get’, url=‘https://www.baidu.com/’, req_kwargs={}),
    gevent.spawn(fetch_async, method=‘get’, url=‘https://www.google.com/’, req_kwargs={}),
    gevent.spawn(fetch_async, method=‘get’, url=‘https://github.com/’, req_kwargs={}),
    ])


四、装饰器

1.1 什么是装饰器?(What)

  • 装饰器本质是函数,用来给其他函数添加新的功能
  • 特点:不修改调用方式、不修改源代码

1.2 装饰器的应用场景?(Where)

  • 用户认证,判断用户是否登录
  • 计算函数运行时间(算是一个功能、在项目里用的不多)
  • 插入日志的时候
  • redis缓存

1.3 为什么使用装饰器?(Why)

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用.

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

1.4 如何使用装饰器?

1.4.1 装饰器求函数运行时间

import time
def timer(func):   #timer(test1)  func=test1
    def deco(*args,`kwargs):
        start_time = time.time()
        func(*args,`kwargs)      #run test1
        stop_time = time.time()
        print("running time is %s"%(stop_time-start_time))
    return deco

@timer     # test1=timer(test1)
def test1():
    time.sleep(3)
    print("in the test1")
test1()

1.4.2 三级装饰器

#! /usr/bin/env python
# -*- coding: utf-8 -*-
import time
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, `kwargs):
            print("wrapper func args:", *args, `kwargs)
            print('运行前')
            func(*args, `kwargs)
            print('运行后')
        return wrapper
    return outer_wrapper

@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"
home()

1.5 装饰器在项目中应用场景

  • 身份验证
  • 写入日志
  • redis缓存

1.5.1 身份验证

user,passwd = 'aaa','123'
def auth(func):
    def wrapper(username,password,*args,`kwargs):
        if user == username and password == passwd:
            print("User has passed authentication")
            res = func(username,password,*args,`kwargs)   #这里执行func()相当于执行调用的函数如home()
            return res          #为了获得home()函数返回值,可以将执行结果赋值给res然后返回print(home())结果是"from home"而不是"None"了
        else:
            raise ValueError("非合法用户")
    return wrapper

@auth
def home(username,password):
    print("welcome to home page")
    return "from home"

home('aaa','123')

1.5.2 记录日志

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from functools import wraps
import traceback
def decoratore(func):
    @wraps(func)
    def log(*args,`kwargs):
        try:
            print("当前运行方法",func.__name__)
            return func(*args,`kwargs)
        except Exception as e:
            print(traceback.format_exc())  # 这里应该调用log模块来记录到日志里
    return log

@decoratore
def test():
    int('a')
    pass

if __name__ == '__main__':
    test()
    
    ''' 上面运行结果
    当前运行方法 test
    Traceback (most recent call last):
      File "C:/Users/tom/Desktop/alipay_demo/aaa/t2.py", line 11, in log
        return func(*args,`kwargs)
      File "C:/Users/tom/Desktop/alipay_demo/aaa/t2.py", line 18, in test
        int('a')
    ValueError: invalid literal for int() with base 10: 'a'
     22222
    '''

五.生成器和迭代器

01.生成器

1.1 什么是生成器?(What)

  • 生成器就是一个特殊的迭代器
  • 一个有yield关键字的函数就是一个生成器
    • 生成器是这样一个函数,它记住 上一次返回时 在函数体中的位置。

    • 对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

      def test():
      yield 1
      print(‘aaaa’)
      yield 2
      print(‘bbb’)

      r1 = test()

      r1.next()
      r1.next()

1.2 生成器哪些场景应用?(Where)

  • 生成器是一个概念,我们平常写代码可能用的并不多,但是python源码大量使用

  • 比如我们tornado框架就是基于 生成器+协程

  • 在我们代码中使用举例

  • 比如我们要生成一百万个数据,如果用生成器非常节省空间,用列表浪费大量空间

    import time
    t1 = time.time()
    g = (i for i in range(100000000))
    t2 = time.time()
    lst = [i for i in range(100000000)]
    t3 = time.time()
    print(‘生成器时间:’,t2 - t1) # 生成器时间: 0.0
    print(‘列表时间:’,t3 - t2) # 列表时间: 5.821957349777222

1.3 为什么使用生成器

  • 节省空间
  • 高效

02.迭代器

2.1 什么是迭代器(W)

  • 迭代器是访问集合内元素的一种方法
    • 总是从集合内第一个元素访问,直到所有元素都被访问过结束,当调用 next而元素返回会引发一个,StopIteration异常
  • 有两个方法:_ _ iter _ _ _ _next _ _
    • iter : 返回迭代器自身
    • next: 返回下一个元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值