发布于2019-08-03 09:01 阅读(3741) 评论(0) 点赞(10) 收藏(3)
类方法:是类对象的方法,在定义时需要在上方使用“@classmethod”进行装饰,形参为cls,
表示类对象,类对象和实例对象都可调用;
类实例方法:是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
静态方法:是一个任意函数,在其上方使用“@staticmethod”进行装饰,可以用对象直接调用,
静态方法实际上跟该类没有太大关系。
编程语言中内存的管理和分配一般有两种方式,程序手动回收和系统自动回收,前者的代表性语言就是C++,而python使用的是系统自动回收的机制。自动化管理的方式大大提供程序员的开发效率的同时,但是会在一些特殊的场景下可能会错误处理的情况,就像前段时间的美国波音737事件,传感器错误数据引起的飞机自动化系统操作强制下降,最终酿成事故
在python中是可能出现内存泄漏的,比如当对象A引用了对象B,同时对象B又引用了对象A,即出现了循环引用,Python中使用引用计数的内存管理方式,此时A和B的引用计数就会永远大于1,也就是说解释器将无法自动回收对象A和B的内存空间,也就产生了内存泄漏。随着该模块对象创建次数的逐渐增加,在操作系统负载上可以看到该Python程序内存持续上升,知道系统资源不够进程被强制终止
有,有出现过性能问题,之前我参与的一个项目中有出现过内存泄漏的情况。
当时经过跟踪后发现有一个项目代码过程中有一个对象A引用对象B,同时对象B又引用对象A,即出现了循环引用。编程语言中内存的管理和分配一般有两种方式是,程序手动回收或者系统自动回收。前者的代表性语言就是C++,而Python使用的是系统自动回收的机制,自动化管理的方式大大提高了程序员的开发效率,但是在一些特殊的场景下也可能出现错误处理的情况,就像前段时间的美国波音737事件,传感器错误数据引发的飞机自动化系统操作强制下降,最终酿成事故
python中使用引用计数的内存管理方式,循环引用出现时对象的引用计数将永远大于等于1,也就是说解释器将无法自动回收对象A和B的内存空间,就会产生内存泄漏
####如何避免
1.不适用一个对象时 使用del object来删除一个对象的引用计数,可以有效防止内存泄露
2. 通过python扩展模块 gc 来查看不能回收的对象的详细信息
3. 可以通过sys.getrefcount(obj)来获取对象的引用计数,并根据返回值是否为0,来判断是否内存泄漏
Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。
函数的传值到底是值传递还是引用传递,要分情况:
不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象
可变参数是引用传递的:比如像列表,字典这样的对象是通过引用传递、和C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变。
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。
*args 是不定长参数,他可以表示输入参数是不确定的,可以是任意多个。
**kwargs 是关键字参数,赋值的时候是以键= 值的方式,参数是可以任意多对在定义函数的时候不确定会有多少参数会传入时,就可以使用两个参数。
1. a = 10
2. b = 20
3. c = [a]
4. a = 15
###结果:10 对于字符串,数字,传递的是相应的值
递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是return;返回终止递归。
终止的条件:
内建类型:布尔类型、数字、字符串、列表、元组、字典、集合;
输出字符串‘a’的所有内建方法;
[0, 1, 4]
hasattr(object, name)函数:
判断一个对象里面是否有name 属性或者name 方法,返回bool 值,有name 属性(方法)返回True,否则返回False。
注意:name 要使用引号括起来。
1. class function_demo(object):
2. name = 'demo'
3. def run(self):
4. return "hello function"
5. functiondemo = function_demo()
6. res = hasattr(functiondemo, 'name') #判断对象是否有name 属性,True
7. res = hasattr(functiondemo, "run") #判断对象是否有run 方法,True
8. res = hasattr(functiondemo, "age") #判断对象是否有age 属性,Falsw
9. print(res)
getattr(object, name[,default]) 函数:
获取对象object 的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。
注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号()。
1. functiondemo = function_demo()
2. getattr(functiondemo, 'name') #获取name 属性,存在就打印出来--- demo
3. getattr(functiondemo, "run") #获取run 方法,存在打印出方法的内存地址---<bound method function_demo.run of
<__main__.function_demo object at 0x10244f320>>
4. getattr(functiondemo, "age") #获取不存在的属性,报错如下:
5. Traceback (most recent call last):
6. File "/Users/liuhuiling/Desktop/MT_code/OpAPIDemo/conf/OPCommUtil.py", line 39, in <module>
7. res = getattr(functiondemo, "age")
8. AttributeError: 'function_demo' object has no attribute 'age'
9. getattr(functiondemo, "age", 18) #获取不存在的属性,返回一个默认值
setattr(object,name,values)函数:
给对象的属性赋值,若属性不存在,先创建再赋值
1.class function_demo(object):
2. name = 'demo'
3. def run(self):
4. return "hello function"
5.functiondemo = function_demo()
6.res = hasattr(functiondemo, 'age') # 判断age 属性是否存在,False
7.print(res)
8.setattr(functiondemo, 'age', 18 ) #对age 属性进行赋值,无返回值
9.res1 = hasattr(functiondemo, 'age') #再次判断属性是否存在,True
综合使用:
class function_demo(object):
name = 'demo'
def run(self):
return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, 'addr')
# 先判断是否存在if res:
if res:
addr = getattr(functiondemo, 'addr')
print(addr)
else:
addr = getattr(functiondemo, 'addr', setattr(functiondemo, 'addr', '北京首都'))
#addr = getattr(functiondemo, 'addr', '美国纽约')
print(addr)
在Python2 中:
reduce(lambda x,y: x*y, range(1,n+1))
注意:Python3 中取消了该函数。
def multipliers1():
return [lambda x: i * x for i in range(4)]
print([m(2) for m in multipliers1()])
###【6,6,6,6】
def multipliers2():
for i in range(4): yield lambda x: i*x
print([m(2) for m in multipliers2()])
###【0,2,4,6】
上述问题产生的原因是Python 闭包的延迟绑定。这意味着内部函数被调用时,参数的值在闭包内
进行查找。因此,当任何由multipliers()返回的函数被调用时,i 的值将在附近的范围进行查找。那时,
不管返回的函数是否被调用,for 循环已经完成,i 被赋予了最终的值3。
一种解决方法就是用Python 生成器。
如上第二哥函数
1.使用__new__方法
class A(object):
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = object.__new__(cls)
return cls._instance
else:
return cls._instance
2.使用模块
新建一个py文件 mysingleton.py
class Singleton(object):
def foo(self):
pass
singleton = Singleton()
#将上面的代码保存在文件mysingleton.py中,要使用时,直接在其他文件中导入词文件中的对象,这个对象就是单例模式的对象
3.使用装饰器
def Singleton(cls):
_instance = {}
def _singleton(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@Singleton
class A(object):
a = 1
def __init__(self, x=0):
self.x = x
a1 = A(2)
a1 = A(3)
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。1.网站的计数器2.应用配置3.多线程池4.
数据库配置,数据库连接池5.应用程序的日志应用....
迭代器是一个更抽象的概念,任何对象,如果它的类有next 方法和iter 方法返回自己本身,对于string、list、
dict、tuple 等这类容器对象,使用for 循环遍历是很方便的。在后台for 语句对容器对象调用iter()函数,iter()
是python 的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()
也是python 的内置函数。在没有后续元素时,next()会抛出一个StopIteration 异常。
生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数
据的时候使用yield 语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置
和所有的数据值)
区别:生成器能做到迭代器能做的所有事,而且因为自动创建了iter()和next()方法,生成器显得特别简洁,而且
生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当
发生器终结时,还会自动抛出StopIteration 异常。
print([[x for x in range(1, 100)][i:i + 3] for i in range(0, 100, 3)]) ##利用切片的原理
不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。
Python 中,数值类型(int 和float)、字符串str、元组tuple 都是不可变类型。而列表list、字典dict、集合set 是可变类型。
is 判断的是a 对象是否就是b 对象,是通过id 来判断的。
==判断的是a 对象的值是否和b 对象的值相等,是通过value 来判断的。
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是两个下划线包围来命名的(比如__init__,lt),Python 的魔法方法是非常强大的,所以了解其使用方法也变得尤为重要!
init 构造器,当一个实例被创建的时候初始化的方法。但是它并不是实例化调用的第一个方法。
__new__才是实例化对象调用的第一个方法,它只取下cls 参数,并把其他参数传给init。__new__很少使
用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
call 允许一个类的实例像函数一样被调用。
getitem 定义获取容器中指定元素的行为,相当于self[key] 。
getattr 定义当用户试图访问一个不存在属性的时候的行为。
setattr 定义当一个属性被设置的时候的行为。
getattribute 定义当一个属性被访问的时候的行为。
面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。
面向对象有三大特性:封装、继承、多态。
封装:将某些内容先封装到一个地方,等需要的时候再去调用
class School:
def __init__(self,name,age): #构造方法,创建对象是执行
self.name=name
self.age=age
#创建对象a1,a2
a1=School("zhangsan",18)
a2=School("lisi",18)
继承:即攀升类(子类)可以继承基类(父类)的方法,我们可以将多个类共有的方法提取到父类中,这样子类仅需要继承父类而不必实现每个方法
#使用class创建一个School类
class School:
def __init__(self,name,age):
self.name=name
self.age=age
def student(self):
print("name:%s,age:%s"%(self.name,self.age))
def classroom(self):
print("%s去教室"%self.name)
class SchoolA(School): #SchoolA继承School
def __init__(self,name):
self.name=name
class SchoolB(SchoolA): #SchoolB继承SchoolA
def __init__(self,name):
self.name=name
#创建对象a1
a1=SchoolA("zhangsan")
a1.classroom()
#创建对象a2
a2=SchoolB("lisi")
a2.classroom()
#执行结果:
# zhangsan去教室
# lisi去教室
多继承
在python中 类可以继承多个类,在继承多个类时,它对类中的函数查找有两种当时,他对类中的函数查找有两种方式
深度优先:类是经典类hi,多继承的情况下,会按照深度有限的方式查找
广度优先:类是新式类,多继承的情况下,会按照广度优先的方式查找
(在python3中)都默认是广度优先,但还是可以了解一下两个的区别,
新式类:当前类或者基类继承了object类 就叫新式类,否则就是经典类
在python2.7中
#python2.7中经典类
class A():
def name(self):
print("AAAAAA")
class B(A):
pass
class C(A):
def name(self):
print("CCCCCC")
class D(B,C):
pass
a1=D()
a1.name() #输出:AAAAAA
#查找顺序:# 首先去自己D类中查找,如果没有,则继续去B类中找,没有则继续去A类中找,没有则继续去C类中找,如果还是未找到,则报错
#深度优先:D-B-A-C
#python2.7中新式类
class A(object):
def name(self):
print("AAAAAA")
class B(A):
pass
class C(A):
def name(self):
print("CCCCCC")
class D(B,C):
pass
a1=D()
a1.name() #输出:CCCCCC
#查找顺序:# 首先去自己D类中查找,如果没有,则继续去B类中找,没有则继续去C类中找,没有则继续去A类中找,如果还是未找到,则报错
#广度优先:D-B-C-A
多态
不同的子类对象调用相同的父类方法,产生不同执行效果,可以增加代码的外部调用灵活度。父类变量能够引用子类对象,当子类中有重写父类父类方法,调用的将是子类对象
①出处:鸭子类型的名称来源于西方谚语:一只鸟长得像鸭子,叫声像鸭子,走路也像鸭子,那它就是鸭子!
②原则:如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
③优缺点:
④应用实例:list、tuple该两个类极为相似,但却没有用继承来实现,它们互为鸭子类型
match()函数只检测RE 是不是在string 的开始位置匹配,
search()会扫描整个string 查找匹配;
也就是说match()只有在0 位置匹配成功的话才有返回,
如果不是开始位置匹配成功的话,match()就返回none。
1. re.findall(r’目的字符串’,’原有字符串’) #查询
2. re.findall(r'cast','itcast.cn')[0]
3. re.sub(r‘要替换原字符’,’要替换新字符’,’原始字符串’)
4. re.sub(r'cast','heima','itcast.cn')
<.*>是贪婪匹配,会从第一个“<”开始匹配,直到最后一个“>”中间所有的字符都会匹配到,中间可能会包含“<>”
<.*?>是非贪婪匹配,从第一个“<”开始往后,遇到的第一个“>”:结束匹配,这中间的字符串都会匹配到,但是不会有“<>”
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
. | 匹配任意除换行符"\n"外的字符。在DOTALL 模式中也能匹配换行符。 | a.c | abc |
\ | 转义字符,使后一个字符改变原来的意思。如果字符串中有字符*需要匹配,可以使用*或者字符集[*] | a.c | a. c a\c |
[...] | 字符集(字符类)。对应的位置可以是 字符集中任意字符。字符集中的字 符可以逐个列出,也可以给出范围, 如[abc]或[a-c]。第一个字符如果是^ 则表示取反,如[^abc]表示不是abc 的其他字符。 所有的特殊字符在字符集中都失去 其原有的特殊含义。在字符集中如 果要使用]、-或^,可以在前面加上反 斜杠,或把]、-放在第一个字符,把^ 放在非第—个字符。 |
a[bcd]e | abe ace ade |
\d | 数字:[0-9] | a\dc | a1c |
\D | 非数字:[^\d] | a\Dc | abc |
\s | 空白字符:[<空格> t\r\n\f\v] | a\sc | ac |
\S | 非空白字符:[^\s] | a\Sc | abc |
\w | 单词字符:[A-Za-z0-9_] | a\wc | abc |
\W | 非单词字符:[^\W] | a\Wc | ac |
* | 匹配前一个字符0 或无限次 | abc* | ab abccc |
+ | 匹配前一个字符0 次或无限次 | abc+ | abc abccc |
? | 匹配前一个字符0 次或1次 | abc? | Ababc |
{m} | 匹配前一个字符m | ab{2}c | abbc |
进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间
片、pid。
创建进程:
1.首先要导入multiprocessing 中的Process;
2.创建一个Process 对象;
3.创建Process 对象时,可以传递参数;
1.p = Process(target=XXX, args=(元组,) , kwargs={key:value})
2.target = XXX 指定的任务函数,不用加()
3.args=(元组,) , kwargs={key:value} 给任务函数传递的参数
4.使用start()启动进程;
5.结束进程。
Process 语法结构:
Process([group [, target [, name [, args [, kwargs]]]]])
target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码
args:给target 指定的函数传递的参数,以元组的形式进行传递
kwargs:给target 指定的函数传递参数,以字典的形式进行传递
name:给进程设定一个名字,可以省略
group:指定进程组,大多数情况下用不到
Process 创建的实例对象的常用方法有:
start():启动子进程实例(创建子进程)
is_alive():判断进程子进程是否还在活着
join(timeout):是否等待子进程执行结束,或者等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process 创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N 为从1 开始递增的整数
pid:当前进程的pid(进程号)
给子进程指定函数传递参数Demo:
1.import osfrom multiprocessing import Process
2.import time
3.
4.def pro_func(name, age, **kwargs):
5. for i in range(5):
6. print("子进程正在运行中,name=%s, age=%d, pid=%d" %(name, age, os.getpid()))
7. print(kwargs)
8. time.sleep(0.2)
9.
10.if __name__ == '__main__':
11. # 创建Process 对象
12. p = Process(target=pro_func, args=('小明',18), kwargs={'m': 20})
13. # 启动进程
14. p.start()
15. time.sleep(1)
16. # 1 秒钟之后,立刻结束子进程
17. p.terminate()
18. p.join()
#注意:进程间不共享全局变量。
进程之间的通信-Queue
在初始化Queue()对象时,(例如q=Queue(),若在括号中没有指定最大可接受的消息数量,或数
量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头)
Queue.qsize():返回当前队列包含的消息数量。
Queue.empty():如果队列为空,返回True,反之False。
Queue.full():如果队列满了,返回True,反之False。
Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block 默认值为
True。
如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞
(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout 秒,若还
没读取到任何消息,则抛出"Queue.Empty"异常;
如果block 值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
Queue.get_nowait():相当Queue.get(False);
Queue.put(item,[block[, timeout]]):将item 消息写入队列,block 默认值为True;
如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此
时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待
timeout 秒,若还没空间,则抛出"Queue.Full"异常;
如果block 值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
Queue.put_nowait(item):相当Queue.put(item, False);
进程间通信Demo:
1.from multiprocessing import Process, Queueimport os, time, random
2.# 写数据进程执行的代码:def write(q):
3. for value in ['A', 'B', 'C']:
4. print('Put %s to queue...' % value)
5. q.put(value)
6. time.sleep(random.random())
7.# 读数据进程执行的代码:def read(q):
8. while True:
9. if not q.empty():
10. value = q.get(True)
11. print('Get %s from queue.' % value)
12. time.sleep(random.random())
13. else:
14. break
15.if __name__=='__main__':
16. # 父进程创建Queue,并传给各个子进程:
17. q = Queue()
18. pw = Process(target=write, args=(q,))
19. pr = Process(target=read, args=(q,))
20. # 启动子进程pw,写入:
21. pw.start()
22. # 等待pw 结束:
23. pw.join()
24. # 启动子进程pr,读取:
25. pr.start()
26. pr.join()
27. # pr 进程里是死循环,无法等待其结束,只能强行终止:
28. print('')
29. print('所有数据都写入并且读完')
1.# -*- coding:utf-8 -*-
2.from multiprocessing import Poolimport os, time, random
3.def worker(msg):
4. t_start = time.time()
5. print("%s 开始执行,进程号为%d" % (msg,os.getpid()))
6. # random.random()随机生成0~1 之间的浮点数
7. time.sleep(random.random()*2)
8. t_stop = time.time()
9. print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
11.po = Pool(3) # 定义一个进程池,最大进程数3
12.for i in range(0,10):
13. # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
14. # 每次循环将会用空闲出来的子进程去调用目标
15. po.apply_async(worker,(i,))
16.
17.print("----start----")
18.po.close() # 关闭进程池,关闭后po 不再接收新的请求
19.po.join() # 等待po 中所有子进程执行完成,必须放在close 语句之后
20.print("-----end-----")
协程的概念最早提出于1963年,但由于其不符合当时崇尚的“自顶向下”的程序设计思想,未能成为当时主流编程语言的一部分
20世纪60年代,进程的概念被引入,进程作为操作系统资源分配和调度的基本单位,多进程的方式很长时间内大大提高了系统运行的效率,虽然中间产生了Copy-On-Write等技术的出现,但进程的频繁创建和销毁代价较大,资源的大量复制和分配耗时任然较高,于是80年代出现了能独立运行的单位--线程,调度执行的最小单位。多线程之间可以直接共享资源,同时线程之间得通信效率远高于进程间,讲任务并发得性能再次向前推了一大步,不过多线程有很多不足得地方,虽然说线程之间切花代价相较进程小了很多,但是一些场景下线程CPU时间片的大量切换其实是做了很多不必要的无用功,特别是python中因为GIL锁的存在,其多线程很多时候并不能提供程序运行效率,于是协程的概念又开始发挥了作用,是一个线程在执行,只有当该子程序内部发生中断或阻塞时,才会交出线程的执行权交给其他子程序,在适当的时候在返回来接着执行。这省区了线程间频繁切换的时间开销,同时也解决了多线程加锁造成的相关问题
具体的生产环境中,Python项目经常会使用多进程+协程的方式,规避GIL锁的问题,充分利用多核的同时又充分发挥协程高效的特性。
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全,那么怎么解决多线程竞争问题?-- 锁。
锁的好处:确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。
锁的坏处:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
锁的致命问题:死锁。
锁(Lock)是Python 提供的对线程控制的对象。有互斥锁、可重入锁、死锁。
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,
互相干等着,程序无法执行下去,这就是死锁。
##死锁不代表程序终止,加上事物 ,过一段时间会回滚
GIL 锁(有时候,面试官不问,你自己要主动说,增加b 格,尽量别一问一答的尬聊,不然最后等到的一句话就是:你还有什么想问的么?)
GIL 锁全局解释器锁(只在cpython 里才有)
作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以cpython 里的多线程其实是伪多线程!
所以Python 里常常使用协程技术来代替多线程,协程是一种更轻量级的线程,
进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent 下切换是遇到了耗时操作才会切换。
三者的关系:进程里有线程,线程里有协程。
每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。
同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。
异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个
同时执行的任务的结果,这个就叫回调!
阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。
同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。
并行:同一时刻多个任务同时在运行。
实现并行的库:multiprocessing
并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况。
实现并发的库:threading
多进程适合在CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。
多线程适合在IO 密集型操作(读写数据操作较多的,比如爬虫)。
IO 密集型:系统运作,大部分的状况是CPU 在等I/O (硬盘/内存)的读/写。
CPU 密集型:大部份时间用来做计算、逻辑判断等CPU 动作的程序称之CPU 密集型。
更好的理解I/O模型,需要先回顾:同步、异步、阻塞、非阻塞
注:同步不等于阻塞
I/O模型总计有五种,其中信号驱动I/O,在实际中并不常用,主要还是学习另外四种I/O模型
web开发中主要碰到的是网络I/O,对于一个network IO 它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段:
#1)等待数据准备 (Waiting for the data to be ready)
#2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
这些IO模型的区别就是在两个阶段上各有不同的情况。在网络中常用的I/O操作有(accept,recv,send),其中send的感官比较少,主要是只存在本地copy阶段,对于网络传输如何不关注。
按照TCP/IP五层协议描述
1.首先进行域名解析,域名解析具体过程如下:
2.应用层:浏览器发起HTTP请求
3.传输层:选择传输协议,TCP/UDP,TCP是可开的传输控制协议,对HTTP请求进行封装,加入端口号等信息;提供端到端的链接
4.网络层:通过IP协议讲ip地址封装成ip数据报,通过路由传输到对端,采用ARP协议,主机发送信息时讲包含目标的ip地址的ARP请求广播到网络上所有的主机,并接收返回信息,以此确定目标的物理地址
5.数据链路层:根据mac地址,建立链接
6.物理层:物理层传输010101的数据流
7.服务器户端要的资源,传回给客户端;断开TCP链接,浏览器对也买你进行渲染呈现给客户端
http协议:
https协议:
注:关于http版本的相关内容还待学习,主要是1.0/1.1/2.0版本之间的区别
http请求报文:HTTP 请求报文由请求行、请求头部、空行 和 请求包体 4 个部分组成,如下图所示:
http响应报文:响应报文由状态行、响应头部、空行 和 响应包体 4 个部分组成,如下图所示:
请求报文以及响应报文相关具体的应用,需要参考具体的项目或者是实例。
每个系列常用的code
2xx:200(get请求成功),201(post,put创建了一个资源),204(删除一个资源,服务器删除成功)
3xx:301(服务器永久移动,自动转发到新的位置),302(服务器临时移动,原服务器没有永久移除)俩者的最大区别为搜索引擎是否记录
4xx:400(客户端请求语法错误),403(服务器拒绝提供服务),404(客户端引用了不存在的资源)
5xx:500(服务器错误,拒绝请求),503(服务器当前不能处理客户请求,当前服务器不可用),504(请求超时,没有到达网关)
500,503,504常见场景
500:常见场景为编程语言语法错误,web脚本错误,高并发,打开文件数超过系统资源限制,一般解决思路为查看服务器nginx,python的错误日志,负载均衡,修复脚本错误
503:常见场景为服务器无法使用,一般为服务器超载或者是停机维护,解决思路为查看服务器系统资源或者确定服务器开启状态
502,504:常见场景为web服务器故障,程序进程不够,一般解决思路为查看nginx代理的问题,或者是nginx的conf配置相关
问题1: 请详细描述三次握手和四次挥手的过程,并画出状态图
问题2: 四次挥手中TIME_WAIT状态存在的目的是什么?
问题3: TCP是通过什么机制保障可靠性的?
2.问题回答
问题1:
状态图如下
补充知识:TCP报文中共计6个标志位,每个标志位占1个字节,即URG、ACK、PSH、RST、SYN、FIN等
三次握手详情
四次挥手详情(被动关闭)
补充1:
SYN攻击:在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:
#netstat -nap | grep SYN_RECV
补充2:
四次挥手的同时关闭状况:实际中还会出现同时发起主动关闭的情况,具体流程如下图
问题2:
在四次挥手中,第三次挥手结束后,Client端进入TIME_WAIT状态,客户端不会马上进入closed状态,理由如下
问题3:
TCP传输的可靠性主要靠以下手段来保证传输
建议:滑动窗口与流量控制视情况是否说明
补充:滑动窗口与流量控制
1).滑动窗口:
“窗口”对应的是一段可以被发送者发送的字节序列,其连续的范围称之为“窗口”;
“滑动”则是指这段“允许发送的范围”是可以随着发送的过程而变化的,方式就是按顺序“滑动”。
案例如下:
TCP建立连接的初始,B会告诉A自己的接收窗口大小,比如为‘20’:
字节31-50为发送窗口
A发送11个字节后,发送窗口位置不变,B接收到了乱序的数据分组:
只有当A成功发送了数据,即发送的数据得到了B的确认之后,才会移动滑动窗口离开已发送的数据;同时B则确认连续的数据分组,对于乱序的分组则先接收下来,避免网络重复传递:
2).流量控制
流量控制方面主要有两个要点需要掌握。一是TCP利用滑动窗口实现流量控制的机制;二是如何考虑流量控制中的传输效率。
\1. 流量控制
所谓流量控制,主要是接收方传递信息给发送方,使其不要发送数据太快,是一种端到端的控制。主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送,案例如图:
这里面涉及到一种情况,如果B已经告诉A自己的缓冲区已满,于是A停止发送数据;等待一段时间后,B的缓冲区出现了富余,于是给A发送报文告诉A我的rwnd大小为400,但是这个报文不幸丢失了,于是就出现A等待B的通知||B等待A发送数据的死锁状态。为了处理这种问题,TCP引入了持续计时器(Persistence timer),当A收到对方的零窗口通知时,就启用该计时器,时间到则发送一个1字节的探测报文,对方会在此时回应自身的接收窗口大小,如果结果仍未0,则重设持续计时器,继续等待。
\2. 传递效率
一个显而易见的问题是:单个发送字节单个确认,和窗口有一个空余即通知发送方发送一个字节,无疑增加了网络中的许多不必要的报文(请想想为了一个字节数据而添加的40字节头部吧!),所以我们的原则是尽可能一次多发送几个字节,或者窗口空余较多的时候通知发送方一次发送多个字节。对于前者我们广泛使用Nagle算法,即:
当到达的数据已达到发送窗口大小的一半或以达到报文段的最大长度时,就立即发送一个报文段;
对于后者我们往往的做法是让接收方等待一段时间,或者接收方获得足够的空间容纳一个报文段或者等到接受缓存有一半空闲的时候,再通知发送方发送数据。
作者:忘也不能忘
链接:https://www.pythonheidong.com/blog/article/3822/6e326e98504c3bd315c9/
来源:python黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 python黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-1
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!