最近要开启日更模式了😑
今天还是继续讲多线程。昨天说到多线程会有可能出现数据不安全的问题,也大致说了第一种方法同步异步,今天来说第二种方法“锁”
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),简单的同步机制就是使用互斥锁。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性。
通过threading.Lock()获取Lock的对象。使用.acquire()方法加锁和.release()方法解锁。
在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁现象。
如果锁之间相互嵌套,就有可能出现死锁。
解决思索的基本方法
所有线程以确定的顺序获得锁
以下情况就会发生死锁
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
def A():
lock1.acquire() # 尝试锁住1
time.sleep(1)
lock2.acquire() # 尝试锁住2
lock2.release()
lock1.release()
def B():
lock2.acquire() # 尝试锁住2
lock1.acquire() # 尝试锁住1
lock1.release()
lock2.release()
if __name__ == '__main__':
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
线程A加锁1 -> 线程A等待 -> 线程B加锁2 -> 线程A加锁2失败已被加锁 -> 线程B加锁1失败已被加锁 -> 形成死锁
将以上代码改成这样就永远不会发生死锁了
def A():
lock1.acquire() # 尝试锁住1
time.sleep(1)
lock2.acquire() # 尝试锁住2
lock2.release()
lock1.release()
def B():
lock1.acquire() # 尝试锁住1
lock2.acquire() # 尝试锁住2
lock2.release()
lock1.release()
线程A加锁1 -> 线程A等待 -> 线程B加锁1发现1已被加锁 -> 线程B堵塞 -> 线程A加锁2 -> 线程A解锁2、1 -> 线程B运行
线程队列Queue
队列是一种先进先出的存储数据结构。
-
import queue 导入模块(Python3)
-
queue.Queue(10) 获取对象,队列长度可为无限或者有限。通过参数来设置队列长度,默认长度无限。
-
.put() 放入一个元素
-
.get() 获取一个元素,如果取不到数据则一直等待
-
.qsize() 返回队列大小
-
.empty() 如果队列为空,返回True,反之False
-
.full() 如果队列满了,返回True,反之False
-
.put_nowait() 如果放不下不等待直接报错
-
.get_nowait() 如果取不到不等待直接报错
-
.task_done() 在完成一项工作后,想任务已经完成的队列发送一个信号
-
.join() 等待队列为空,再执行别的操作。如果线程没从队列里取出一次,但没有触发task_done(),则join无法判断队列到底有没有结束,join()会一直挂起
import queue
if __name__ == '__main__':
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get())
这个程序会一直卡住等待有空位放 4
假如我就是想放四个元素,可以在第四个位置使用put_nowait()
import queue
if __name__ == '__main__':
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
q.put_nowait(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get())
结果是报错
D:\Code\Python\Test\venv\Scripts\python.exe D:/Code/Python/Test/t.py
Traceback (most recent call last):
File "D:/Code/Python/Test/t.py", line 489, in <module>
q.put_nowait(4)
File "D:\Python\lib\queue.py", line 190, in put_nowait
return self.put(item, block=False)
File "D:\Python\lib\queue.py", line 136, in put
raise Full
queue.Full
可以通过try except修改报错内容
import queue
if __name__ == '__main__':
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
try:
q.put_nowait(4)
except:
print("溢出了")
print(q.get())
print(q.get())
print(q.get())
print(q.get())
结果为
溢出了
1
2
3
明明放了四个数据进去,只拿到了三个数据进去,所以put_nowait()方法会丢失数据
但是假如我非要取第四个数据怎么办,在接受时使用get_nowait()
import queue
if __name__ == '__main__':
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
try:
q.put_nowait(4)
except:
print("溢出了")
print(q.get())
print(q.get())
print(q.get())
print(q.get_nowait())
但是依然会报错,会告诉队列已空。可以用try except修改报错内容。
❗这两个方法使用最广的场景就是防止线程堵塞❗
祝大家2020继续牛逼😏