Python 线程间的同步机制1:Lock锁和Semaphore信号量

1.信号量:Semaphore

信号量的本质是一个计数器,某个线程调用信号量的.acquire()方法时,如果当前计数器>0,则计数器-1,某个线程调用.release()方法时,计数器+1。

acquire(blocking = True,timeout=None)

如果当前计数器不为0,acquire()返回True,如果计数器值为0,acquire()会被阻塞。设置blockingFalse,代表不阻塞直接返回False,设置timeout可以指定最大阻塞时间(为了方便阐述,不讨论blockingtimeout参数)。

release()

调用release使计数器+1。BoundedSemphore对象实例化时可指定MAX值,如果计数器值达到MAX,则后续的所有release都被阻塞。

我们可以给想要限制执行的代码块前面加入.acquire()代码,这样就可以限制最多有多少个线程可以执行该代码块,你想要执行,你就得先acquire,什么?计数器为0了?等着吧,等某个线程release之后,计数器不为0,你就可以进去了。

2.最常用的互斥锁:Lock

当一个线程调用Lock锁对象的.acquire方法,如果锁是unlocked状态,该线程会lock锁,如果锁已经是locked状态,则.acquire()会被阻塞。
当一个线程调用锁对象的release方法,如果锁已经是unlocked状态,会报错。如果锁是locked状态,会打开锁。

我们通常会在进入可能发生竞态的临界区前执行锁的acquire方法,在执行完临界区的代码后执行锁的release方法。这样就保证了同时最多只有一个线程执行临界区的代码。

我最开始是通过廖雪峰的python教程入门的,很感谢廖老师的无私奉献,廖老师讲到线程的锁时是这么说的:

如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。

应该是方便初学者理解,廖老师使用了“持有该锁”这样的描述,但实际上这样的描述是有问题的。我们这里所说的Lock并不会被某个线程持有,如果说锁被某个线程持有,那么理应只有调用acquire()拿到了锁的那个线程才能释放锁,但实际上,任何线程都可以调用release把锁释放,并不限于那个acquire了锁的线程。

这里的锁对于所有线程是一视同仁的,当某个线程成功acquire()仅仅是把锁从unlocked状态变为locked状态而已。如果不进入临界区的话,其它线程的执行并不受任何影响,但你如果要进入临界区,那你得先执行一个lock.acquire(),如果锁是locked状态,那么不好意思,你被阻塞了。

我们强调过:任何线程都可以调用release把锁释放。当一个线程执行完acquire进入临界区后,锁变为locked态,按理说直到它退出临界区调用release之前,别的线程都无法执行acquire,也就无法进入临界区。此时如果另一个线程有个刁民,它非要调用一个release把锁打开,会有什么后果?某一个本应被阻塞的acquire()就被放行了,临界区里出现了第二个线程,可能会出现竞态。

最后:
1.使用锁的时候要记得release。可以把release放在try...finally 的finally block中,这样保证无论如何都会release。也可以使用with语句,锁对象实现了上下文管理器协议,使用with语句时Lock对象会在其__enter__中调用acquire并且在__exit__中调用release。
如果由于某些异常导致release方法没有调用,其他线程中被acquire阻塞的倒霉蛋就只能永远等下去(死锁)。

2.个人认为Lock和MAX值为1的BoundedSemphore信号量很相似。锁为locked态对应BoundedSemphore计数器为0,此时所有acquire()都被阻塞。任何线程都可以执行release使锁进入unlocked态(计数器+1);
锁为unlocked态对应BoundedSemphore计数器为1,此时调用release会报错。而此时如果调用acquire不会阻塞,但会立刻改变锁的状态为locked态(计数器归零)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,可以使用`threading`模块中的信号来避免两个线程的竞争。 的作用是防止多个线程同时执行临界区代码,从而避免竞争条件。在Python中,可以使用`threading.Lock()`来创建一个对象,然后使用`acquire()`方法获取,使用`release()`方法释放。当一个线程获取时,其他线程如果想要获取就会被阻塞,直到被释放。 信号的作用是控制并发线程的数。在Python中,可以使用`threading.Semaphore()`来创建一个信号对象,然后使用`acquire()`方法获取信号,使用`release()`方法释放信号。当一个线程获取信号时,如果信号已经被其他线程获取,那么它就会被阻塞,直到有足够的信号可用。 下面是一个使用信号的示例代码: ```python import threading # 创建一个对象 lock = threading.Lock() # 创建一个信号对象,限制同时执行的线程数为2个 semaphore = threading.Semaphore(2) def worker(): # 获取 lock.acquire() print("线程{}正在执行...".format(threading.current_thread().name)) # 释放 lock.release() def worker_with_semaphore(): # 获取信号 semaphore.acquire() print("线程{}正在执行...".format(threading.current_thread().name)) # 释放信号 semaphore.release() # 创建10个线程,每个线程都执行worker函数 for i in range(10): t = threading.Thread(target=worker) t.start() # 创建10个线程,每个线程都执行worker_with_semaphore函数 for i in range(10): t = threading.Thread(target=worker_with_semaphore) t.start() ``` 在上面的代码中,我们创建了一个对象和一个信号对象,并分别在`worker()`函数和`worker_with_semaphore()`函数中使用它们来保证线程的同步和并发数的控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值