python-悲观锁和乐观锁

乐观锁和悲观锁它们都是一种思想,都是人们定义出来的概念,和语言无关
并发控制:当程序出现并发的问题时,我们需要保证在并发情况下数据的准确性,以保证当前用户在和其他用户一起操作时,得到的结果和他单独操作时得到的结果是一样的,没有做好并发控制,就可能导致脏读、幻读、不可重复读等问题
悲观锁:当要对一条数据进行修改时,为了避免数据同时被其他人修改,最好的办法,最好的方法就是直接对该数据进行加锁,让并行变成串行,其他线程想要访问数据时需要阻塞挂起,互斥锁就属于悲观锁
乐观锁:总是假设最好的情况,每次拿数据都认为别人不会修改,所以不会上锁,只在更新的时候会判断以下在此期间别人有没有更新这个数据,如果更新了,我们就不会修改
乐观锁的实现:
CAS:即比较并替换,是解决多线程并行情况下使用锁造成性能损耗的一种机制。CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B),执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。CAS会出现ABA的问题:比如我们准备对一个数据进行修改,这个数是1,在准备修改和提交的这段时间内,另一个事物已经将数字修改成了2,然后再修改为1,那么虽然我准备修改的数字是1,但是已经不是原来的那个1了。
解决ABA问题,我们需要对数据增加一个版本控制字段,只要有人动过这个数据,就将版本增加,修改一次版本是1,在修改一次版本是2,就说明这个数字有人动过。
版本号控制就是典型的CAS的应用:在数据表中增加一个版本号字段,每次更新数据时将版本号加1,同时将当前版本号作为更新条件,如果当前版本号与更新时的版本号一致,则更新成功,否则更新失败
使用场景:
并发量:如果并发量不大,可以使用悲观锁解决并发问题,如果并发量非常大的话,悲观锁会产生很严重的性能问题
响应速度:要求响应速度搞,建议使用乐观锁,成功就成功,失败就失败
冲突频率:冲突频率很高建议使用悲观锁
重试代价:重试代价大,建议使用悲观锁
读多写少:推荐使用乐观锁

django事务操作:

每个视图函数开启
from django.db import transaction
@transaction.atomic
def seckill(request):

局部开启
from django.db import transaction
def seckill(request):
	with transaction.atomic():
		pass
	return HttpResponse('秒杀成功')

保存点,回滚保存点Savepoint
设置回滚点:sid = transaction.savepoint()
提交(从回滚点开始):transaction.savepoint_commit(sid)
提交(整个事务):transaction.commit()
回滚到回滚点:transaction.savepoint_rollback(sid)   transaction.rollback()
事务提交后回调函数:transaction.on_commit(send_email)

django中事务的使用:

from django.shortcuts import render,HttpResponse
from django.db import transaction
from .models import Book,Order
# Create your views here.

def seckill(request):
    with transaction.atomic():
        sid = transaction.savepoint()
        print(sid)  # s96612_x1
        try:
            book = Book.objects.get(pk=1)
            book.name = '红楼梦'
            book.save()
            '''book表中有数据,order表中没有数据,所以try代码会报错,走道except会回滚,如果是order
            表中有数据,book表中书名会修改成功'''
            Order.objects.get(pk=1)
            '''从回滚点开始,提交事务'''
            transaction.savepoint_commit(sid)
        except Exception as e:
            transaction.savepoint_rollback(sid)
            print('出异常了,回滚')

    return HttpResponse('秒杀成功')

image
image
事务执行成功之后还可以执行回调函数:

from django.shortcuts import render,HttpResponse
from django.db import transaction
from .models import Book,Order
# Create your views here.

def send_email():
    print('发邮件了')

def seckill(request):
	'''开启事务'''
    with transaction.atomic():

        book = Book.objects.get(pk=1)
        book.count -= 1
        book.save()
        transaction.on_commit(send_email)

    return HttpResponse('秒杀成功')

image
现在用代码实现悲观锁:只要在orm中加入了select_for_update(),这一行代码就加上了锁,直到事务结束,用@transaction.atomic就是视图函数执行完毕事务才结束。由于我们的筛选条件是.filter(pk=1).first(),我们的悲观锁是行锁,这个事务只锁定这一行。如果我们查询的是表中所有数据(.all()),那么锁就变成了表锁。只要加了锁,线程执行就变成了串行。
使用场景:秒杀

import time

@transaction.atomic
def seckill(request):
    '''设置回滚点'''
    sid = transaction.savepoint()
    '''只要执行了select_for_update(),开启悲观锁,直到事务结束才释放锁(如果事务范围想控制小一些可以使用with)'''
    book_obj = Book.objects.select_for_update().filter(pk=1).first()
    time.sleep(4)
    if book_obj.count > 0:
        count = book_obj.count - 1
        book_obj.count = count
        book_obj.save()
        Order.objects.create(order_id='weyqwui',order_name='www')
        return HttpResponse('秒杀成功')
    else:
        return HttpResponse('库存不足,秒杀失败')

image
image
乐观锁代码实现:思路是在拿到数据的时候先记录一下,然后在修改的时候加一条筛选条件,除了pk或者name还应该加一条库存,如果筛选不到,回滚,继续修改。直到修改成功,或者库存为0秒杀失败:

@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    sid = transaction.savepoint()
    book = Book.objects.filter(pk=1).first()  # 没加锁
    count = book.count
    print('现在的库存为:%s' % count)
    if book.count > 0:
        print('库存可以,下单')
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
        # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
        time.sleep(random.randint(1, 4))  # 模拟延迟
        res = Book.objects.filter(pk=1, count=count).update(count=count - 1)
        if res >= 1:  # 表示修改成功
            transaction.savepoint_commit(sid)
            return HttpResponse('秒杀成功')
        else:  # 修改不成功,回滚
            transaction.savepoint_rollback(sid)
            return HttpResponse('被别人改了,回滚,秒杀失败')

    else:
        transaction.savepoint_rollback(sid)
        return HttpResponse('库存不足,秒杀失败')
posted @   ERROR404Notfound  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
Title
点击右上角即可分享
微信分享提示