共享变量/线程同步
- 多个线程同时访问同一个变量时会产生共享变量问题
- 使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。
- 解决办法:锁,信号灯
- 锁(Lock)
- 是一个标志,表示一个线程在占用一些资源
- 使用方法
- 对资源上锁
- 使用资源
- 取消锁,释放锁
- 锁(Lock)
线程安全问题
- 如果一个资源/变量,对于多线程来讲,不用加锁也会引起任何问题,则称为线程安全
- 线程不安全的变量类型:list,set,dict
- 线程安全的变量类型:queue
In [31]:
#本段代码中由于sum为myAdd与myMinu的共享变量,而且并未合理设置,出现的结果未达到预期结果
import threading
sum = 0
loopSum = 100000
def myAdd():
global sum,loopSum
for i in range(1,loopSum+1):
sum += 1
def myMinu():
global sum,loopSum
for i in range(1,loopSum+1):
sum -= 1
if __name__ == '__main__':
print('开始了')
t1 = threading.Thread(target=myAdd,args=())
t2 = threading.Thread(target=myMinu,args=())
t1.start()
t2.start()
t1.join()
t2.join()
print('结果是:{0}'.format(sum))
开始了 结果是:-23806
In [47]:
#锁 实例
import threading
sum = 0
loopSum = 100000
lock = threading.Lock()
def myAdd():
global sum,loopSum
for i in range(1,loopSum+1):
#上锁
lock.acquire()
sum += 1
#释放锁
lock.release()
def myMinu():
global sum,loopSum
for i in range(1,loopSum+1):
#上锁
lock.acquire()
sum -= 1
#释放锁
lock.release()
if __name__ == '__main__':
print('开始了是{0}'.format(sum))
t1 = threading.Thread(target=myAdd,args=())
t2 = threading.Thread(target=myMinu,args=())
t1.start()
t2.start()
t1.join()
t2.join()
print('结果是:{0}'.format(sum))
开始了是0 结果是:0
线程优先级队列( Queue)
- Python的Queue模块中提供了同步的、线程安全的队列类,包括
- FIFO(先入先出)队列Queue
- LIFO(后入先出)队列LifoQueue
- 优先级队列PriorityQueue
- 这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
- Queue模块中的常用方法:
- Queue.qsize() 返回队列的大小
- Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False
- Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait() 相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- Queue.join() 实际上意味着等到队列为空,再执行别的操作
In [61]:
import queue
import time
import threading
class Producer(threading.Thread):
def run(self):
global qu
count = 0
while True:
if qu.qsize()<100:
for i in range(100):
count = count + 1
msg = '生产产品'+str(count)
qu.put(msg)
print(msg)
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global qu
while True:#无线循环
if qu.qsize()>10:
for i in range(3):
msg = self.name + '消费了' +qu.get()
print(msg)
time.sleep(1)
if __name__ == '__main__':
qu = queue.Queue()
for i in range(500):
qu.put('初始化产品'+str(i))
for i in range(2):
p = Producer()
p.start()
for i in range(5):
c = Consumer()
c.start()
Thread-145消费了初始化产品0 Thread-145消费了初始化产品1 Thread-145消费了初始化产品2 Thread-146消费了初始化产品3 Thread-146消费了初始化产品4 Thread-146消费了初始化产品5 Thread-147消费了初始化产品6 Thread-147消费了初始化产品7 Thread-147消费了初始化产品8 Thread-148消费了初始化产品9 Thread-148消费了初始化产品10 Thread-148消费了初始化产品11 Thread-149消费了初始化产品12 Thread-149消费了初始化产品13 Thread-149消费了初始化产品14 Thread-118消费了初始化产品15Thread-115消费了初始化产品18Thread-114消费了初始化产品21Thread-116消费了初始化产品24Thread-117消费了初始化产品27 #进入死循环
死锁/解决死锁
- 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
- 添加timeout 锁的等待时间
- Timer: 隔一定时间调用一个函数,如果想实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread的一个派生类
- semphore (信号量) 允许一个资源最多有几个线程同时使用
- threading模块里的Semaphore类实现了信号量对象,可用于控制获取资源的线程数量。
- 所具有的acquire()和release()方法,能够使用with语句的上下文管理器。
- 当进入时,将调用acquire()方法,当退出时,将调用release()。
- acquire(blocking=True, timeout=None):
- timeout设置超时秒。如果未在时间间隔内完成,返回false,否则返回true
- 没有blocking返回false,否则返回true。
- release():释放一个信号量
递归锁
- 解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
- 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
- RLock:为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
In [3]:
#死锁
from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 拿到A锁' %self.name)
mutexB.acquire()
print('%s 拿到B锁' %self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('%s 拿到B锁' %self.name)
time.sleep(2)
mutexA.acquire()
print('%s 拿到A锁' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()
Thread-24 拿到A锁 Thread-24 拿到B锁 Thread-24 拿到B锁 Thread-25 拿到A锁
In [4]:
#信号量
import threading
import time
s = threading.Semaphore(2)
def func():
if s.acquire():
for i in range(3):
print('调用线程 {0}'.format(i))
time.sleep(10)
print('释放线程 {0}'.format(i))
s.release()
for i in range(4):
t = threading.Thread(target=func)
t.start()
调用线程 0 调用线程 0 释放线程 0 调用线程 1 释放线程 0 调用线程 1 调用线程 0 调用线程 0 释放线程 1释放线程 1释放线程 0 调用线程 2调用线程 1调用线程 2 释放线程 0 调用线程 1 释放线程 2释放线程 2释放线程 1释放线程 1 调用线程 2调用线程 2 释放线程 2释放线程 2
In [*]:
#Timer
import threading
import time
def func():
print('running,running...')
time.sleep(2)
print('done done')
if __name__ == "__main__":
t = threading.Timer(3,func)
t.start()
i = 0
while i<6:
print('{0}hahahahaha'.format(i))
time.sleep(5)
i += 1
In [1]:
#RLock()
import threading
import time
class MyThread(threading.Thread):
def run(self):
global num
time.sleep(1)
if mutex.acquire(1):
num = num+1
msg = self.name+' set num to '+str(num)
print (msg)
mutex.acquire()
mutex.release()
mutex.release()
num = 0
mutex = threading.RLock()
def test1():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test1()
Thread-4 set num to 1Thread-5 set num to 2 Thread-6 set num to 3 Thread-7 set num to 4 Thread-8 set num to 5