信号量 信号量用来控制线程并发数的,信号量里面维护了一个计数器,这个计数器可以理解为锁的数量,线程通过acquire方法去申请锁,每申请到一个锁,计数器就减1。线程通过release释放锁,每释放一个锁,计数器就加1。当计数器为0的时候,通过acquire方法去申请锁会被阻塞,直到有其它的线程释放锁让计数器不为0才有可能申请到锁。
信号量有两种BoundedSemaphore或Semaphore,用Semaphore举个栗子:
import threading, time
class myThread(threading.Thread):
def run(self):
semaphore.acquire()
print(threading.currentThread().name + " 获得锁")
time.sleep(1)
print(threading.currentThread().name + " 释放锁")
semaphore.release()
if __name__ == "__main__":
semaphore = threading.Semaphore(2)
for i in range(6):
myThread().start()
输出:
Thread-1 获得锁
Thread-2 获得锁
Thread-1 释放锁
Thread-2 释放锁
Thread-3 获得锁
Thread-4 获得锁
Thread-4 释放锁
Thread-5 获得锁
Thread-3 释放锁
Thread-6 获得锁
Thread-6 释放锁
Thread-5 释放锁
BoundedSemaphore或Semaphore的用法几乎是一样的,这两个信号量有什么区别呢?要想明白这两个信号量的区别,得先弄明白release这个方法。其实任何一个线程都可以调用release方法,即使这个线程没有获取过锁,并且一个线程可以多次调用release,任意一个线程调用release方法都是有效的。前面说过线程每调用一次release方法,信号量内部的计数器都会加1,所以会出现由于线程调用release次数过多,导致计数器的值大于信号量计数器的初始值。Semaphore对内部的计数器是没有限制的,但是BoundedSemaphore有限制,BoundedSemaphore内部的计数器大于初始值时会报错。
class MyThread(threading.Thread):
def run(self):
# semaphore.acquire()
# print(threading.currentThread().name + " 获得锁")
print(threading.currentThread().name + " 释放锁")
# 连续释放三次锁
semaphore.release()
semaphore.release()
semaphore.release()
class MyAcquire(threading.Thread):
def run(self):
semaphore.acquire()
time.sleep(5)
print(threading.currentThread().name + " 获得锁")
if __name__ == "__main__":
semaphore = threading.Semaphore(1)
MyThread().start()
for i in range(4):
MyAcquire().start()
输出:
Thread-1 释放锁
Thread-2 获得锁
Thread-5 获得锁
Thread-4 获得锁
Thread-3 获得锁
上面这个案例中,使用Semaphore信号量,一个线程多次释放锁,使得其它几个线程都能获取到锁。如果将Semaphore改成BoundedSemaphore,这个程序就会报错,因为BoundedSemaphore设置的计数器初始值是1,连续三次释放信号量肯定会使计数器的值大于1,而BoundedSemaphore是不允许计数器的值大于初始值,所以会抛出异常。程序里面是为了演示效果,所以让一个线程多次释放,实际使用的时候不要这么做,最好是线程获取一次信号量再释放一次信号量。
threading.local() threading.local()是一个全局对象,每个线程使用threading.local()都能创建属于当前线程特有的属性。举个简单的栗子:
import threading
a = threading.local()
def worker():
a.x = 0
a.x += 1
print(threading.currentThread().name, a.x)
for i in range(3):
threading.Thread(target=worker).start()
输出:
Thread-1 1
Thread-2 1
Thread-3 1
输出:
AttributeError: '_thread._local' object has no attribute 'y'
上面这个例子中加了一个a.y属性,这个属性只属于主线程,所以再其它线程中访问a.y的时候就报错了(AttributeError: ‘_thread._local’ object has no attribute ‘y’)。这进一步说明每个线程可以在threading.local()里面添加属于当前线程的特有属性,这些属性对其它线程是不可见的。
这是怎么实现的呢?其实Threading.local()内部维护了一个(key,dict)这么一个映射,每个线程在使用threading.local()时都会分配一个key,线程添加的属性都会存在dict里面,由于这个映射的存在,每个线程能且只能访问自己添加的属性。