(十四)python之并发编程(队列)

一、队列

  • Python的Queue模块中提供了同步的、线程安全的队列类,包括1、FIFO(先入先出)队列Queue  ,2、LIFO(后入先出),队列LifoQueue,3、优先级队列PriorityQueue,这些队列都实现了锁原理,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
  • 初始化Queue()对象时(例如: q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上线。

问题:我们在学习线程的时候,遇到了一个问题,就是多线程共享全局变量的时候,对同一个全局变量操作的时候,会出现资源竞争的问题,导致我们的数据不准确,因为线程切换的话是由我们python的GL锁去控制的,如果你有这个锁你才能去执行,如果说你没有获取到这个锁的话,你就没有办法执行。

当时我们怎么解决的?是添加锁去解决这个多线程共享全局变量导致资源竞争这个问题。

我们现在还可以通过队列去解决这个问题。

 

 

 

(1)什么是队列?

from queue import Queue, LifoQueue, PriorityQueue

我们先讲Queue这个队列,称之为先入先出队列。我们把队列想象成一个管子,先放进去的数据放在最右边,依次从右往左排列,出来的时候看图,大家也能看的明白,肯定是1先出来。,所以称之为谁先进来谁就先出去,先入先出。

(2)如何往队列里添加数据?

  •  实例化一个队列对象     q = Queue(5) ,参数maxsize默认为不限制
  •  通过 队列对象.put方法往里添加数据,  q.put('python001')
from queue import Queue, LifoQueue, PriorityQueue

# 实例化一个队列对象
q = Queue(5)  # 参数maxsize默认为不限制

# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006')

 这个时候是没有问题的,但是如果我们继续往队列 q 里面添加数据,去执行的时候,程序就处于一种堵塞的状态,一直显示在运行中,为什么呢? 因为你设置的这个队列 最大存取5个数据 q = Queue(5) ,所以就会出现这个情况。

(3)队列满了,设置一个等待时间,不想让他一直处于堵塞的状态。

我们通过设置  q.put('python006', timeout=3), 添加了一个参数 timeout=3,设置等待时间3秒,等待三秒后队列还是满的话就报错,queue.Full,队列已满。

from queue import Queue, LifoQueue, PriorityQueue

# 实例化一个队列对象
q = Queue(5)  # 参数maxsize默认0

# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)


# 报错打印结果
Traceback (most recent call last):
  File "E:\pycharm\testing_and_development\py09_11day\demo1_python内置的队列模块.py", line 17, in <module>
    q.put('python006', timeout=3)
  File "E:\python\lib\queue.py", line 148, in put
    raise Full
queue.Full

(4)获取队列中数据的长度 q.qsize()

我们把maxsize限制去掉,然后通过q.qsize()打印显示有6条数据。

from queue import Queue, LifoQueue, PriorityQueue

# 实例化一个队列对象
q = Queue()  # 参数maxsize默认0

# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)

# 获取队列数据的长度
print(q.qsize())


# 打印结果
6

 (5)获取队列中的数据 q.get()

我们看打印的是多少? 打印的是python001,这就验证了刚才咱们说的,先进去的数据先出来

 

我们把他依次获取出来,看看打印结果,嗯6条数据全部出来了。

from queue import Queue, LifoQueue, PriorityQueue

# 实例化一个队列对象
q = Queue()  # 参数maxsize默认0

# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)

# 获取队列数据的长度
# print(q.qsize())

# get: 获取数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())



# 打印结果
python001
python002
python003
python004
python005
python006

我们把六条数据全部打印出来了,那如果说 我们继续打印会怎么样呢?

看代码我们看出来,打印6条数据之后,我们再去获取队列中的数据的长度,显示为0。然后我们再去获取 q.get()同样也会阻塞,大家看程序就阻塞在那边。

(6)往队列里面添加数据,他不会阻塞,不会等,满了就直接报错,q.put_nowait('python007')

这个方法往队列里面添加数据,他不会阻塞,不会等,前面提到的方法(q.put('python006', timeout=3))的话 他会等。

(7)获取数据不等待,如果没有数据了直接报错 q.get_notwait()

其实就是对put ,get方法做了一个封装,里面的参数block默认是True

(8)判断队列是否为空  q.empty()

from queue import Queue, LifoQueue, PriorityQueue

# 实例化一个队列对象
q = Queue()  # 参数maxsize默认0

# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)

# 获取队列数据的长度
# print(q.qsize())

# get: 获取数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.qsize())

# 判断队列是否为空
print(q.empty())



# 打印结果
python001
python002
python003
python004
python005
python006
0
True

(9)判断队列是否已满   q.full()

代码就不多于上传了,相信大家知道怎么玩了。        

(10)join(等待队列中的任务执行完毕)、task_done(向队列发送一个任务执行完毕的信号。)

join:会去检查这个队列里面的任务是否执行完毕。

我们先看下下面的代码,我们看执行结果,发现执行到print("-----join  1------")之后,程序就阻塞了,一直阻塞在这里,这是为什么呢?

from queue import Queue
"""
task_done: 向队列发送一条任务执行完毕的消息
join: 等待队列中所有的任务是否执行完毕
"""


q = Queue(3)
q.put(111)
q.put(222)
q.put(33)

print("-----------join  1----------------")
q.join()
print("-----------join  2----------------")

 

我们上面解释到了,join的作用就是等待队列中的任务执行完毕,那我们看代码,我们在队列里面添加了三个数据111,222,33 ,那我们是不是应该把他拿出来,那这个队列里面的任务就执行完毕了?这只是我们猜想的结果,我们试一下。

我们发现代码依然会在这个地方卡住。这是为什么呢?

按理来说,我把三条数据全部获取出来了,应该不会卡了啊?这是为什么呢?

因为:join不是看这个队列里的任务数据有没有获取出来,也不是去看这个队列是否为空,他是去评判的是你这个任务被获取出去之后,也就是这个任务执行完毕之后,有没有向这个队列发送一个任务执行完毕的信号。

 

那怎么向这个队列发送一个任务执行完毕的信号? 用task_done,我们改下代码试试。

果然没错,代码也不会阻塞,join 2也打印出来了。 

我如果少调用一次task_done,那程序就会卡住,这个我就不上传代码了,大家自行去试一下。

注意的地方:1.必须要等队列里面所有的数据全部获取出去,2.并且任务全部调用了task_done方法,代码才不会阻塞。

你看我只执行了2个,也会卡住,必须两者条件都要满足。

(11)后入先出队列

看打印结果很明显,就不做赘述了。(实际上LifoQueue类继承了Queue类,并自己实现了自己的四个私有方法)

from queue import LifoQueue, PriorityQueue

# 后入先出
q = LifoQueue()
q.put(11)
q.put(22)
print(q.get())


# 打印结果
22

(12)优先级队列(队列中添加的数据为元祖,元祖的第一个值用来设置队列的优先级)

根据你传进去的元祖,元祖里面第一个数值大小决定优先级,越小的先出来。

from queue import LifoQueue, PriorityQueue


# 优先级队列:(队列中添加的数据为元组,元组的一个值,用来设置队列的优先级)
q = PriorityQueue()
q.put((-15, '9999'))
q.put((77, '9999'))
q.put((4, '9999'))
q.put((99, '9999'))
print(q.get())
print(q.get())
print(q.get())
print(q.get())



# 打印结果
(-15, '9999')
(4, '9999')
(77, '9999')
(99, '9999')

(13)通过队列保证线程数据的安全性

from threading import Thread
from queue import Queue

q = Queue()
q.put(0)


def work1():
    for i in range(100000):
        a = q.get()
        a += 1
        q.put(a)


def work2():
    for i in range(100000):
        a = q.get()
        a += 1
        q.put(a)


if __name__ == '__main__':
    t1 = Thread(target=work1)
    t1.start()
    t2 = Thread(target=work2)
    t2.start()
    t1.join()
    t2.join()

    print("主线程---全局a:", q.get())




# 打印结果
200000
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python并发编程涉及创建子进程、进程池、队列以及进程之间的通信,还包括线程、线程锁和线程同步等内容。 然而,Python并发编程方面存在一些限制。其中一个主要的限制是全局解释器锁(Global Interpreter Lock,GIL)。GIL是Python解释器中的一种机制,它确保同一时间只有一个线程能够执行Python字节码。这意味着在多线程的情况下,只有一个线程能够真正地并发执行Python代码。 GIL是为了解决Python多线程之间数据完整性和状态同步问题而引入的。通过限制同一时间只有一个线程执行Python字节码,GIL简化了对共享资源的管理。例如,当多个线程访问同一个对象时,GIL确保了对象的状态不会被破坏。 然而,由于GIL的存在,Python的多线程无法充分利用多核CPU并发执行。这意味着在某些特定场景下,Python相对于C/C++等语言可能会表现出较慢的速度。 要规避GIL带来的限制,可以考虑以下几种方法: 1. 使用多进程代替多线程:由于每个进程都有自己独立的解释器和GIL,所以多进程可以实现真正的并发执行。 2. 使用C扩展模块:编写一些计算密集型任务的关键部分的C扩展模块,以提高性能。 3. 使用并发编程库:使用像`multiprocessing`、`concurrent.futures`和`asyncio`这样的库,它们提供了更高级别的API,可以更有效地管理并发任务。 请注意,具体的规避方法会根据具体情况而异。在进行并发编程时,建议根据实际需求和场景选择合适的方法来克服GIL带来的限制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python并发编程](https://blog.csdn.net/qq_46092061/article/details/117461858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python 并发编程](https://blog.csdn.net/qq_39445165/article/details/124674435)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值