一、进程
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)僵尸进程定义
-
- 僵尸进程产生的原因就是父进程产生子进程后,子进程先于父进程退出
-
- 但是父进程由于种种原因,并没有处理子进程发送的退出信号,那么这个子\就会成为僵尸进程。
-
-
2)用python写一个僵尸进程
#!/usr/bin/env python
#coding=utf8import os, sys, time
#产生子进程
pid = os.fork()if pid == 0:
#子进程退出
sys.exit(0)
#父进程休息30秒
time.sleep(30)先产生一个子进程,子进程退出,父进程休息30秒,那就会产生一个僵尸进程
-
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 Pooldef 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 timedef 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: 返回下一个元素