分布式锁

在并发编程中,经常会遇到多个线程访问同一个共享资源而这时候/我们就需要保证数据的“致性,那么就要用到锁的概念,给资源加上锁,拿到锁所有权的人才能够进行操作共享资源,没有拿到资源的线程需要等待,等其他线程使用完,释放锁。 在项目中,遇到多个用户抢购商品时、商品的数量就是共享资源。因此,在操作商品库存数据时,也需要使用锁保证商品库存的一致。 线程锁 如果在单服务器架构中,就可以使用线程锁保证数据的一致性。但是,此种方案不适合多服务器架构。 假设服务器1上使用线程锁保证同一时刻只有1个用户操作数据,却无法保证其他服务器上同时不能有用户操作,仍然会产生资源竞争。 因此,在多服务器架构中,不能使用线程锁的机制。 分布式锁 在上面的情况中,锁是分布在每个服务器上的。因此,不能保证所有服务器间数据的一致性。 想一想,可以将锁独立于各个服务器之外吗? 答案是肯定的,这就是分布式锁。 当确定了不同节点服务器之间需要分布式锁,那么我们就需要确定分布式锁到底有哪些功能? 互斥性:和本地锁一样,互斥性是最基本的,但是分布式锁需要保证在不同节点不同线程的互斥。 可重入性:同一个节点同一个线程如果获取了锁之后,那么也可以再次获取这个锁。 锁超时:和本地锁一样,支持锁超时,防止死锁。 高效、高可用:加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效。 常见的分布式锁实现方式:MySQL、Redis等 MySQL锁 通过MySQL实现分布式锁,有两种方式:悲观锁和乐观锁。 悲观锁 概念 悲观锁是基于一种悲观的态度来防止一切数据冲突。它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据读写,在它释放锁之前,其他任何人不能对数据操作,直到前一个人把锁释放后,下一个人才对数据进行加锁,然后进行数据操作。 一般的关系型数据库管理系统中,锁的机制都是基于悲观锁的机制实现的。 特点:可以完全保证数据的独占性和正确性,因为每次请求都会对数据进行加锁,然后进行数据操作,最后再解锁,而加锁解锁的过程会造成服务器性能消耗。因此,在高并发的情况下,用MySQL实现分布式锁,毫无疑问是不行的!!! 只是了解MySQL实现分布式锁的思想即可。 自定义锁表实现 首先,需要创建一个有关资源的锁记录表: CREATE TABLE resourceLock( id INT UNSIGNED PRIMARY KEY AUTO_NCREMENT, resource_name VARCHAR(128) NOT NULL UNIQUE DEFAULT '' COMMENT '资源名字', node_info VARCHAR(128) DEFAULT NUUL COMMENT '机器信息', count INT UNSIGNED NOT NULL DEFAULT O COMMENT '锁的次数,实现重入性', desc_info VARCHAR(128) DEFAULT NULL COMMENT '额外的信息', update_time TIMESTAMP DEFAULT NULL COMMENT'更新时间', create_time TIMESTAMP DEFAULT NULL COMMENT '创建时间' )ENGINE=INNODB DEFAULT CHARSET=utf8; 定义锁的工具类 import time from django.db import connection, transaction class MysqlLock(object): def lock(self, ...
2023年10月04日

python常见函数的时间复杂度

python常见函数的时间复杂度 很多函数都有自己的很多方法,其中有些方法的功能类似,但是其复杂度有时却大不相同 本节我们将引入一个新模块 timeit ,其功能是来测试一小段 Python 代码的执行速度。 1、timeit class timeit.Timer(stmt='pass', setup='pass',timer=<timer function>) Timer 是测量小段代码执行速度的类 stmt 是要测试的代码语句 (statment), 字符串类型, setup 是运行代码时需要的设置 , 字符串类型, 就是从 __main__ 引入需要的是的方法名 timer 参数是一个定时器函数, 与平台有关, 不用去管 timeit.Timer.timeit(numer=1000000),numer-> 测算次数, 返回平均耗时, 一个 float 类型的秒数 部分测试示例 测试结果 2、list 的内置函数时间复杂度 方法 复杂度 简介 index[x] O(1) 索引 index assignment O(1) 索引赋值 append O(1) 尾部追加 pop() O(1) 尾部弹出 pop(i) O(n) 指定位置弹出 n 列表长度, 最坏时间复杂度 insert(i, item) O(n) ...
2023年10月04日

Python垃圾回收机制

python垃圾回收机制 简单分析一下python中的垃圾回收机制,主要从三方面阐述:引用计数、标记清除、分代回收。 引用计数 引用计数是什么? 引用计数是编程语言中的一种内存管理技术;将资源(可以是对象、内存或磁盘空间等)的引用次数保存起来 引用计数为零时,资源将被释放。 如何使引用计数减少? del语句会删除对象的一个引用,这会导致该引用指向的对象的引用计数减1 ==注意==:任何调试或追踪程序会给对象增加一个额外引用,这会推迟该对象的回收时间 引用计数会导致什么问题? 由于两个或以上对象互相引用时,彼此引用计数不为0, 造成循环引用而无法回收。 In [1]: a = [1] In [2]: b = [2] In [3]: a.append(b) In [4]: b.append(a) In [5]: a Out[5]: [1, [2, [...]]] In [6]: b Out[6]: [2, [1, [...]]] 如何查看引用计数? In [7]: import sys In [8]: sys.getrefcount(a) Out[8]: 15 In [9]: sys.getrefcount(b) Out[9]: 15 标记清除 Python引入了其它的垃圾回收机制来弥补引用计数的缺陷:"标记-清除" 『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢? 对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。 在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。 垃圾标记时(也就是检测循环引用时),先将集合中对象的引用计数复制一份副本(以免在操作过程中破坏真实的引用计数值) 这个计数副本的唯一作用是寻找root ...
2023年10月04日

python中的callable概念

python中的callable概念 是类还是函数 几乎所有的教程都告诉你 int() 是 python 的内置函数,然而当你看到 int 的定义,发现它竟然是类 class int(object): pass 不只是 int(), 还有 float(), bool(), str(),很多你以为是函数但实际上却是类,但是呢,当你使用它们时完全察觉不出它们有什么不同,本文不是要和你讨论类和函数,而是要讨论学习 callable 什么是 callable 一个可 callable 的对象是指可以被调用执行的对象,并且可以传入参数, 用另一个简单的描述方式,只要可以在一个对象的后面使用小括号来执行代码,那么这个对象就是 callable 对象。下面列举 callable 对象的种类: 函数 类 类里的函数 实现了__call__方法的实例对象 函数 def test(): print('ok') print(callable(test)) # True test() # ok 函数是 python 里的一等公民,函数是可调用对象,使用 callable 函数可以证明这一点 类 class Stu(object): def __init__(self, name): self.name = name print(callable(Stu)) # True print(Stu('小明').name) # 小明 在其他编程语言里,类与函数可以说是两个完全不搭的东西,但在 python 里,都是可调用对象 类里的方法 类里的方法也是用 def 定义的,本质上也是函数 from inspect import isfunction, ismethod class Stu(object): def __init__(self, ...
2023年10月04日

CPython的内存概念栈、堆和引用

CPython的内存概念栈、堆和引用 Python的实现版本有很多,例如Jython底层就是JVM, IronPython的底层是.Net,它们的内存管理千差万别取决于底层的运行时系统。在CPython实现中,堆和栈有各自的职责。 C语言 堆(heap)和栈(stack)原本是两种不同的数据结构,在C语言内存表述中,代表着用这两种数据结构管理的两种内存块。 堆由整个系统共享,各个进程拥有同一个堆。 栈由每个进程自行管理,也就是每个进程的栈是独立的,互不相关。 具体区别如下: 栈上的内存由系统自动管理分配,用于存储局部变量。 堆中的内存由编程人员主动申请,在C语言中申请内存的函数为malloc, 使用后需要编程人员自行调用free函数释放。 从分配释放及访问速度上,栈内存的存取,申请释放速度要高于堆内存。 栈内存相对于堆内存要小的多,所以在编程的时候,一般不建议使用占空间过大的局部变量。 堆中所有数据均由编程人员申请使用。 栈中除了存放函数中可见的局部变量外,还有各种系统环境数据。 python语言 堆: 主要负责存储CPython运行时的所有对象实体(也就是Python对象的所有属性数据),例如:smt='Hello Word'这个字符串对象PyASCIIObject,n=23这是一个整数PyLongObject,它们都是Python对象,赋值符号=右边的数据值,CPython会将其存储到堆内存中。 栈: 在CPython的语义中,又叫数据栈或值栈,它主要负责保存对堆中Python对象的引用,例如:当CPython在执行smt='Hello Word'这个简单的Python语句,CPython会将'Hello Word'这个字符串实体所处的内存地址压入栈(对于Python语义级别理解,就是对"Hello Word"的引用),而不是将'Hello Word'这个字符串值压入栈。 smt='Hello Word'这些简单的Python赋值语句,你不能单纯地认为将'Hello World'赋值给变量smt,这是大错特错的。 赋值符号右边的是Python对象实体(从C实现的理解,就是构成该PyObject子类对象的属性值,这些值有具体的字面量值表示),并且CPython会为该Python对象在堆中分配内存并且存储它。 而变量smt仅持有该Python对象实体的引用(从C实现的理解,就是该PyObject对象的内存地址),而不是实际的Python对象。 s1变量持有Python对象'Hello world'的引用,对于CPython虚拟机来说,就是在执行s1='Hello Word',将它的内存地址0x71334推入数据栈,那么当CPython碰到同样的语句s2='Hello Word',明显是指向同一个Python对象,那么变量s2和s1一样,它自然持有是'Hello Word'的引用,即s2实质上拥有的'Hello Word'的堆中的地址。 对于其他简易的数据类型,也是如出一辙的。那么现在给Python引用我们可以下一个定义。 Python对象的引用:就是Python变量持有Python对象在堆内存中的内存地址。 我们可以通过python的内置id函数或者关键字is 来判断两个变量是否对同一个对象的引用。 In [1]: s1 = 'hello world' In [2]: s2 = 'hello world' In [3]: id(s1) Out[3]: 4472932144 In [4]: id(s2) Out[4]: 4472034928 在Python中有两种类型的对象:可变对象和不可变对象。 可变对象: 比较典型的就是list,一个列表作为一个对象存储在堆内存中,如果我们要更改该列表的某些元素,它将仍然是内存中的同一个列表对象。 In [5]: alist = [1, 2, ...
2023年10月04日