1 锁的概括
# 以下是对python中锁的整理
2 互斥锁
# 互斥锁,又名同步锁
# 最基本的锁
# 多个进程同时操作一份数据的时候,会出现数据错乱。
针对上述问题,解决方案就是加锁:将并发变成串行,牺牲效率换来数据安全。互斥锁只用于数据处理,能不用就不用。改串行会大幅降低程序效率。
# 加锁的代码演示
from multiprocessing import Lock ,Process
import time
import random
def read(i,mutex):
mutex.acquire() #加锁
time.sleep(random.randint(1,3)) #锁中间的代码会会从并发改成串行
print(f'{i}')
mutex.release() #解锁
'''这4行代码也可以写成:
with mutex:
time.sleep(random.randint(1,3)) #锁中间的代码会会从并发改成串行
print(f'{i}')
'''
if __name__ == '__main__':
mutex =Lock() #实例化一个锁
for i in range(1,5):
p=Process(target=read,args=(i,mutex))
p.start()
3 死锁现象
# 死锁现象是使用互斥锁时的一种现象,不是锁
# 描述:
场景1:2把锁,一人抢了一把,还都要抢另一把,就卡死了
场景2:1把锁,自己抢了,又要抢一次,就卡死了(程序的重复调用导致的)
# 都是代码逻辑导致的
4 递归锁
# 递归锁,又名重入锁
# 为了解决场景2的死锁现象,引入递归锁(RLock模块)
# 特点:
1.可以连续的acquire和realse
2.只能被第一次抢的人使用
3.内置计数器,acquire+1,realse-1,当计数为0时,释放锁
5 信号量(Semaphore)
#相当于是多把锁,用法和锁一样
import time
from threading import Thread,Semaphore #Semaphore模块
def fun():
s.acquire()
print('kkk')
time.sleep(1)
s.release()
s =Semaphore(5) #指定锁的个数,即同时运行的线程数
for i in range(1,9):
t = Thread(target=fun,)
t.start()
6 GIL全局解释器锁
# 特点:
1.GIL不是python的特点,而是Cpyhton的特点
2.GIL保证的是解释器级别的数据安全
Cpython中内存管理不是线程安全的,如果不加互斥锁,在给变量赋值的时候,有可能垃圾回收机制也在工作,导致错误
3.同一进程下多线程无法同时运行,无法利用多核优势,但可以快速切换,伪多线程
4.解释型语言的通病
5.针对不同的数据还是要加不同的锁处理(自己加锁是为了避免由于阻塞而自动切换线程)
注释:GIL本质就是给python解释器加了锁,使得线程间只能串行,保证了数据内存的安全
7 补充
7.1 行锁、表锁、页锁
# 行锁、表锁、页锁是对锁粒度划分
# 行锁:只针对当前操作的行加锁。行级锁能减少数据库操作的冲突。加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。行级锁按照使用方式分为共享锁和排他锁。
# 表锁:只针对当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁,但是InnoDB默认的是行级锁。是mysql锁中粒度最大的一种锁,
# 页锁:页级锁是MySQL中锁定粒度介于行级和表级中间的一种锁。表级锁速度快冲突多,行级冲突少速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
7.2 共享锁和排它锁
# 共享锁和排它锁是对锁使用方式划分
# 个人理解:
共享锁:加锁后,大家都能读,只有抢到锁的能写
排它锁:加锁后,只有抢到锁的能读、写
# 共享锁(读锁)
从多线程的角度来讲,共享锁允许多个线程同时访问资源,但是对写资源只能又一个线程进行。
从事务的角度来讲,若事务 T 对数据 A 加上共享锁,则事务 T 只能读 A; 其他事务也只能对数据 A 加共享锁,而不能加排他锁,直到事务 T 释放 A 上的 S 锁。这就保证了其他事务可以读 A,但是在事务 T 释放 A 上的共享锁之前,不能对 A 做任何修改。
# 排它锁(写锁)
从多线程的角度来讲,在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。
从事务的角度来讲,若事务T对数据对象A加上排它锁,则只允许T读取和修改数据A,其他任何事务都不能再对A加任何类型的锁,直到事务T释放X锁。它可以防止其他事务获取资源上的锁,直到事务末尾释放锁。
7.3 乐观锁和悲观锁
# 悲观锁、乐观锁是一种思想上的划分
# 悲观锁PCC:
见名知意,悲观的认为你会修改数据,所以在我修改前就加锁,再进行修改
# 乐观锁OCC:
假设数据不会冲突,在数据提交的时候才会进行冲突检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
# 总结:
1.无论是悲观锁还是乐观锁,他们本质上不是数据库中具体的锁概念,而是我们定义出来,用来描述两种类别的锁的思想。
2.相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。
7.4 event
#让一个线程等待另一个线程发送信号后再执行
import time
from threading import Thread, Event #Event模块
def fun():
print('kkk')
time.sleep(2)
event.set() #给另一个线程发信号
def fun2():
event.wait() #等待另一个线程的信号,接收到之后再继续执行
print("等啊等")
event = Event() #实例化
t = Thread(target=fun, )
t.start()
t2 = Thread(target=fun2, )
t2.start()