题型:python

2020-12-13 04:34

阅读:348

标签:comm   行操作   回滚   光缆   魔法   导入   lte   源代码   malloc   

目录

  • 变量
    • 变量定义
    • 作用域
    • 可变类型和不可变类型
    • is和==区别
    • 引用、浅拷贝、深拷贝
    • 推导式
    • %和format
    • 连接字符串用join还是+
  • 函数
    • 函数参数传递
    • lambda
    • 函数式编程
    • *args和**kwargs
    • 函数的工作原理
  • 迭代器和生成器
    • 迭代器
    • 生成器
  • 面向切面编程AOP
    • AOP
    • 闭包
    • 装饰器
  • 面向对象编程OOP
    • OOP
    • 鸭子类型
    • 类变量和实例变量
    • 实例方法、类方法、静态方法
    • 单下划线和双下划线
    • 重载
    • 新式类和旧式类
    • 魔法函数
    • 元类
    • 自省
  • 内存管理和垃圾回收
    • 内存管理
    • 垃圾回收
    • 内存的结构
    • 栈顶和栈底
  • 设计模式
    • 单例模式
  • 网络编程
    • ISO七层协议
    • wsgi
    • 一次http请求的过程
    • socket
  • 并发编程
    • GIL全局解释器锁
    • 进程和线程
    • 进程间通信
    • 并发和并行
    • 协程
    • Queue
    • 线程同步
    • 线程池和进程池
    • 同步、异步、阻塞、非阻塞
    • select、poll、epoll
    • UNIX五种I/O模型
    • io多路复用+回调+事件循环
    • 异步编程和回调之痛
    • 协程
    • next和send源码
    • 生成器send、throw、close
    • yield from
    • yield from应用
    • pep380生成器yield from
    • asyncio
    • yield和await
    • asyncio:wait&gather
    • asyncio之task调度过程
  • 数据结构
    • 列表和元组之间的区别
    • dict底层结构
    • LRUCache
  • 算法
    • 排序算法
      • 快速排序
      • 归并排序
    • 查询算法
      • 二分查找
  • 数据库
    • 事务
    • 事务的异常和隔离性
    • 数据类型
    • 存储引擎
    • 数据库索引
    • mysql分库分表
    • 分布式锁
    • Redis
    • Redis特性
    • Redis持久化RDB
    • Redis持久化AOF
    • Redis事务
    • Redis实现分布式锁
    • 缓存模式
    • 缓存问题

变量

变量定义

# 什么是变量
用标识符命名的存储单元的地址称为变量,变量是用来存储数据的,通过标识符可以获取变量的值,也可以对变量进行赋值

作用域

创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,该空间称作为作用域
Python中,一个变量的作用域在声明时就决定了,与在何处调用无关
函数作用域的LEGB顺序
L:local 函数内部作用域
E:enclosing 函数内部与内嵌函数之间
G:global 全局作用域
B:build-in 内置作用域
python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的

可变类型和不可变类型

# 可变类型(mutable):列表、字典
当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类,就称可变数据类型

# 不可变类型(immutable):数字、字符串、元组
对于不可变类型来说,变量保存的实际上都是对象的引用,对其进行修改时,会开辟一块内存空间创建了一个新的对象并指向它,然后将原对象的引用计数-1

# 例1
    a = 1
    print(a)
    a = 2
    print(a)
# 例2
        b = [1, 2, 3]
    print(id(b))
    b[1] = 1
    print(id(b))

is和==区别

is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址
==:比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法

引用、浅拷贝、深拷贝

import copy
a = 'Allen'
a = b 
c = copy.copy(a)
d = copy.deepcopy(a)
# 赋值:传递的a所指对象的引用,如果是可变类型修改后,影响原对象,不可变类型则创建一个新的对象,并指向新的对象,将原对象的应用计数-1
# 浅拷贝:将一个对象的引用拷贝到别一个对象,对拷贝的对象作出改动,会影响到原对象
# 深拷贝:将一个对象拷贝到别一个对象,对拷贝的对象作出改动时,不会影响到原对象

推导式

# 字典推导式
d = {'allen': 'name'}
print({v: k for v, k in d.items()})
# 列表推导式
print([i.upper() for i in 'abcdef'])
# 集合推导式
print({i for i in 'abcdef'})
# 生成器推导式
print((i for i in range(10)))

%和format

%:无法同时传递一个变量和元组
c = (250, 250)
s1 = "pos:%s" % c # 报一个异常:TypeError: not all arguments converted during string formatting
c = (250, 250)
s1 = 'pos:%s'(c,)
format:就不会存在上面的问题
c = (250, 250)
s1 = 'pos:{}'.format(c)
# python3.6支持 f-strings
c = (250, 250)
s1 = f'pos:{c}'

连接字符串用join还是+

# +
当用操作符+连接字符串的时候,每执行一次+都会申请一块新的内存,然后复制上一个+操作的结果和本次操作的右操作符到这块内存空间,因此用+连接字符串的时候会涉及好几次内存申请和复制
# join
join在连接字符串的时候,会先计算需要多大的内存存放结果,然后一次性申请所需内存并将字符串复制过去

函数

函数参数传递

# 例1
a = 1
def fun(a):
    a = 2
    # 由于是不可变类型,在向不可变类型变量赋值时,就是创建新的对象并引用,将原有的对象引用计数-1
fun(a)
print(a) # 结果为1

# 例2
a = []
def fun(a):
    a.append(1)
    # 因为list是可变类型,可以在原处修改
fun(a)
print a  # [1] # 

lambda

