python中锁的归纳整理

1 锁的概括

# 以下是对python中锁的整理

image

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()
posted @ 2021-11-01 20:46  hai437  阅读(492)  评论(0编辑  收藏  举报