使用Lock和RLock对象
如果多个线程共同对某个数据进行修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步修改,在Python程序中,使用对象Lock和RLock
可以实现简单的线程同步功能,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。
多线程的优势在于可以同时运行多个任务(感觉上是这样),但是当线程需要共享数据时,可能存在数据不同步的问题。以下代码演示了使用RLock实现线程同步。
import threading import time class mt(threading.Thread): def run(self): global x lock.acquire() for i in range(5): x += 10 time.sleep(1) print(x) lock.release() x = 0 lock = threading.RLock() def main(): thrs = [] for item in range(8): thrs.append(mt()) for item in thrs: item.start() if __name__ == '__main__': main()
上述实例中,自定义了一个带锁访问全局变量x的线程类mt,在主函数main()中初始化了8个线程来修改x,在同一时间只能由一个线程对x进行操作,执行效果如下:
50,100,150....。在python程序中,要想让可变对象安全的用在多线程环境下,可以利用threading中的Lock对象来解决,以下代码演示了Lock对临界区加锁的过程。
import threading class ShareCounter: def __init__(self, initial_value=0): self._value = initial_value self._value_lock = threading.Lock() def incr(self, delta=1): with self._value_lock: self._value += delta def decr(self, delta=1): with self._value_lock: self._value -= delta def test(c): for n in range(10000): c.incr() for n in range(10000): c.decr() if __name__ == '__main__': c = ShareCounter() t1 = threading.Thread(target=test, args=(c,)) t2 = threading.Thread(target=test, args=(c,)) t3 = threading.Thread(target=test, args=(c,)) t1.start() t2.start() t3.start() print('Runing test') t1.join() t2.join() t3.join() assert c._value == 0 print('Locks good!',c._value) # 0
上述代码中,当使用with语句时,Lock对象可确保产生互斥的行为,也就是说在同一时间只运行一个线程执行with里面的语句。
使用Condition对象
在Python中,使用Condition对象可以在某些事件触发或达到特定条件下后才能处理数据,提供的Condition对象目的就是实现对复杂线程同步问题的支持,
Condition通常和一个锁关联,当需要在多个Condition中共享一个锁时,可以传递一个Lock/RLock实例,否则它会自动生成一把锁。下面代码会使用
Conditon实现一个捉迷藏游戏:1.当游戏开始之后,Seeker先把眼睛蒙上,统治Hider;2.Hider接收到通知后,将自己藏起来,再通知Seeker可以找了。
import threading, time class Hider(threading.Thread): def __init__(self,cond,name): super(Hider,self).__init__() # 需要先执行父类的初始化函数,否则name会被覆盖 self.cond = cond self.name = name def run(self): time.sleep(1) # 确保Seeker先运行 self.cond.acquire() # 3,获得锁,执行下面的操作 print("我已经把眼睛闭上了!") self.cond.notify() # 通知另一个解锁,自己挂起 self.cond.wait() print(self.name,": 我已经找到你了") self.cond.notify() self.cond.release() print(self.name,': 我赢了') class Seeker(threading.Thread): def __init__(self,cond,name): super(Seeker,self).__init__() # 需要先执行父类的初始化函数,否则name会被覆盖 self.cond = cond self.name = name def run(self): self.cond.acquire() # 1, 获得锁 self.cond.wait() # 2,释放锁的占用,同时线程挂起,知道notify() 并重新占用锁 print(self.name,": 我已经藏好了,你快点找我把") self.cond.notify() self.cond.wait() self.cond.release() print(self.name,": 被你找到了,哎") cond = threading.Condition() seeker = Seeker(cond, 'seeker') hider = Hider(cond, 'hider') seeker.start() hider.start() """ 我已经把眼睛闭上了! seeker : 我已经藏好了,你快点找我把 hider : 我已经找到你了 hider : 我赢了 seeker : 被你找到了,哎"""
使用Semaphore和BoundedSemaphore对象
Python中,可以使用Semaphore和BoundedSemaphore来控制多线程信号系统中的计数器。
1、Semaphore: 类threading.Semaphore是一个信号机,控制着对公共资源或者临界区的访问,信号机维护着一个计数器,指定可同时访问资源或者进入临界区的线程数,每次
有一个线程获得信号机,计数器为-1,如计数器为0,其他线程停止访问。
import threading, time def fun(semaphore,num): semaphore.acquire() print("降龙十八掌,发出%d掌"%num) time.sleep(3) semaphore.release() if __name__ =='__main__': semaphore = threading.Semaphore(2) for num in range(4): t = threading.Thread(target=fun, args=(semaphore,num)) t.start()
执行后,线程0,1是同时运行的,2,3是3秒后运行。
2、BoundedSemaphore: 它会检查内部计数器的值,并保证它不会大于初始值,如果超过就会引发一个ValueError错误,常用于守护限制访问资源。
使用Event对象
在Python中,事件对象是线程间最简单的通信机制之一,线程可以激活在一个事件对象上等待的其他线程。Event对象的实现类是threading.Event,
这是一个实现事件对象的类,一个event管理一个标志,该标志可以通过set()方法设置为真或通过clear()方法重新设置为假,wait()方法阻塞,直到标志为真,该标志
初始值为假。
import threading, time event = threading.Event() def func(): # print("%s wait for event ..."%threading.currentThread().getName()) # event.wait(timeout) 阻塞线程,直到event对象内部标实位为True,或超时 event.wait() print("%s recv event."%threading.currentThread().getName()) t1 = threading.Thread(target=func) t2 = threading.Thread(target=func) t1.start() t2.start() time.sleep(2) print("MainTread set event") event.set() # 将标识位改为True """ Thread-1 wait for event ... Thread-2 wait for event ... MainTread set event Thread-2 recv event. Thread-1 recv event."""
使用Timer对象
Timer:定时器,用于在指定时间后调用一个方法,相应的可以通过cancel() 函数取消相应的timer动作。
import threading def func(): print("ping") timer = threading.Timer(5,func) timer.start()