1 线程互斥锁
2 进程互斥锁
3 死锁
4 递归锁
线程互斥锁(Mutex,全名 Mutual Exclusion)是多线程编程中一种用于同步线程访问共享资源的机制。
在并发编程中,当多个线程同时访问共享的数据或资源时,可能会导致不确定的行为,
因为一个线程的操作可能与其他线程的操作交叉进行,导致数据不一致或错误。
互斥锁通过确保在任何时刻只有一个线程可以访问共享资源来解决这个问题。
当一个线程获得了互斥锁(锁定),其他线程就必须等待,直到这个线程释放锁。
from threading import Thread
from multiprocessing import Lock
import time
money = 100
mutex = Lock()
def task():
global money
mutex.acquire()
tmp = money
time.sleep(0.1)
money = tmp - 1
mutex.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
进程互斥锁是一种用于多进程编程的同步原语,用于保护共享资源,确保在任何时刻只有一个进程可以访问
这些资源。在多进程环境中,由于进程之间拥有独立的内存空间,因此需要一种机制来协调它们对共享资源
的访问,以避免数据竞争和不一致的状态。
作用:
保护共享资源: 进程互斥锁用于确保在任何给定时刻只有一个进程可以访问共享资源。
这对于防止数据竞争和维护一致性非常重要。
同步进程: 进程互斥锁还可以用于同步进程的执行。
例如,一个进程可能需要等待另一个进程完成某个任务之后再继续执行。
import time
from multiprocessing import Process, Lock
import json
import random
def search(i):
with open('data', 'r', encoding='utf8') as f:
dic = json.load(f)
print(f'用户{i}查询余票{dic.get("ticket_num")}')
def buy(i):
with open('data', 'r', encoding='utf8') as rf:
dic = json.load(rf)
time.sleep(random.randint(1, 3))
if dic.get('ticket_num') > 0:
dic['ticket_num'] -= 1
with open('data', 'w', encoding='utf8') as wf:
json.dump(dic, wf)
print(f'用户{i}买票成功')
else:
print(f'用户{i}没有抢到票')
def run(i, mutex):
search(i)
mutex.acquire()
buy(i)
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(1, 11):
p = Process(target=run, args=(i, mutex))
p.start()
"""
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是加锁处理:将并发编程串行,牺牲效率但是保证了数据的安全。
"""
lock3_h1_127">
lock">3 死锁
***死锁***是指两个或多个进程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。
死锁的产生通常包含以下几个必要条件,称为死锁的四个必要条件:
1. **互斥条件(Mutual Exclusion):** 一个资源每次只能被一个进程使用。
2. **请求与保持条件(Hold and Wait):** 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3. **不剥夺条件(No Preemption):** 进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时自己释放。
4. **环路等待条件(Circular Wait):** 若干进程之间形成一种循环等待资源的关系。
这四个条件同时满足时,就可能发生死锁。
**解决死锁的方法:**
1. **预防死锁:** 通过破坏死锁的四个必要条件中的一个或多个,来预防死锁的发生。
例如,使用资源的有序分配,使得每个进程按照一定的顺序获取资源,防止环路等待。
2. **避免死锁:** 在系统运行时采取一些策略来避免死锁。
银行家算法是一种典型的避免死锁的算法,通过判断每次资源请求是否使系统处于安全状态,
来避免死锁的发生。
3. **检测死锁:** 允许死锁的发生,但是在发生死锁时,能够及时地检测到并采取措施解除死锁。
操作系统可以周期性地检测系统中是否有死锁发生,一旦检测到死锁,
就通过终止某些进程或者撤销某些资源的分配来解除死锁。
4. **解除死锁:** 当系统检测到死锁存在时,可以通过撤销进程或者回收资源等手段来解除死锁。
死锁是多任务系统中常见的问题,需要在系统设计和运行中采取合适的措施来防范和处理。
from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(2)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start()
递归锁(Recursive Lock)是一种特殊类型的互斥锁,也称为可重入锁。
它允许同一线程在多次请求时成功地获取锁,而不会造成死锁。当一个线程持有一个递归锁时,
它可以再次获取自己已经持有的锁,而不会发生阻塞。
递归锁在多层递归函数中可以避免死锁的发生。在一个函数内部调用自己,如果没有递归锁,
可能会因为锁的重复获取而导致死锁。递归锁保证了在同一线程内对于同一个递归锁的重复加锁和解锁是安全的。
使用递归锁的主要场景是在复杂的程序结构中,可能存在多层嵌套的函数调用,这时如果需要在这些函数之间
使用锁来保护共享资源,递归锁就能够方便地解决同一线程中多次获取锁的问题。
"""
递归锁的特点:
可以被连续的acquire和release
但是只能被第一个抢到这把锁执行上述操作
它的内部有一个计数器,每acquire一次计数加1,每release一次计数减1
只要计数不为0,那么其他人都无法抢到该锁
"""
from threading import Thread, Lock, RLock
import time
mutexA = mutexB = RLock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(2)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start()