Multiplex on queue.Queue?

后端 未结 4 2034
感情败类
感情败类 2021-02-13 22:19

How can I go about \"selecting\" on multiple queue.Queue\'s simultaneously?

Golang has the desired feature with its channels:

select {
case i1 = <-c1:         


        
4条回答
  •  醉话见心
    2021-02-13 22:40

    If you use queue.PriorityQueue you can get a similar behaviour using the channel objects as priorities:

    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')
    

    Example output:

    Producer#0 - Putting x in Channel#2
    Producer#2 - Putting l in Channel#0
    Producer#2 - Putting A in Channel#2
    Producer#3 - Putting c in Channel#0
    Producer#3 - Putting z in Channel#1
    Producer#1 - Putting I in Channel#1
    Producer#1 - Putting L in Channel#1
    Producer#0 - Putting g in Channel#1
    MainThread - Producers finished
    Consumer#1 - Received c from Channel#0
    Consumer#2 - Received l from Channel#0
    Consumer#0 - Received I from Channel#1
    Consumer#0 - Received L from Channel#1
    Consumer#2 - Received g from Channel#1
    Consumer#1 - Received z from Channel#1
    Consumer#0 - Received A from Channel#2
    Consumer#1 - Received x from Channel#2
    Consumer#2 - Received None from default
    MainThread - Consumers finished
    

    In this example, ChannelManager is just a wrapper around queue.PriorityQueue that implements the select method as a contextmanager to make it look similar to the select statement in Go.

    A few things to note:

    • Ordering

      • In the Go example, the order in which the channels are written inside the select statement determines which channel's code will be executed if there's data available for more than one channel.

      • In the python example the order is determined by the priority assigned to each channel. However, the priority can be dinamically assigned to each channel (as seen in the example), so changing the ordering would be possible with a more complex select method that takes care of assigning new priorities based on an argument to the method. Also, the old ordering could be reestablished once the context manager is finished.

    • Blocking

      • In the Go example, the select statement is blocking if a default case exists.

      • In the python example, a boolean argument has to be passed to the select method to make it clear when blocking/non-blocking is desired. In the non-blocking case, the channel returned by the context mananager is just the string 'default' so it's easy in the code inside to detect this in the code inside the with statement.

    • Threading: Object in the queue module are already ready for multi-producer, multiconsumer-scenarios as already seen in the example.

提交回复
热议问题