线程同步锁、死锁、递归锁、信号量、GIL
2020-12-13 13:57
标签:全局 阻塞 time 锁定 list 条件 总结 就会 释放 目录 所有线程同一时间读写同一个数据,有的线程已经对数据进行修改了,造成有的线程拿到的数据时旧的数据,而不是修改后的数据,造成结果不正确,于是引入了同步锁解决问题, 同步锁的原理是同一时间只能有一个线程读写数据。 锁通常被用来实现对共享资源的同步访问。从threading模块导入一个Lock类,为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其他线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。 下面这个例子就需要用到同步锁: 上述代码输出结果是60000,看起来程序是没问题,但是当把for循环的20000改成更大的数的话,会出现错误的结果,比如改成200000,每次结果都是小于600000,这就出现了数据安全问题,这就需要用到同步锁,同一时间只能一个线程来操作x变量,t2只能等t1操作完再运行,只需要给对x变量的操作代码加个同步锁,具体使用同步锁看下面的代码: 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源释放后才能继续往下执行,就会造成死锁。因为系统判断这部分资源都正在使用,所以这两个线程在无外力作用下将一直等待(阻塞)下去。 一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。死锁是很容易发生的,尤其是在系统中出现多个同步锁的情况下。下面的代码就会出现死锁问题: 上述程序就是出现死锁问题,在线程2抢到锁1后阻塞住了。线程1拿到了(锁头2)想要往下执行需要(锁头1);线程2拿到了(锁头1)想要往下执行需要(锁头2)。两个线程互相都拿到了彼此想要往下执行的必需条件,互相都不释放手里的锁头,这就是死锁问题。 死锁是不应该出现在程序中的,所以在进行多线程编程时应该采取措施避免出现死锁。下面有几种常见的方式用来解决死锁问题。 为了支持在同一线程中多次请求同一资源,python提供了"递归锁":threading.RLock。 RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源. 下面用递归锁来解决上述的死锁问题: 上面我们用一把递归锁,就解决了多个同步锁导致的死锁问题。大家可以把RLock理解为大锁中还有小锁,只有等到内部所有的小锁,都没有了,其他的线程才能进入这个公共资源。 思考:如果我们都加锁也就是单线程了,那我们还要开多线程有什么用呢? 它控制同一时刻多个线程访问同一个资源的线程数 原理: 在Cpython解释器中有一把GIL锁(全局解释器锁),GIL锁本质是一把互斥锁。导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势。同一个进程下多个线程只能实现并发不能实现并行。 为什么在多线程中不能实现真正的并行操作呢? 为什么要有GIL锁? 如果一个线程抢掉了GIL,如果遇到io或者执行时间过长(cpu被剥夺),会强行释放掉GIL锁,以便其他的线程抢占GIL 分析:我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是: 计算密集型 :推荐使用多进程 IO密集型: 推荐使用多线程 具体看以下例子: 多进程 5.499674558639526 多线程 5.004202604293823 小结:你发现了嘛!!!多线程的时间更短,相差0.5秒意味着什么!!!你明白吗???奶茶都可以绕地球两百圈了!! 多线程为什么更快? 多进程 18.062480211257935 多线程 33.27059483528137 这回居然差了15秒之多,多进程为什么更快? 总结: 线程同步锁、死锁、递归锁、信号量、GIL 标签:全局 阻塞 time 锁定 list 条件 总结 就会 释放 原文地址:https://www.cnblogs.com/zhuangyl23/p/11546416.html
线程同步锁、死锁、递归锁、信号量、GIL
一、同步锁
from threading import Thread
x = 0
def task():
global x
for i in range(20000):
x=x+1
# t1 的 x刚拿到0 保存状态 就被切了
# t2 的 x拿到0 进行+1 1
# t1 又获得运行了 x = 0 +1 1
# 思考:一共加了几次1? 加了两次1 真实运算出来的数字本来应该+2 实际只+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)
from threading import Thread,Lock
x = 0
mutex = Lock()
def task():
global x
mutex.acquire() # 加锁,加锁之后,同一时间只能有一个线程操作下面的代码
for i in range(100000):
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)
二、死锁
from threading import Thread,Lock
mutex1 = Lock() # 同步锁也叫互斥锁
mutex2 = Lock()
import time
class MyThreada(Thread): # 用继承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
三、递归锁(Rlock)
# 递归锁 在同一个线程内可以被多次acquire
# 如何释放: 内部相当于维护了一个计数器 也就是说同一个线程 acquire了几次就要release几次
from threading import Thread,Lock,RLock
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()
------------------------------------------------------------------------------
Thread-1 抢到了 锁1
Thread-1 抢到了 锁2
Thread-1 释放了 锁2
Thread-1 释放了 锁1
Thread-1 抢到了 锁2
Thread-1 抢到了 锁1
Thread-1 释放了 锁1
Thread-1 释放了 锁2
Thread-2 抢到了 锁1
Thread-2 抢到了 锁2
Thread-2 释放了 锁2
Thread-2 释放了 锁1
Thread-2 抢到了 锁2
Thread-2 抢到了 锁1
Thread-2 释放了 锁1
Thread-2 释放了 锁2
Thread-3 抢到了 锁1
Thread-3 抢到了 锁2
Thread-3 释放了 锁2
Thread-3 释放了 锁1
Thread-3 抢到了 锁2
Thread-3 抢到了 锁1
Thread-3 释放了 锁1
Thread-3 释放了 锁2
四、信号量(Semphare)
from threading import Thread,currentThread,Semaphore
import time
def task():
sm.acquire()
print(f'{currentThread().name} 在执行')
time.sleep(3)
sm.release()
sm = Semaphore(5) # 规定一次只能有5个线程执行
for i in range(15):
t = Thread(target=task)
t.start()
------------------------------------------------------------------------------
Thread-1 在执行
Thread-2 在执行
Thread-3 在执行
Thread-4 在执行
Thread-5 在执行
Thread-7 在执行
Thread-6 在执行
Thread-8 在执行
Thread-9 在执行
Thread-10 在执行
Thread-12 在执行
Thread-15 在执行
Thread-14 在执行
Thread-13 在执行
Thread-11 在执行
五、GIL(全局解释器锁)
每个都要计算10s
多线程
在同一时刻只有一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40.ns
多进程
可以并行的执行多个线程,10s+开启进程的时间
4个任务每个任务90%大部分时间都在io.
多线程
可以实现并发,每个线程io的时间不咋占用cpu, 10s + 4个任务的计算时间
多进程
可以实现并行,10s+1个任务执行的时间+开进程的时间io密集型
'''采用多进程计时情况'''
from threading import Thread
from multiprocessing import Process
import time
def work1():
x = 1+1
time.sleep(5)
if __name__ == '__main__':
t_list = []
start = time.time()
for i in range(4):
t = Process(target=work1)
t_list.append(t)
t.start()
for t in t_list:
t.join()
end = time.time()
print('多进程',end-start)
'''采用多线程计时情况'''
from threading import Thread
from multiprocessing import Process
import time
def work1():
x = 1+1
time.sleep(5)
if __name__ == '__main__':
t_list = []
start = time.time()
for i in range(4):
t = Thread(target=work1)
# t = Process(target=work1)
t_list.append(t)
t.start()
for t in t_list:
t.join()
end = time.time()
print('多线程',end-start)
计算密集型
'''采用多进程计时情况'''
from threading import Thread
from multiprocessing import Process
import time
def work1():
res=0
for i in range(100000000): #1+8个0
res*=i
if __name__ == '__main__':
t_list = []
start = time.time()
for i in range(4):
t = Process(target=work1)
t_list.append(t)
t.start()
for t in t_list:
t.join()
end = time.time()
print('多进程',end-start)
'''采用多线程计时情况'''
from threading import Thread
from multiprocessing import Process
import time
def work1():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
t_list = []
start = time.time()
for i in range(4):
t = Thread(target=work1)
# t = Process(target=work1)
t_list.append(t)
t.start()
for t in t_list:
t.join()
end = time.time()
print('多线程',end-start)