# lambda
语法:lambda argument_list:expression
lambda是python预留关键字,argument_list和expression有用户自定义;
argument_list:参数列表 # *arg, **kwargs, None
expression:表达式,表达式中出现的参数必须在argument_list中定义,并且表达式只能是单行的
lambda argument_list:expression表示的是一个函数,这个函数叫做lambda函数
# 三个特性
1.lambda函数式匿名的:所谓匿名函数,lambda函数没有名字,引用计数为0,使用一次就释放
2.lambda函数有输入和输出:输入是argument_list中传递的参数,输出是:根据expression计算出来的值
3.lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能
# 四种用法
1.将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数
2.将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换
3.将lambda函数作为其他函数的返回值,返回给调用者
4.将lambda函数作为参数传递给其他函数

函数式编程

纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言
# 函数式编程的三大特性
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数
1.immutable data 不可变数据
2.first class function
3.尾递归优化 # 尾递归优化技术——每次递归时都会重用stack!!!python不支持
函数式编程的几个技术
# filter函数
此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]
# sorted函数
此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]
# map函数
此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]
# reduce函数
python3中从"functools"中导入
此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是'1, 2, 3, 4, 5, 6, 7, 8, 9'

*args和**kwargs

当你不确定你的函数里将要传递多少参数时你可以用*args,它可以接受任意数量的参数:
def f1(*args):
        for couter,item in enumerate(args):
                print(f'{couter}-{item}')
        
f1(1, 2, 3, 4)# 我们可以传递任意个数的参数
**kwargs允许你使用没有事先定义的参数名:
def f2(**kwargs):
        for key,value in kwagrs.items():
                print(key,value)
*args和**kwargs可以同时存在,但是*args必须在**kwargs前面,
# 我们可以在调用函数传递参数是使用*和**
def f3(*args, **kwargs):
        print(args,kwargs)

t = (1,2,3)
d = {'name':'allen','age':18}
f3(*t, **d) # *将t拆成一个一个位置参数:1, 2, 3,**将d拆成一个个的关键字参数:name='allen','age'=18
# 缺省参数即是调用该函数时:
        # foo(x, y=1)
        缺省参数的值若未被传入,则传入默认预设的值
# 多值参数
    函数参数列表中,参数前增加一个*可以接收元组,增加两个*可以接收字典。 
    *args(arguments) 存放元组参数 
    **kwargs 存放字典参数

函数的工作原理

def foo():
        bar()

def bar():
        pass

首先要知道当我们执行一个python程序实际上是由python解释器运行的(CPython是运行在C语言之上)
python解释器会用一个叫做PyEval_EvalFrameEx()的C函数去执行foo函数,在C函数执行foo函数时,首先会创建一个栈帧对象(stack frame),栈帧是一个上下文,保存命名空间的全局/局部字典,当前正在执行字节码的信息,索引指针,然后将代码转成字节码对象,字节码是运行在栈帧上下文中的,当foo调用子函数bar时,又会创建一个栈帧对象,在新创建的栈帧对象上下文中执行字节码
# 所有的栈帧时分配在堆内存中的,堆内存的特性就是如果我们分配的堆内存不去释放,那么会一直保存在堆内存中
函数工作的流程图
# 1
PyEval_EvalFrameEx(PyFrameObject *f)

        PyFrameObject
        f_back # 指向调用的栈帧对象,此时foo没有调用的栈帧对象
        f_code # 指向foo的字节码对象

                PyCodeObject
                        foo's bytecode #
# 2
PyEval_EvalFrameEx(PyFrameObject *f)

            PyFrameObject
        f_back # 指向foo的栈帧对象
        f_code # 指向bar的字节码对象
      
                PyCodeObject
                        bar's bytecode #

迭代器和生成器

迭代器

# 可迭代对象 iterable
        对象必须拥有__iter__方法,该方法返回一个迭代器对象
# 迭代器对象
        迭代器是访问集合内元素的一种方式,一般用来遍历数据
        迭代器和以下标的访问方式不一样,迭代器是不能返回的,迭代器提供了一种惰性访问数据的方式
        对象必须拥有__iter__和__next__方法,执行是返回迭代器中的下一项,或者引起StopIteration异常,终止迭代
# 迭代过程
        使用iter(iterable object)内置函数,该函数利用可迭代对象的__iter__方法生成一个迭代器对象;每一步调用next(iterator)内置函数调用迭代器对象,利用迭代器的__next__方法生成一个值,直到抛出StopIteration异常,结束迭代
# 自定义可迭代对象
from collections.abc import Iterator

