Python 队列上的多路复用。队列?
我怎样才能同时对多个进行“选择” Golang通过其渠道拥有以下优势:Python 队列上的多路复用。队列?,python,python-3.x,queue,go,multiplexing,Python,Python 3.x,Queue,Go,Multiplexing,我怎样才能同时对多个进行“选择” Golang通过其渠道拥有以下优势: select { case i1 = <-c1: print("received ", i1, " from c1\n") case c2 <- i2: print("sent ", i2, " to c2\n") case i3, ok := (<-c3): // same as: i3, ok := <-c3 if ok { print("received
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
default:
print("no communication\n")
}
选择{
案例i1=如果使用queue.PriorityQueue
可以使用通道对象作为优先级获得类似的行为:
import threading, logging
import random, string, time
from queue import PriorityQueue, Empty
from contextlib import contextmanager
logging.basicConfig(level=logging.NOTSET,
format="%(threadName)s - %(message)s")
class ChannelManager(object):
next_priority = 0
def __init__(self):
self.queue = PriorityQueue()
self.channels = []
def put(self, channel, item, *args, **kwargs):
self.queue.put((channel, item), *args, **kwargs)
def get(self, *args, **kwargs):
return self.queue.get(*args, **kwargs)
@contextmanager
def select(self, ordering=None, default=False):
if default:
try:
channel, item = self.get(block=False)
except Empty:
channel = 'default'
item = None
else:
channel, item = self.get()
yield channel, item
def new_channel(self, name):
channel = Channel(name, self.next_priority, self)
self.channels.append(channel)
self.next_priority += 1
return channel
class Channel(object):
def __init__(self, name, priority, manager):
self.name = name
self.priority = priority
self.manager = manager
def __str__(self):
return self.name
def __lt__(self, other):
return self.priority < other.priority
def put(self, item):
self.manager.put(self, item)
if __name__ == '__main__':
num_channels = 3
num_producers = 4
num_items_per_producer = 2
num_consumers = 3
num_items_per_consumer = 3
manager = ChannelManager()
channels = [manager.new_channel('Channel#{0}'.format(i))
for i in range(num_channels)]
def producer_target():
for i in range(num_items_per_producer):
time.sleep(random.random())
channel = random.choice(channels)
message = random.choice(string.ascii_letters)
logging.info('Putting {0} in {1}'.format(message, channel))
channel.put(message)
producers = [threading.Thread(target=producer_target,
name='Producer#{0}'.format(i))
for i in range(num_producers)]
for producer in producers:
producer.start()
for producer in producers:
producer.join()
logging.info('Producers finished')
def consumer_target():
for i in range(num_items_per_consumer):
time.sleep(random.random())
with manager.select(default=True) as (channel, item):
if channel:
logging.info('Received {0} from {1}'.format(item, channel))
else:
logging.info('No data received')
consumers = [threading.Thread(target=consumer_target,
name='Consumer#{0}'.format(i))
for i in range(num_consumers)]
for consumer in consumers:
consumer.start()
for consumer in consumers:
consumer.join()
logging.info('Consumers finished')
在本例中,ChannelManager
只是queue.PriorityQueue
的包装器,它将select
方法实现为contextmanager
,使其看起来类似于Go中的select
语句
需要注意的几点:
- 订购
- 在Go示例中,在
select
语句中写入通道的顺序决定了如果有多个通道的数据可用,将执行哪个通道的代码
- 在python示例中,顺序是由分配给每个通道的优先级决定的。但是,优先级可以直接分配给每个通道(如示例所示),因此,可以使用更复杂的
select
方法更改排序,该方法负责根据方法的参数分配新的优先级。此外,上下文管理器完成后,可以重新建立旧的排序
- 阻塞
- 在Go示例中,如果存在
default
情况,则select
语句将被阻塞
- 在python示例中,必须向
select
方法传递一个布尔参数,以明确何时需要阻塞/非阻塞。在非阻塞情况下,上下文管理器返回的通道只是字符串'default'
,因此在中的代码中很容易检测到这一点h
语句
- 线程:
队列
模块中的对象已准备好用于多生产者、多消费者场景,如示例中所示
生产者-消费者队列有许多不同的实现,如available。它们通常在许多属性上有所不同,如Dmitry Vyukov所列。如您所见,可能有超过10k种不同的组合。用于此类队列的算法也因需求而异。这不是pos扩展现有的队列算法以保证附加属性是可行的,因为这通常需要不同的内部数据结构和不同的算法
Go的频道提供了相对较高数量的保证属性,因此这些频道可能适用于许多节目。其中最难的要求之一是支持同时读取/阻塞多个频道(select语句)如果一个select语句中有多个分支能够继续,那么就公平地选择一个通道,这样就不会留下任何消息。Python不提供这种功能,因此不可能用它来存档相同的行为
因此,如果您想继续使用,您需要找到解决该问题的解决方法。但是,这些解决方法有自己的缺点列表,并且更难维护。寻找另一个提供您所需功能的生产者-消费者队列可能是一个更好的主意!无论如何,这里有两种可能的解决方法:
轮询
while True:
try:
i1 = c1.get_nowait()
print "received %s from c1" % i1
except queue.Empty:
pass
try:
i2 = c2.get_nowait()
print "received %s from c2" % i2
except queue.Empty:
pass
time.sleep(0.1)
这可能会在轮询通道时占用大量CPU周期,并且在有大量消息时可能会很慢。使用time.sleep()和指数后退时间(而不是此处显示的恒定0.1秒)可能会大大改进此版本
单个通知队列
queue_id = notify.get()
if queue_id == 1:
i1 = c1.get()
print "received %s from c1" % i1
elif queue_id == 2:
i2 = c2.get()
print "received %s from c2" % i2
使用此设置,在发送到c1或c2之后,您必须向通知队列发送内容。这可能适用于您,只要您只有一个这样的通知队列就足够了(即,您没有多个“选择”,每个“选择”在不同的通道子集上阻塞)
可选地,您也可以考虑使用Go。GO的GOODUTIN和并发支持比Python有限的线程能力更强大。
from queue import Queue
# these imports needed for example code
from threading import Thread
from time import sleep
from random import randint
class MultiQueue(Queue):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queues = []
def addQueue(self, queue):
queue.put = self._put_notify(queue, queue.put)
queue.put_nowait = self._put_notify(queue, queue.put_nowait)
self.queues.append(queue)
def _put_notify(self, queue, old_put):
def wrapper(*args, **kwargs):
result = old_put(*args, **kwargs)
self.put(queue)
return result
return wrapper
if __name__ == '__main__':
# an example of MultiQueue usage
q1 = Queue()
q1.name = 'q1'
q2 = Queue()
q2.name = 'q2'
q3 = Queue()
q3.name = 'q3'
mq = MultiQueue()
mq.addQueue(q1)
mq.addQueue(q2)
mq.addQueue(q3)
queues = [q1, q2, q3]
for i in range(9):
def message(i=i):
print("thread-%d starting..." % i)
sleep(randint(1, 9))
q = queues[i%3]
q.put('thread-%d ending...' % i)
Thread(target=message).start()
print('awaiting results...')
for _ in range(9):
result = mq.get()
print(result.name)
print(result.get())
与其尝试使用多个队列的.get()
方法,这里的想法是让队列在数据准备就绪时通知多队列
,这有点像是一种选择
。这是通过多队列
包装各种队列的put()
和put\u nowait()实现的
方法,以便在向这些队列添加内容时,该队列将put()
放入MultiQueue
中,相应的MultiQueue.get()
将检索准备好数据的队列
此多队列
基于先进先出队列,但您也可以根据需要使用后进先出队列或优先级队列作为基础。该项目在Python中复制Go通道,包括多路复用。它实现了与Go相同的算法,因此它满足您所需的所有属性:
- 多个制作者和消费者可以通过Chan进行交流。当制作者和消费者都准备好时,这对制作者和消费者将进行阻止
- 生产者和消费者按照他们到达的顺序得到服务(FIFO)
- 空(满)队列将阻止使用者(生产者)
下面是您的示例的样子:
c1 = Chan(); c2 = Chan(); c3 = Chan()
try:
chan, value = chanselect([c1, c3], [(c2, i2)])
if chan == c1:
print("Received %r from c1" % value)
elif chan == c2:
print("Sent %r to c2" % i2)
else: # c3
print("Received %r from c3" % value)
except ChanClosed as ex:
if ex.which == c3:
print("c3 is closed")
else:
raise
(完全披露:我写了这个图书馆)我认为这并不能回答问题——您已将所有三个响应加载到队列中,但在他的程序中,数据将异步到达。如果C2在C1之前到达,则应首先返回C2,并且应在可用时尽快返回。@EthanFurman我的理解是,q.get
阻塞直到元素ent可用,然后返回它。因此,如果C2在C1之前,那么C2将被返回,然后是C1。示例目标是显示go中的select
语句中的自顶向下的求值是实际的
c1 = Chan(); c2 = Chan(); c3 = Chan()
try:
chan, value = chanselect([c1, c3], [(c2, i2)])
if chan == c1:
print("Received %r from c1" % value)
elif chan == c2:
print("Sent %r to c2" % i2)
else: # c3
print("Received %r from c3" % value)
except ChanClosed as ex:
if ex.which == c3:
print("c3 is closed")
else:
raise