互斥锁、死锁、递归锁、信号量、Event
2017-07-18 18:22
204 查看
互斥锁
死锁和递归锁
Semaphore信号量
Event事件
在多进程/多线程程序中,当多个线程处理一个公共数据时,会有数据安全问题,唯一能保证数据安全的,就是通过加锁的方式,同一时间只能有一个修改数据的操作,将处理数据变为串行。虽然牺牲了速度,但是保证了数据安全。
来看一个不加锁的栗子:
在上面这个程序中,我们开一百个线程,每个线程都对全局变量num实现-1的操作,如果顺利,最终num的值应该为0.
实际运行过程是这样的:
100个线程开始抢GIL,抢到的将被CPU执行:
step1: 执行global num
step2: temp = num 赋值操作
step3: 发生I/O阻塞,挂起,GIL释放 (下一步的num=temp-1 还未被执行,因此全局变量num的值仍然为100)
剩余的99个线程抢GIL锁,重复上面的步骤。
剩余的98个线程抢GIL锁,重复上面的步骤。
。。。
如果阻塞时间够长(比如大于0.1秒),在阻塞期间,100个线程都被被切换一遍的话,那么最终num的值是99;
如果阻塞时间短一点,在某个时刻,前面阻塞的线程恢复并抢到了GIL被CPU继续执行,那么执行num=temp-1赋值操作 ,全局变量num的值被改变,线程结束,下一个被执行的线程拿到的num值就是99……依次类推,最终num的值经过多次赋值操作后将变得不确定,这取决于有多多少线程从阻塞中恢复过来。
如果不阻塞的话,每个线程都会执行对num 赋值操作,下一个线程拿到的num就是上一个线程减一的结果,最终num的值归零。
下面我们进行加锁操作:
lock = threading.Lock() # 获取锁对象
lock.acquire() # 加锁
数据操作部分
lock.release() # 释放锁
上面加锁和解锁的操作也可以通过上下文管理来实现:
解决方案就是使用递归锁:
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
rlock = threading.RLock() # 拿到一个可重入锁对象,将上面的所有锁都更换为rlock。
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire
4000
()将阻塞线程直到其他线程调用release()。
锁信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念。
看一个栗子:
创建一个event对象:
event = threading.Event()
event对象的方法:
1. event.isSet() 或 event.is_set(), 返回event对象的bool值,event对象的初始bool值是False.
2. event.wait() 如果上面是True, 啥也不做,往下执行,如果上面是False, 则阻塞线程. wait(num)为超时设置,超过num秒,继续往下执行。
3. event.set() 设置event对象True
4. event.clear() 恢复为False
5. 图示:
上栗子:
死锁和递归锁
Semaphore信号量
Event事件
互斥锁
互斥锁也叫用户锁、同步锁。在多进程/多线程程序中,当多个线程处理一个公共数据时,会有数据安全问题,唯一能保证数据安全的,就是通过加锁的方式,同一时间只能有一个修改数据的操作,将处理数据变为串行。虽然牺牲了速度,但是保证了数据安全。
来看一个不加锁的栗子:
import time def sub(): global num temp = num time.sleep(0.001) num = temp -1 num = 100 t_l = [] for i in range(100): t = threading.Thread(target=sub,args=()) t.start() t_l.append(t) for t in t_l: t.join() print(num)
在上面这个程序中,我们开一百个线程,每个线程都对全局变量num实现-1的操作,如果顺利,最终num的值应该为0.
实际运行过程是这样的:
100个线程开始抢GIL,抢到的将被CPU执行:
step1: 执行global num
step2: temp = num 赋值操作
step3: 发生I/O阻塞,挂起,GIL释放 (下一步的num=temp-1 还未被执行,因此全局变量num的值仍然为100)
剩余的99个线程抢GIL锁,重复上面的步骤。
剩余的98个线程抢GIL锁,重复上面的步骤。
。。。
如果阻塞时间够长(比如大于0.1秒),在阻塞期间,100个线程都被被切换一遍的话,那么最终num的值是99;
如果阻塞时间短一点,在某个时刻,前面阻塞的线程恢复并抢到了GIL被CPU继续执行,那么执行num=temp-1赋值操作 ,全局变量num的值被改变,线程结束,下一个被执行的线程拿到的num值就是99……依次类推,最终num的值经过多次赋值操作后将变得不确定,这取决于有多多少线程从阻塞中恢复过来。
如果不阻塞的话,每个线程都会执行对num 赋值操作,下一个线程拿到的num就是上一个线程减一的结果,最终num的值归零。
下面我们进行加锁操作:
lock = threading.Lock() # 获取锁对象
lock.acquire() # 加锁
数据操作部分
lock.release() # 释放锁
import threading import time def sub(lock): global num lock.acquire() #获得锁 temp = num time.sleep(0.01) num = temp -1 lock.release() # 执行完数据修改,释放锁 num = 100 lock = threading.Lock() # 实例化一个用户锁/互斥锁,这个锁是全局变量, # 每个线程获取到锁才能执行,执行完了释放,下一个线程才能获取锁 t_l = [] for i in range(100): t = threading.Thread(target=sub,args=(lock,)) t.start() t_l.append(t) for t in t_l: t.join() print(num)
上面加锁和解锁的操作也可以通过上下文管理来实现:
with lock: 数据修改部分
死锁和递归锁
两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。看一个栗子:import threading import time class MyThread(threading.Thread): def run(self): self.foo() self.bar() def foo(self): LockA.acquire() print('%s got LockA '% self.name) LockB.acquire() print('%s got LockB'%self.name) LockB.release() LockA.release() # Thread-1在执行完foo函数后,释放锁。然后继续执行bar函数,重新获取锁。 def bar(self): LockB.acquire() print('%s got LockB ' % self.name) time.sleep(1) # 让Thread-1获得了LockB后阻塞,OS切换线程Thread-2,其先执行foo,获取到LockA,然后需要获取LockB,才能执行下去,才能释放锁。而LockB在Thread-1手中,Thread-1从阻塞中恢复,需要获得LockA才能继续执行下去,才能释放锁。于是两个线程互相等待,发生死锁。 LockA.acquire() # 因为LockA已经被其它线程抢走了,所以这里卡死了。 print('%s got LockA' % self.name) LockA.release() LockB.release() LockA = threading.Lock() LockB = threading.Lock() for i in range(10): t = MyThread() t.start() '''死锁了 Thread-1 got LockA Thread-1 got LockB Thread-1 got LockB Thread-2 got LockA '''
解决方案就是使用递归锁:
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
rlock = threading.RLock() # 拿到一个可重入锁对象,将上面的所有锁都更换为rlock。
Semaphore信号量
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据。Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire
4000
()将阻塞线程直到其他线程调用release()。
锁信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念。
看一个栗子:
import threading import multiprocessing import time, os, random def go_wc(sem,name): with sem: print('员工%s 抢到一个茅坑,开始蹲...'% name) time.sleep(random.uniform(1,3)) print('员工%s 完事了,感到身心愉悦...'% name) if __name__ == '__main__': # 如果是进程的话,在windows系统下,进程必须写到if __name__ == '__main__':内,否则报错 print('大家开始上厕所》》》') sem = threading.Semaphore(3) # 设定最大为3 # sem = multiprocessing.Semaphore(3) t_l = [] for i in range(10): t = threading.Thread(target=go_wc, args=(sem,i)) # t = multiprocessing.Process(target=go_wc,args=(sem,i)) t.start() t_l.append(t) for t in t_l: t.join() print('大家都完事了《《《') ''' 大家开始上厕所》》》 员工1 抢到一个茅坑,开始蹲... 员工0 抢到一个茅坑,开始蹲... 员工5 抢到一个茅坑,开始蹲... 员工0 完事了,感到身心愉悦... 同一时刻,只有3个坑,其中一个完事了,下一个才能开始 员工4 抢到一个茅坑,开始蹲... '''
Event事件
在多线程环境中,每个线程的执行一般是独立的,如果一个线程的执行依赖于另一个线程的状态,那么就有必要引入某种标志位来进行判断,event就相当于一个全局的标志位。event常用于主线程控制其他线程的执行。创建一个event对象:
event = threading.Event()
event对象的方法:
1. event.isSet() 或 event.is_set(), 返回event对象的bool值,event对象的初始bool值是False.
2. event.wait() 如果上面是True, 啥也不做,往下执行,如果上面是False, 则阻塞线程. wait(num)为超时设置,超过num秒,继续往下执行。
3. event.set() 设置event对象True
4. event.clear() 恢复为False
5. 图示:
上栗子:
import threading import time def request(): print('waitting for server...') event.wait() #阻塞,等待主线程开启服务器 print('connecting to server....') if __name__ == '__main__': event = threading.Event() for i in range(5): t = threading.Thread(target=request) t.start() print('attemp to start server') time.sleep(3) event.set() # 开启服务器后,更改event状态
相关文章推荐
- python多线程,event,互斥锁,死锁,递归锁,信号量
- 守护进线程,互斥锁,信号量,队列,死锁递归锁等
- Python3之多线程GIL、同步锁、信号量、死锁与递归锁、线程Queue、Event、定时器
- python并发编程之多线程2------------死锁与递归锁,信号量等
- python并发编程之多线程2------------死锁与递归锁,信号量等
- 死锁与递归锁及信号量等
- 12.1、多线程:互斥锁、递归锁、信号量、事件
- Python--同步锁(互斥锁)、死锁(状态)、递归锁、信号量、Event对象
- Python进阶(3)_进程与线程中的lock(线程中互斥锁、递归锁、信号量、Event对象、队列queue)
- FreeRTOS 二值信号量,互斥信号量,递归互斥信号量
- Linux驱动学习5(详细分析字符设备驱动信号量实现互斥)
- C++多线程信号量,互斥
- java多线程 信号量(Semaphore),死锁
- delphi 多线程同步 互斥变量,信号量,事件对象。http://www.bianceng.cn/Programming/Delphi/200912/12689.htm
- 操作系统的信号量 进程互斥 同步等概念
- 互斥同步-临界区,互斥量,信号量,事件的区别
- 互斥锁、死锁和递归锁
- 信号量和同步互斥
- pthread互斥信号量使用总结
- 条件变量与互斥锁、信号量的区别