class MyIterator(Iterator):
    def __init__(self, iterable):
        self.iterable = iterable
        self.index = 0

    def __next__(self):
        try:
            result = self.iterable[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

class MyIterable(object):
    def __init__(self, employee):
        self.employee = employee

    def __iter__(self):
        return MyIterator(self.employee)

emp = ['allen', 'kevin', 'collins']
my = MyIterable(emp)
for i in my:
    print(i)
----------------------------------------------------------------
# 迭代器是访问集合内元素的一种方式,一般用来遍历数据
# 迭代器和下标获取的方式不同,迭代器不能返回,迭代器提供了一种惰性的访问机制

# 迭代协议
# python是基于协议的编程的
# 一个可迭代对象内部实现一个__iter__魔法函数,调用iter是返回一个迭代器对象,迭代器实现__iter__和__next__的魔法函数
class Iterable(object):
    def __init__(self, seq):
                self.seq = seq
        def __iter__(self):
        return Iterator(self.seq)

class Iterator(object):
        def __init__(self, seq)
            self.seq = seq
        self.index = 0
    def __next__(object):
        try:
            ele = self.seq[self.index]
        except InderError:
                        raise StopIteration
                index+=1
        return ele

生成器

# 生成器
    只要在函数中出现yield关键字,他就是一个生成器
    生成器可以挂起执行并且保持当前执行的状态
    生成器对象,在python编译字节码的时候就产生了
    生成器对象,实现了迭代协议,所以我们可以使用for循环来遍历
    生成器与函数运行过程有所不同,PyGenObject(生成器对象)封装了一个栈帧对象和字节码对象,而栈帧对象中ilast保存生成器最后执行的位置(刚开始为-1,意味着生成器尚未开始),当我们调用send时(预激),生成器执行到第一个yield暂停,并返回值,基于这一特性生成器可以在任何时候被任何函数恢复执行
    函数中声明了 yield 关键字,python解释器在编译字节码的时候,将该函数标记成生成器
# 基于生成器的协程
        python3之前没有原声协程,只有基于生成器的协程
        pep342赠强了生成器功能
    生成器可以通过yield暂停执行和产出数据
    同时支持send()向生成器发送数据和throw()向生成器抛出异常
# 基于生成器的协程注意点
        协程序需要使用send(None)或者next(coroutine)来"预激"(prime)才能启动
    在yield处协程会暂停执行
    单独的yield value会产出值给调用方
    可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield表达式左边的变量,value=yield
    协程执行完成后(没有遇到下一个yield语句)会抛出StopIteration异常
# 协程装饰器
from functools import wraps
# 避免每次都要用send预激它
def coroutine(func):
    @wraps(func)
    def primer(*args,**kwargs): # 这样就不用每次都用send(None)启动了
        """装饰器:向前执行到第一个yield表达式,预激func"""
        gen = func(*args, **kwargs)
        next(gen)
        return gen
        return primer

面向切面编程AOP

AOP

# AOP
简言之、这种在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程
我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
优点是:这样的做法,对原有代码毫无入侵性

闭包

# 内部函数包含对外部作用域而非全局作用域的引用
# 闭包的意义:返回的函数对象,不仅仅是一个函数对象:在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
# 应用领域:延迟计算(原来我们是传参,现在我们是包起来)
# 装饰器就是闭包函数的一种应用场景
当一个内嵌函数引用其外部作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
1.必须有一个内嵌函数
2.内嵌函数必须引用外部函数中的变量
3.外部函数的返回值必须是内嵌函数
def index(path):
    def get():
        fd = open(path)
        fd.read()
        fd.close()
    return get

装饰器

# 装饰器
经常被用于有切面需求的场景,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用
装饰器的原则:
        1.不修改被装饰对象的源代码 
    2.不修改被装饰对象的调用方式
装饰器的目标:为被装饰对象添加上新功能
# 装饰模版
# 装饰器就是把其他函数作为参数的函数
def decorator(func):
    # 在函数里面,装饰器在运行中定义函数: 包装.
    # 这个函数将被包装在原始函数的外面,所以可以在原始函数之前和之后执行其他代码..
    def wrapper(*args, **kwargs):
        # 把要在原始函数被调用前的代码放在这里
        print "Before the function runs"
        # 调用原始函数(用括号)
        result = func(*args, **kwargs)
        # 把要在原始函数调用后的代码放在这里
        print "After the function runs"
    # 在这里"a_function_to_decorate" 函数永远不会被执行
    # 在这里返回刚才包装过的函数
    # 在包装函数里包含要在原始函数前后执行的代码.
    return wrapper
# 假如你建了个函数,不想修改了
def foo():
    print "I am a stand alone function, don't you dare modify me"
foo()
#输出: I am a stand alone function, don't you dare modify me
# 现在,你可以装饰它来增加它的功能
# 把它传递给装饰器,它就会返回一个被包装过的函数.
new_foo = decorator(foo)
# 执行
nwe_foo()
#输出s:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# 装饰器语法糖
def decorator(func):
        def wrapper(*args, **kwargs):
                print('Before func runs')
        result =func(*args, **kwargs)
        print('After func runs')
        return wrapper

@decorator # 其实就是decorator(foo)简写
def foo(*args, **kwargs):
        print('Hello')
# 无参装饰器
def decorator(func):
        def wrapper(*args, **kwargs):
                result = func(*args, **kwargs)
                return result
        return wrapper

# 有参装饰器
def decorator(*arg, **kwargs):
    def wrapper(func):
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return result
        return inner
    return wrapper
# 叠加多个装饰器
1. 加载顺序(outter函数的调用顺序):自下而上
2. 执行顺序(wrapper函数的执行顺序):自上而下
# 案例
def wrapper1(func):
    print('loading wrapper1')

    def inner1(*args, **kwargs):
        print('execute inner1')
        result = func(*args, **kwargs)
        return result

    return inner1
def wrapper2(func):
    print('loading wrapper2')

    def inner2(*args, **kwargs):
        print('execute inner2')
        result = func(*args, **kwargs)
        return result

    return inner2
def wrapper3(func):
    print('loading wrapper3')

    def inner3(*args, **kwargs):
        print('execute inner3')
        result = func(*args, **kwargs)
        return result

    return inner3
@wrapper1
@wrapper2
@wrapper3
def my_func():
    print('From my_func')

# 装饰器知识
装饰器使函数调用变慢了.一定要记住.
装饰器不能被取消(有些人把装饰器做成可以移除的但是没有人会用)所以一旦一个函数被装饰了.所有的代码都会被装饰.
Python自身提供了几个装饰器,像property, staticmethod,classmetho
Django用装饰器管理缓存和视图的权限.
Twisted用来修改异步函数的调用.

面向对象编程OOP

OOP

面向对象是相当于面向过程而言的,面向过程语言是一种基于功能分析的,以算法为中心的程序设计方法,而面向对象是一种基于结构分析的,以数据为中心的程序设计思想。在面向对象语言中有一个很重要的东西,叫做类。面向对象有三大特性:封装、继承、多态。
面向对象的基本特征
      1.封装
    简单来讲: 将现实世界的事物抽象成计算机领域中的对象,对象同时具有属性和行为,这种抽象就是封装.
    封装的一个重要特性: 数据隐藏. 对象只对外提供与其它对象交互的必要接口,而将自身的某些属性和实现细节对外隐藏,
    通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
    这样就在确保正常交互的前提下,保证了安全性.
      2.继承
    面向对象的一个重要特性是复用性.继承是实现复用性的一个重要手段.
    可以在不重复编写以实现的功能的前提下,对功能进行复用和拓展.
    继承概念的实现方式有二类:实现继承与接口继承。
      *实现继承是指直接使用基类的属性和方法而无需额外编码的能力
      *接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力
      被继承的类叫做父类
      继承的类叫做派生类、子类
      3.多态
                多态就是不同的对象可以调用相同的方法然后得到不同的结果
                多态的几个前提
      * a:要有继承关系。
      * b:要有方法重写。
      * c:要有父类引用指向子类对象。
          多态的好处
      * a:提高了代码的维护性(继承保证)
      * b:提高了代码的扩展性(由多态保证)
    多态的限制
          * 不能使用子类的特有属性和行为。

鸭子类型

# 当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
# python中的鸭子类型允许我们使用任何提供所需方法的对象,而不需要迫使它成为一个子类。
# 在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的
python鸭子类型的体现
file StringIO socket 对象都有read方法,所以都叫 like file object
比如定义__iter__魔法方法的对象可以使用for循环迭代它

类变量和实例变量

# 类变量(类创建后在全局是唯一的)
类变量定义在类中且在函数体之外,类变量通常不作为实例变量使用,类变量在整个实例化的对象中是公用的,即类实例都可以访问
# 实例变量
实例化之后,每个实例单独拥有的变量
class Test(object):
    country = 'China'
    def say(self):
        print('Hello%s' % Test.country)
        print('Hello%s' % self.country)
t = Test()
print(t.country)
t.country = 'ZH'
print(t.country)
# 实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.

实例方法、类方法、静态方法

class User(object):
    def __inin__(self, name):
        self.name = name
        def intro(self):
        print(self.name)
        @staticmethod
    def welcome():
        print('Welcome')
        @classmethod
    def from_cls(cls, name):
        return cls(name)
# 实例方法
        绑定到对象的方法:没有被任何装饰器装饰的方法
    通过对象来调用该方法,自动将对象当作第一个参数传入
# 类方法
        绑定到类的方法:用classmethod装饰器装饰的方法
    通过类来调用该方法,调用时自动将类当作第一个参数传入
# 静态方法
        不与类或对象绑定,类和对象都可以调用,不会自动传值

单下划线和双下划线

# __foo__
__foo__:双下划线开头双下划线结尾的是一些 Python 的"魔术"对象,如类成员的 __init__、__del__、__add__、__getitem__ 等,以及全局的 __file__、__name__ 等,Python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用
# _foo
_foo:一种约定,用来指定变量私有,程序员用来指定私有变量的一种方式,不能用from module import * 导入,其他方面和公有一样访问
# __foo
__foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问
# foo_
foo_:在Python的官方推荐的代码样式中,还有一种单下划线结尾的样式,这在解析时并没有特别的含义,但通常用于和 Python关键词区分开来,如果我们需要一个变量叫做 class,但 class 是 Python 的关键词,就可以以单下划线结尾写作 class_

重载

函数重载主要是为了解决两个问题
1. 可变参数类型
2. 可变参数个数
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

对于情况1:函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

对于情况2:函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的
# 缺省参数即是调用该函数时:缺省参数的值若未被传入,则传入默认预设的值
# 多值参数
函数参数列表中,参数前增加一个*可以接收元组,增加两个*可以接收字典。 
* args(arguments) 存放元组参数 
* * kwargs 存放字典参数

好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

新式类和旧式类

新式类都从object继承,经典类不需要。
新式类的MRO(method resolution order 基类搜索顺序)算法采用C3算法广度优先搜索,而旧式类的MRO算法是采用深度优先搜索
新式类相同父类只执行一次构造函数,经典类重复执行多次
Python 2.x中默认都是经典类,只有显式继承了object才是新式类
Python 3.x中默认都是新式类,经典类被移除,不必显式的继承object
# python 新式类例子
class A(object):
        pass
class B(A):
    pass
class C(A):
    pass
class D(A):
        pass
class E(B, C):
    pass
class F(C, D):
    pass
class G(D):
    pass
class H(E, F):
    pass
class I(F, G):
    pass
class K(H, I):
    pass
# K --> H --> E--> B--> I --> F --> C --> G --> D -->A

魔法函数

class A(object):
    def __new__(cls,*args,**kwargs):
        print('__new__')
        return super().__new__(cls)
        def __init__(self,*args,**kwargs):
        print('__init__')
a = A()
# __init__
        __init__方法做的事情是在对象创建好之后初始化变量
# __new__
        __new__方法会返回一个创建的实例,而__init__什么都不返回
    当创建一个新实例时调用__new__,初始化一个实例时用__init__
# __call__
        当调用实例对象,自动会调用此方法
# __del__
        析构函数,当删除一个对象时,则会执行此方法,对象在内存中销毁时,自动会调用此方法

元类

# 类也是对象,元类是创建类的类
# 元类可控制类对象创建的过程
# python中类的实例化过程,会首先寻找metaclass,通过metaclass创建类
# 如果没有找到metaclass,则由type来创建类对象
class MetaClass(type): # 自定义元类继承type
    def __new__(cls, *args, **kwargs):
        super().__new__(cls, *args, **kwargs)
class User(metaclass=MetaClass):
    # _metaclass_ 指定元类
        def __init__(self):
            pass

自省

# 自省(Introspection):自省是通过一定的机制查询到对象的内部结构
dir(obj)
  dir(obj)可以获取一个对象所有的属性与方法,返回为列表(仅有属性或方法名称)
  dir()是Python提供的一个API函数,dir()函数会自动寻找一个对象的所有属性(包括从父类中继承的属性和方法)
__dict__
  __dict__字典中存储的是对象或类的部分属性,键为属性名,值为属性值
  实例对象的__dict__仅存储与该实例相关的实例属性  
  类的__dict__存储所有实例对象共享的变量和函数(类属性,方法等),类的__dict__并不包含其父类的属性和方法
dir()和__dict__的区别
  1.dir()是一个函数,返回的是list,仅有属性名和方法名;
  2.__dict__返回是一个字典,键为属性名,值为属性值;
  3.dir()用来寻找一个对象的所有属性和方法(包括从父类中继承的属性和方法),包括__dict__中的属性和方法,__dict__是dir()的子集;

  注:并不是所有对象都拥有__dict__属性。许多内建类型就没有__dict__属性,如list,此时就需要用dir()来列出对象的所有属性和方法
---------------------------------------------------------------------------------------------
hasattr(object, name)
检查对象是否具体 name 属性。返回 bool.
getattr(object, name, default)
获取对象的name属性。
setattr(object, name, default)
给对象设置name属性
delattr(object, name)
给对象删除name属性
dir([object])
获取对象大部分的属性
isinstance(name, object)
检查name是不是object对象
type(object)
查看对象的类型
callable(object)
判断对象是否是可调用对象

内存管理和垃圾回收

内存管理

Python有内存池机制,用于对内存的申请和释放管理,预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存,这样做最显著的优势就是能够减少内存碎片,提升效率

垃圾回收

垃圾回收机制,Python采用GC作为自动内存管理机制,GC要做的有2件事,一是找到内存中无用的垃圾对象资源,二是清除找到的这些垃圾对象,释放内存给其他对象使用。
Python采用了"引用计数"为主,"标志清除"和"分代回收"为辅助策略
# 引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数,当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放
优点:
        简单
    实时性
缺点:
    需要额外的空间维护引用计数
    无法解决循环引用问题

# 循环引用
A和B相互引用而且没有外部引用A与B中的任何一个,也就是对象之间互相应用,导致引用链形成一个环

# 标记清除
标记清除主要是解决循环引用问题
标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法,它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边,从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象,根对象就是全局变量、调用栈、寄存器
优点:
        解决类循环引用的问题
缺点:
    清除非活动的对象前它必须顺序扫描整个堆内存

# 分代回收
分代回收是一种以空间换时间的操作方式
分代回收思想将对象分为三代(generation 0,1,2) 0代表幼年对象,1代表青年对象,2代表老年对象,随着代数越高脚检测频率越低,每一代都有最大容纳对象的个数,一旦超过容纳个数就会出发垃圾回收机制,老年对象触发将清理所有三代,青年对象触发会清理青年和幼年,幼年对象触发后只会清理自己

内存的结构

# 程序的内存分配
栈(stack):有编译器自动分配和释放,存放函数的参数、局部变量、临时变量、函数返回地址等
堆(heap):一般有程序员分配和释放,如果没有手动释放,在程序结束时可能由操作系统自动释放,稍有不慎会引起内存泄漏
# Python、Go有回收机制的语言,C/CPP必须手动释放开辟的堆内存)

# 申请后系统的响应
栈:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出
堆:在记录空闲内存地址的链表中寻找一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统会在这块内存空间的首地址处记录本次分配空间的大小,这样代码中的delete才能正确释放本内存空间,系统会将多余的那部分重新空闲链表中

# 申请大小限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域,这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在linux下默认栈大小为8Mb,如果申请的空间超过栈的剩余空间时,将提示overflow,因此,能从栈获得的空间较小(速度快,无法控制)
堆:堆是向高地址扩展的数据结构,是不连续的内存区域,这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址,
  堆的大小受限于计算机系统中有效的虚拟内存,由此可见,堆获得的空间比较灵活,也比较大(速度慢,操作方便)

# 分配效率
栈:由系统自动分配,速度较快,但程序员是无法控制的
堆:一般速度比较慢,而且容易产生内存碎片,不过用起来最方便

# 存储内容
栈:在栈中,第一个进栈的是主函数下一条指令的地址,然后是函数的各个参数,在大多数编译器中,参数是由右往左入栈,然后是函数中的局部变量,注意,静态变量不入栈,出栈则刚好顺序相反
堆:一般在堆的头部用一个字节存放堆的大小,具体内容由程序员安排
  
# 内存分成5个区
栈:内存由编译器在需要时自动分配和释放,通常用来存储局部变量和函数参数,(为运行函数而分配的局部变量、函数参数、返回地址等存放在栈区),
  栈运算分配内置于处理器的指令集中,效率很高,但是分配的内存容量有限

堆:内存使用new进行分配,使用delete或delete[]释放,如果未能对内存进行正确的释放,会造成内存泄漏,但在程序结束时,会由操作系统自动回收

自由存储区:使用malloc进行分配,使用free进行回收,和堆类似

全局/静态存储区:全局变量和静态变量被分配到同一块内存中,C语言中区分初始化和未初始化的,C++中不再区分了(全局变量、静态数据、常量存放在全局数据区)

常量存储区:存储常量,不允许被修改
--
# 内存分为3个区
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,它主要存放静态数据、全局数据和常量

栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限

堆区:亦称动态内存分配,程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或 delete释放内存,动态内存的生存期可以由我们决定,
    如果我们不释放内存,程序将在最后才释放掉动态内存,但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象

# 代码示例
void fn()
{
int *p = new int[5]
}
new,分配了一块堆内存,指针p分配的示一块栈内存,所以这句话的意思,就是:在栈内存中存放了一个指向一块堆内存的指针p

栈顶和栈底

ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部

设计模式

单例模式

# __new__
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            instance = super(Singleton, cls).__new__(cls, *args, **kwargs)  
            # 因为__new__是一个静态方法
            setattr(cls, '_instance', instance)
        return cls._instance
a = Singleton()
b = Singleton()
c = Singleton()
print(id(a)) # 4325283320
print(id(b)) # 4325283320
print(id(c)) # 4325283320
# 共享属性
创建实例时把所有实例的`__dict__`指向同一个字典,这样它们具有相同的属性和方法.
class Singleton(object):
    _local = {'name':'Allen'}
        def __new__(cls, *args, **kwargs):
        obj = super(Singleton, cls).__new__(cls, *args, **kwargs)
        obj.__dict__ = cls._local
        return obj
s1 = S()
s2 = S()
print(s1.name)
s1.name = 'Kevin'
print(s2.name)
# 装饰器(类装饰器)
def decorator(cls):
    instances = {}
        def wrapper(*args,**kwargs):
        if cls not in instances:
            obj = cls(*args,**kwargs)
            instances[cls] = obj
                return instances[cls]
        return wrapper
@decorator
class MyClass(objcet):
    a = 1
m1 = MyClass()
m2 = MyClass()
m3 = MyClass()
print(id(m1))
print(id(m2))
print(id(m3))
# import
# 作为python的模块是天然的单例模式
a.py
class MyClass(object):
    a = 1
obj = MyClass()
b.py
import a
o1 = a.obj
o2 = a.obj
o3 = a.obj

网络编程

ISO七层协议

# 物理层:比特
通过光缆、电缆、双绞线、无线电波发送电信号,高电压对应1,低电压对应数字0
# 数据链路层:帧
工作于以太网协议,将一组电信号构成一个数据包,叫做帧,每一组数据帧分成报头head和数据data来个部分(head固定18字节,data最短46字节,最长1500字节)
以太网协议的接入internet的设备都必须具备网卡,发送端和接受端的地址便是mac地址并且该地址为全球唯一的,通过广播的的方式来进行通信,向在同一局域网内的每一个计算机发送消息来确认接受者
# 网络层:报文
引入IP协议,该协议定义的地址成为IP地址,广泛采用ipv4,规定网络定制有32位2进制表示:0.0.0.0-255.255.255.0
ip地址分为两个部分:表示子网,表示主机
子网掩码:通过子网掩码,我们就能判断,任意两个ip地址是否处于同一个子网内,IP地址和子网掩掩码通过and运算得出结果,ip数据包分为head和data部分,无需为ip数据包定义单独的栏位,直接放入以太网包的data部分,head(20-60字节),data(最长65515字节),而以太网数据包的data(数据)部分,最长只有15000字节,因此,如果ip数据包超过了1500字节,他就需要分隔成几个以太网数据包,分开发送,

# 传输层:TPDU--传输协议数据单元
# 会话层:SPDU--会话协议数据单元
# 表示层:SPDU--表示协议数据单元
# 应用层:APDU--应用协议数据单元

wsgi

WSGI是 Web Server Gateway Interface 的缩写。
它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。
它是一种协议,一种规范,其是在 PEP 333提出的,并在 PEP 3333 进行补充(主要是为了支持 Python3.x)。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件
WSGI 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取
WSGI 对于 application 对象有如下三点要求
必须是一个可调用的对象
接收两个必选参数environ、start_response
返回值必须是可迭代对象,用来表示http body

一次http请求的过程

一个HTTP请求的过程分为2个阶段
#第一个阶段是从客户端到WSGI server
#第二个阶段是从WSGI server到WSGI application

socket

并发编程

GIL全局解释器锁

# 一个python文件执行的过程
    1、首先执行py文件,向操作系统发起请求,操作系统划出了一块内存空间(运行一个python程序只产生一个进程)
    2、先把python解释器数据送到内存中,然后在把py文件送到内存中,(他们都在一个进程中传送)
    3、最后把py文件当作普通字符串传送到python解释器中,然后解释器对其进解释(py程序脱离了解释器就是普通的字符串)
# GIL的作用
GIL:global interpreter lock(全局解释器锁)
Python中一个线程对应于c语言中的一个线程
GIL使得同一个时刻只有一个线程运行在一个CPU上执行字节码,在同一进程下无法将多个线程映射到多个cpu上执行
GIL会根据执行的字节码行数以及时间片释放GIL,GIL在遇到IO操作时会主动释放
GIL同步线程执行字节码
# GIL的优缺点:
优点:
    保证CPython解释器中内存管理的线程安全
缺点:
    同一进程下的多线程无法实现并行
# 全局解释器锁(Global Interpreter Lock)
        Python为了保证线程安全而采取的独立线程运行的限制,也就是说一个核只能在同一时间运行一个线程,对于io密集型任务,python的多线程起到作用,io不占用cpu资源,但对于cpu密集型任务,python的多线程发挥不出多核优势
# 注意:
        GIL锁不能保证数据安全,GIL只是保证解释器数据安全,如果要保证自己的数据安全就需要自己加一把锁
# 解决方法
        1.多进程
        2.协程(协程也只是单CPU,但是能减小切换代价提升性能)

进程和线程

# 进程:
        定义:系统资源分配与调度的基本单位
        优点:独占操作系统与计算机资源,尤其是独占内存地址空间,所以多进程场景中的单个进程异常不会让整个应用程序崩溃
    缺点:独占资源多,所以进程的创建、销毁、通信及切换的成本都比较高

# 守护进程:
        对于主进程来说:运行完毕指的是主进程代码运行完毕
    主进程在其代码执行完毕就算结束了(此时回收守护进程),但是主进程会等待所有非守护进程执行完毕并挥手资源才结束(防止产生僵尸进程);
# 线程
    定义:处理器调度的基本单位
    优点:轻量基本不独占资源,所以线程的创建、销毁、通信及切换的成本都更低,更有利于在多线程场景中提高程序的并发性能
    缺点:没有隔离出私有内存,所以单个线程的崩溃可能会导致整个应用程序退出

# 守护线程:
        对于主线程来说:主线程所在的进程中所有非守护线程执行完毕,主线程才算执行完毕
        主线程在所有非守护线程执行完毕才结束,对主线程来说,主线程的结束意味着主进程的结束,线程是执行单位
# 线程安全

进程间通信

from multiprocessing import Process
from multiprocessing import Manager
from multiprocessing import Queue
from multiprocessing import Pool
from multiprocessing import Pipe
import time


def producer(q):
    q.put("Hello")


def consumer(q):
    result = q.get()
    print(result)


def p(pi):
    time.sleep(2)
    pi.send("Hello")


def c(pi):
    print(pi.recv())


def add(s_dict, key, value, lock):
    lock.acquire()
    s_dict[key] += value
    lock.release()


if __name__ == '__main__':
    # 共享变量在多进程不适用,在多线程适用
    # q = Queue(10)
    # p1 = Process(target=producer, args=(q,))
    # p2 = Process(target=consumer, args=(q,))
    # p1.start()
    # p2.start()
    # p1.join()
    # p2.join()
    # 进程池不能使用Queue,Pool使用Manager中Queue来传递参数
    # o = Manager().Queue(10)
    # pool = Pool(2)
    # pool.apply_async(func=producer, args=(o,))
    # pool.apply_async(func=consumer, args=(o,))
    # pool.close()
    # pool.join()

    # pipe的性能要不queue要高
    # 通过Pipe来进行进程间通信
    # Pipe只能适用于两个进程间的通信
    # recv, send = pipe = Pipe()  # 返回两个connection,一个发送数据,一个接收数据
    # px = Process(target=p, args=(send,))
    # cx = Process(target=c, args=(recv,))
    # px.start()
    # cx.start()
    # px.join()
    # cx.join()

    # Manager()共享内存,使用共享内存在修改数据要保证同步性
    shareDict = Manager().dict()
    mutex = Manager().Lock()
    shareDict["key"] = 0
    all_task = [Process(target=add, args=(shareDict, "key", 10000, mutex)) for _ in range(10)]
    for t in all_task:
        t.start()
    for x in all_task:
        x.join()

    print(shareDict)

并发和并行

1.单线程下实现并发
    并发指的是多个任务看起来好像是同时运行的

    并发实现的本质:切换+保存状态

    并发、并行、串行:
    并发:看起来是同时运行,切换+保存状态
    并行:真正意义上的同时运行,只有在多cpu的情况下才能实现并行,4个cpu能够并行4个任务
    串行:一个任务完完整整的执行完毕才能运行下一个任务

    协程:
        是单线程下的并发,又称为线程,纤程,英文名(Coroutine)。操作系统不会认协程


    #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
    #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

    优点:
    #1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    #2. 单线程内就可以实现并发的效果,最大限度地利用cpu

    缺点:
    #1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

    特点:
    必须在只有一个单线程里实现并发
    修改共享数据不需加锁
    用户程序里自己保存多个控制流的上下文栈
    附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制)

