同步的场景:多数情况是多个线程需要操作 相同的变量。
比如:A、B两个线程同时删除某个列表中的同一个元素,A删除完之后,B再去删除,B会找不到该元素,从而出错。示例如下程序所示
import threading, time
import queue
li = [1, 2, 3, 4, 5]
def pri(id):
while li:
a = li[-1]
print('%i--%i' % (id, a))
time.sleep(1)
try:
li.remove(a)
print('%i--%i--%s' % (id, a, 'remove complete'))
except Exception as e:
print('%i--%i--%s' % (id, a, e))
t1 = threading.Thread(target = pri, args = (1, ))
t1.start()
t2 = threading.Thread(target = pri, args = (2,))
t2.start()
输出结果为
从输出结果我们可知,线程1要删除列表中的5,线程2也要删除,但是在线程1删除5之前线程2已经删除了,所以挡线程1再去删除5,就会抛出5不在列表中的异常
改进方法,就是通过加锁。在这里加锁的方式有两种:第一种是通过线程锁;第二种是将list转换为queue,queue提供同步的、线程安全的队列类
1.加线程锁,示例程序如下所示:
import threading, time
import queue
li = [1, 2, 3, 4, 5]
threadLock = threading.Lock()
def pri(id):
while li:
threadLock.acquire()
if len(li) > 0:
a = li[-1]
print('%i--%i' % (id, a))
time.sleep(1)
try:
li.remove(a)
print('%i--%i--%s' % (id, a, 'remove complete'))
# threadLock.release()
except Exception as e:
print('%i--%i--%s' % (id, a, e))
threadLock.release()
t1 = threading.Thread(target = pri, args = (1, ))
t1.start()
t2 = threading.Thread(target = pri, args = (2,))
t2.start()
输出结果为:
可以看到并不会抛出异常,是因为在线程1删除列表中最后一个元素时,用threadLock.acquire()和threadLock.release() 将它们之间的代码与线程1锁定,别的线程不能对这部分代码进行操作,进入阻塞状态,直到线程1解锁
2.将list转换为queue
queue提供同步的、线程安全的队列类。包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。不过本次只演示FIFO(先入先出)队列Queue。示例程序如下所示:
li = [1, 2, 3, 4, 5]
q = queue.Queue(5)
for i in li:
q.put(i)
def pri(id):
while not q.empty():
a = q.get()
print('%i--%i' % (id, a))
time.sleep(1)
print('%i--%i--%s' % (id, a, 'remove over'))
t1 = threading.Thread(target = pri, args = (1, ))
t1.start()
t2 = threading.Thread(target = pri, args = (2,))
t2.start()
输出结果为:
本次不仅没有异常,而且代码相比使用线程锁的更加整洁。这就是使用queue的好处之一。