一、同步锁
1.1 多个线程抢占资源的情况
from threading import Thread,Lock
x = 0
def task():
global x
for i in range(200000):
x = x+1
# t1 的 x刚拿到0 保存状态 就被切了
# t2 的 x拿到0 进行+1 1
# t1 又获得运行了 x = 0 +1 1
# 这就产生了数据安全问题.
if __name__ == '__main__':
# 使用的是操作系统的原生线程.
t1 = Thread(target=task)
t2 = Thread(target=task)
t3 = Thread(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(x)
1.2 对公共数据进行锁操作
import threading
lock = threading.Lock()
lock.acquire() # 锁头
'''
公共数据的一系列操作
'''
lock.replease() # 释放锁
1.3 同步锁的引用
from threading import Thread,Lock
x = 0
mutex = Lock()
def task():
global x
mutex.acquire()
for i in range(200000):
x = x+1
mutex.release()
if __name__ == '__main__':
# 使用的是操作系统的原生线程.
t1 = Thread(target=task)
t2 = Thread(target=task)
t3 = Thread(target=task)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(x) #结果肯定是600000,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
二、死锁与递归锁
所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
2.1 死锁实例
from threading import Thread,Lock
mutex1 = Lock()
mutex2 = Lock()
import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ') # 程序在这里的时候会卡住
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
for i in range(3):
t = MyThreada()
t.start()
Thread-1 抢到了 锁1
Thread-1 抢到了 锁2
Thread-1 释放了 锁2
Thread-1 释放了 锁1
Thread-1 抢到了 锁2
Thread-2 抢到了 锁1
**:线程1在sleep1秒后拿到了锁头1,但是在线程1sleep的过程中,操作系统已经去调度了其他的线程,而另一个线程开始运行拿到了锁头2,然后当线程拿到锁2时sleep的时候,cpu又调度去执行了线程1,但是线程1这个时候已经释放锁1,需要锁2,但是锁2此时已经被线程2拿到还没有释放,而线程2接着往下执行的条件是拿到锁1,也就是可以理解为:
线程1 拿到锁1,没释放
线程2 拿到锁2,没释放
线程1往下执行的条件是拿到锁2
线程2往下执行的条件是拿到锁1
这个时候,就出现了资源占用的问题,程序就会一直卡在那里,造成了死锁
解决的放法:递归锁
递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。
只有同一个线程下可以多次acquire,acquire了几次就要release几次
2.2 递归锁解决上述死锁问题
from threading import Thread,Lock,RLock
# 递归锁 在同一个线程内可以被多次acquire
# 如何释放 内部相当于维护了一个计数器 也就是说同一个线程 acquire了几次就要release几次
# mutex1 = Lock()
# mutex2 = Lock()
mutex1 = RLock()
mutex2 = mutex1
import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
for i in range(3):
t = MyThreada()
t.start()
# 此时程序将会正常执行结束
三、信号量
关键字:Semaphore
Semaphore也是一个类,需要一个参数,参数是整型,代表可以同时可以有多个线程可以拿到多把锁
参数是几就代表一次可以执行多少个线程去拿几把锁
from threading import Thread,currentThread,Semaphore
import time
def task():
sm.acquire()
print(f'{currentThread().name} 在执行')
time.sleep(3)
sm.release()
sm = Semaphore(5)
for i in range(15):
t = Thread(target=task)
t.start()
原文地址:https://www.cnblogs.com/xichenHome/p/11569104.html