协程

# 协程(Coroutine):可以被暂停并切换到其他协程运行的函数(协程不同于进程和线程,有我们程序(员)自己调度)
# 协程可以让我们像写同步代码去写异步代码
# 在python有一个特例是可以被暂停的那就是:生成器
# 无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
# 无需原子操作锁定及同步的开销
# 方便切换控制流,简化编程模型
# 高并发+高扩展性+低成本,适合做并发处理
# 进行 阻塞 操作 会阻塞掉整个程序,如是要避免出现阻塞操作发生
def gen_func():
    yield 1
    yield 2
    yield 3

gen = gen_func() # 创建一个生成器对象
v = gen.__next__()
print(v)

# 1.将生成器包装成协程
from tornado.gen import coroutine

@coroutine
def cor1():
    yield 1
    yield 2
    yield 3

# 2.(在python3.5中提供原生协程)就是async/awiat
async def cor2():
    # yield和yield from 不可以写在async定义的协程中
    await cor1() # 如果想要获取其他协程的结果就要使用await

Queue

# 使用Queue作为线程间通信的媒介,可以不用去考虑资源竞态的问题,他是线程安全的
# Queue内部使用的是deque双端队列(deque是线程安全的)
q.get() # 向队列中取值,默认阻塞,可以设置block=false异步执行
q.put() # 向队列中放置
q.get_nowait() # 内部还是调用get方法,执行传递了一个block=false参数
q.put_nowait()
q.qsize() # 队列的长度
q.empty() # 队列是否为空
q.full() # 队列是否为满
q.join() # 阻塞队列,直到向队列发送一个task_done
q.task_done() # 向队列发出一个信号

