目录
Python语言特性
类变量和实例变量
类变量
是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。
例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。
实例变量
实例化之后,每个实例单独拥有的变量。
1 | class Test(object): |
补充
1 | class Student(object): |
metaclass
- object是所有类的超类,而且type也是继承自object;所有对象创建自type,而且object也是type的实例。
- object和type是python中的两个源对象,事实上,它们是互相依赖对方来定义,所以它们不能分开而论。
- 通过这两个源对象可以繁育出一堆对象:list、tuple、dict等。元类就是这些类的类,这些类是元类的实例。
- metaclass是type的子类,通过替换type的__call__运算符重载机制,“超越变形”正常的类。
Python自省
自省就是面向对象的语言所写程序在运行时,所能知道对象的类型。运行时能获得对象的类型。比如type()、dir()、getattr()、hasattr()、isinstance()
__new__和__init__区别
- __new__是一个静态方法,而__init__是一个实例方法。
- __new__方法会返回一个创建的实例,而__init__什么都不会返回。
- 只有在__new__返回一个cls的实例后,__init__才会被调用。
- 当创建一个新实例时调用__new__,初始化一个实例时调用__init__。
__metaclass__是创建类时起作用,所以我们可以分别使用__metaclass__、__new__和__init__来分别在类创建,实例创建和实例初始化的时候做一些小手脚.
单例模式
使用__new__方法
1
2
3
4
5
6
7
8
9class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance')
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance
class Test(Singleton):
pass装饰器
1
2
3
4
5
6
7
8
9
10
11def singleton(cls):
instances = {}
def get_instance(*args, **kw)
if cls not in instances:
instances[cls] = cls(*arg, **kw)
return instances[cls]
return get_instance
class Test:
passimport
1
2
3
4
5
6
7
8
9# singleton.py
class My_Singleton(object):
pass
my_singleton = My_Singleton()
# use
from singleton import my_singleton
GIL线程全局锁
线程全局锁(Global Interpreter Lock),Python为了保证线程安全而采取的独立线程运行的限制,一个核只能在同一时间运行一个线程。Python中的多线程是假的多线程,Python解释器虽然可以开启多个线程,但同一时间只有一个线程能在解释器中执行,而做到这一点正是由于GIL锁的存在,它的存在使得CPU资源同一时间只会给一个线程使用。
解决办法就是多进程和协程(协程也只是单CPU,但是能减少切换代价而提升性能)。
线程锁
线程锁是由于在进程进行数据操作时要保证数据操作的安全性(同一进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)。
进程和线程
进程是资源(CPU、内存)分配的基本单位,它是执行程序是的一个实例。程序运行时系统会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程是程序执行时的最小单位,它是进程的执行流,是CPU调度和分派的基本单位,一个进程可以由多个线程组成,线程间共享进程的所有资源,每个进程都有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
- 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
使用场景
IO密集型的程序,可以使用多线程提高程序整体效率(socket server 网络并发)。程序是CPU密集型的,使用Python的多线程是无法提升效率的,使用多进程来实现。
进程间通信方式
优点:
- 协程的开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。
- 单线程内就能实现并发的效率,最大限度的利用CPU。
缺点
- 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程开启多个线程,每个线程开启协程。
- 协程是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程。
特点
- 必须在只有一个单线程里实现并发。
- 修改共享数据不需加锁。
- 用户程序里自己保存多个控制流的上下文栈。
- 一个协程遇到IO操作自动切换到其它协程。
应用场景
yield
- 提高并发。
并行和并发
并行(parallel)指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
Python垃圾回收机制
Python GC主要使用引用计数(Reference Counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(Mark and Sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(Generation Collection)以空间换时间的方法提高垃圾回收效率。
- 引用计数
PyObject是每个对象必须有的内容,其中ob_refcnt就是作为引用计数。当一个对象有新的引用是,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会被减少。引用计数为0时,该对象生命就结束了。
优点:
- 简单
- 实时性
缺点:
- 维护引用计数消耗资源
- 循环引用
标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没有标记的对象释放。分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,垃圾手机频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活越长。
举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
Python的魔法方法
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用,它们通常是两个下划线包围起来命名的(比如__init__、__len__)
浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;深拷贝是拷贝多层,每一级别的数据都会拷贝出来;
可变类型和不可变类型
可变类型,是指变量所指向的内存地址处的值是可以被改变的。
可变类型:Set(集合)、List(列表)、Dict(字典)
不可变类型,是指变量所指向的内存地址处的值是不可被改变的。
不可变类型:Number(数字)、String(字符串)、Tuple(元组)
闭包
闭包的特点就是内部函数引用了外部函数中的变量。
判断是否是闭包函数
函数名.__closure__在函数式闭包函数时,返回一个cell对象的元组;不是闭包时,返回None。
装饰器
装饰器(Decorator),能在代码运行期间动态增加功能。
示例
1 |
|
带参数的装饰器
1 | import time |
偏函数
functools.partial的作用就是,把设置某个函数的默认值,返回一个新的函数,调用这个新函数会更简单。
1 | import functools |
迭代
Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代。
判断可迭代
通过collections模块的Iterable类型判断。
1 | from collections import Iterable |
生成器
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
数据库
mysql
事务
事务主要用于处理操作量大、复杂度高的数据。
事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务。
ACID
事务四大特征(ACID)
- 原子性(Atomicity):指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
- 一致性(Consistency):事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。
- 隔离性(Isolation):事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化)。
- 持久性(Durability):事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的隔离性
锁机制
隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。
锁可以分为表锁、行锁以及其他位于二者之间的锁。
表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
隔离级别
read uncommitted,读未提交。
脏读:可能。
不可重复读:可能。
幻读:可能。read committed,读已提交。
脏读:不可能。
不可重复读:可能。
幻读:可能。repeatable read,可重重复读。
脏读:不可能。
不可重复读:不可能。
幻读:可能。serializable,可串行化。
脏读:不可能。
不可重复读:不可能。
幻读:不可能。
多并发
并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
乐观锁和悲观锁
乐观锁
乐观锁顾名思义就是在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改),因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS(compare and swap)算法实现。
版本号机制
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
核心SQL
1 | update table set name = 'Aron', version = version + 1 where id = #{id} and version = #{version}; |
CAS算法
乐观锁的另一种技术技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加(悲观)锁。一旦加锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。
悲观锁在MySQL、Java有广泛的使用:MySQL的读锁、写锁、行锁等,Java的synchronized关键字。
总结
读的多,冲突几率小,乐观锁。
写的多,冲突几率大,悲观锁。
锁
锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。
一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。
threading模块中提供了5种最常见的锁,下面是按照功能进行划分:
同步锁:lock(一次只能放行一个)
递归锁:rlock(一次只能放行一个)
条件锁:condition(一次可以放行任意个)
事件锁:event(一次全部放行)
信号量锁:semaphore(一次可以放行特定个)
Lock() 同步锁
- 同步锁
- 互斥锁
- 互斥指的是某一资源同一时刻仅能有一个访问者对其进行访问,具有唯一性和排他性,但是互斥无法限制访问者对资源的访问顺序,即访问是无序的。
- 同步是指在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问。
- 同步其实已经实现了互斥,是互斥的一种更为复杂的实现,因为它在互斥的基础上实现了有序访问的特点。
同步锁一次只能放行一个线程,一个被加锁的线程在运行时不会将执行权交出去,只有当该线程被解锁时才会将执行权通过系统调度交由其他线程。
对于同步锁来说,一次acquire()必须对应一次release(),不能出现连续重复使用多次acquire()后再重复使用多次release()的操作,这样会引起死锁造成程序的阻塞,完全不动了。
RLock() 递归锁
递归锁是同步锁的一个升级版本,在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。
RLock递归锁,递归锁的实现非常简单,它的内部会维护着一个计数器,当计数器不为0的时候该线程不能被I/O操作和时间轮询机制切换。
Condition() 条件锁
条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()与notify()来控制线程执行的个数。
Condition条件锁的内部其实是有两把锁的,一把底层锁(同步锁)一把高级锁(递归锁)。
低层锁的解锁方式有两种,使用wait()方法会暂时解开底层锁同时加上一把高级锁,只有当接收到别的线程里的notfiy()后才会解开高级锁和重新上锁低层锁,也就是说条件锁底层是根据同步锁和递归锁的不断切换来进行实现的。
Event() 事件锁
事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。
Semaphore() 信号量锁
信号量锁也是根据条件锁来做的,它与条件锁和事件锁的区别如下:
- 条件锁:一次可以放行任意个处于“等待”状态的线程
- 事件锁:一次可以放行全部的处于“等待”状态的线程
- 信号量锁:通过规定,成批的放行特定个处于“上锁”状态的线程