Python入门笔记(十二)—— 多线程(中)

最近要开启日更模式了😑

今天还是继续讲多线程。昨天说到多线程会有可能出现数据不安全的问题,也大致说了第一种方法同步异步,今天来说第二种方法“锁”

当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),简单的同步机制就是使用互斥锁。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性。

通过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继续牛逼😏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值