线程同步

Lock:互斥锁
RLock:可重入锁
Condition:条件锁
from threading import Condition
from threading import Thread
from threading import Lock


# condition条件变量,用于复杂的线程间同步
# 通过Condition完成协同读诗
class Ai(Thread):
    def __init__(self, cond: Condition):
        super(Ai, self).__init__(name="小爱同学")
        self.cond = cond

    def run(self):
        with self.cond:
            self.cond.wait()

            print(f"{self.name}:在")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:好啊")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:君住长江尾")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:共饮长江水")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:此恨何时已")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:定不负相思意")


class TMall(Thread):
    def __init__(self, cond: Condition):
        super(TMall, self).__init__(name="天猫精灵")
        self.cond = cond

    def run(self):
        with self.cond:
            print(f"{self.name}:小爱同学")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:我们来古诗吧")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:我住长江头")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:日日思君不见君")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:此水几时休")
            self.cond.notify()

            self.cond.wait()

            print(f"{self.name}:只愿君心似我心")
            self.cond.notify()


if __name__ == '__main__':
    lock = Condition()
    t = TMall(lock)
    a = Ai(lock)
    a.start()
    t.start()
    # 启动顺序很重要
    # 在调用with cond(或者cond.acquire)之后才能调用wait或者notify方法 error:cannot notify on un-acquired lock
    # condition有两层锁,cond.acquire和waiter,一把底层锁会在线程调用了wait方法的时候释放,上面的锁会在每次调用wait的时候分配一把并放入cond的等待队列中(deque)等待notify方法的唤醒
    t.join()
    a.join()

Semaphores:信号量
# Semaphore是用于控制进入数量的锁
# 文件,读,写,写一般只是用于一个线程写,读可以允许有多个
import time
from threading import Semaphore
from threading import Thread


def task(index, sem: Semaphore):
    time.sleep(2)
    print(f"Get success {index}")
    sem.release()


def handler(s: Semaphore):
    for i in range(20):
        s.acquire()
        Thread(target=task, args=(i, s)).start()


if __name__ == '__main__':
    semaphore = Semaphore(3)
    handler(semaphore)

线程池和进程池

同步、异步、阻塞、非阻塞

1、阻塞和非阻塞指的是程序的两种运行状态(运行态、就绪态、阻塞态)
阻塞(就绪态和阻塞态):遇到IO操作就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源
非阻塞(运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地,执行其他操作,力求尽可能多的占有CPU
2、同步与异步指的是提交任务的两种方式
同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才能继续执行下一行代码
异步调用:提交任务后,不在原地等待,直接执行一下一行代码,结果?

同步(调用/执行/任务/提交),发起任务后必须等待任务结束,拿到一个结果才能继续执行
异步(调用/执行/任务/提交),发起任务后不需要关心任务的执行结果,可以继续往下运行
异步效率高于同步
但是并不是说所有任务都可以异步,判断一个任务是否可以异步的条件是,任务发起是否立即需要执行结果

但使用异步方式发起任务是 任务中可能包含io操作 异步不可能阻塞
同步提交任务 也会卡主程序 但是不等同阻塞,因为任务中可能在做一对计算任务,cpu没走

同步和异步关注的是获取结果的方式,同步是获取到结果之后才进行下一步操作,阻塞非阻塞关注的是调用接口时当前线程的状态,同步可以调用阻塞也可非阻塞,异步是调用非阻塞接口

# 同步是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
# 异步是指代码调用IO操作时,不必等IO操作完成就返回的调用方式
# 阻塞是指调用函数时候当前线程被挂起
# 阻塞是指调用函数时候当前线程不会被挂起,而是立即返回

select、poll、epoll

select、poll、epoll都是io多路复用的机制,i/o多路复用就是同一种机制,一个进程可以监视多个描述符(可以理解为多个socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,但是select、poll、epoll本质上都是同步i/o,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步i/o则无需自己负责进行读写,异步i/o的实现会负责把数据从内核拷贝到用户空间
# select
select函数事件的文件描述符分为3类,分别是writefds(可写的文件描述符)、readfds(可读的文件描述符)、exceptfds(异常文件描述符),调用后select函数会阻塞,直到有描述符就绪(可数据可读、可写、有异常),或者超时(timeout指定等待时间,如果立即返回设为None即可),函数返回,当select函数返回后,可以通过遍历fdset,来找到就绪的描述符.
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低(select每一个调用文件描述符都会去遍历一次[性能比较低])
# poll
不同与select使用三个位图表示三个fdset的方式,poll使用一个pollfd的指针实现
pollfd结构包含了监视的event和发生的event,不在使用select'参数-值'传递的方式,同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降),和select函数一样,poll返回后,需要轮训pollfd来获取就绪的描述符
从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket,事实上,同时连接的大量客户端在同一时刻只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降
# epoll
epoll是在linux2.6内核中提出的,是之前的select和poll的增强版本,相对于selct和poll来说,epoll更加灵活,没有描述符限制,epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次






FD_ZERO(int fd, fd_set* fds) 
FD_SET(int fd, fd_set* fds) 
FD_ISSET(int fd, fd_set* fds) 
FD_CLR(int fd, fd_set* fds) 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 
       struct t


评论


亲,登录后才可以留言!