简体   繁体   English

具有许多线程的Python生产者/消费者

[英]Python Producer/consumer with many threads

I wrote this small application to solve the classical Producer/Consumer problem in Python. 我写了这个小应用程序来解决Python中的经典生产者/消费者问题。 I know that I could use a thread safe Queue mechanism to solve this problem, but I was interested in solving this problem on my own for learning. 我知道我可以使用线程安全的队列机制来解决这个问题,但我有兴趣自己解决这个问题来学习。

from threading import Thread, Condition
from collections import deque
import random
import time

tasks = deque()
condition = Condition()

class Consumer(Thread):

    def process_task(self, task):
        self.log("Completed task " + str(task) )

    def get_task(self):
        # this is the only method that access the global object
        global tasks
        condition.acquire()
        if len(tasks) is 0:
            self.log("Nothing in the Queue... Sleeping!")
            condition.wait()
            return None
        task = tasks.popleft()
        condition.release();
        return task

    def sleep(self):
        self.log("Sleeping")
        time.sleep(1)

    def execute(self):
        while True:
            task = self.get_task()
            if task:
                self.process_task(task)

    def run(self):
        self.log("Started")
        self.execute()

    def log(self, msg):
        print("[ Consumer: {0} ] Consumed {1}".format(self.getName(), msg))

class Producer(Thread):

    def create_tasks(self):
        return [random.randint(0,100) for x in range(100)]

    def add_tasks(self, new_tasks):
        global tasks
        condition.acquire()
        tasks.extend(new_tasks)
        self.log("Produced " + str(len(new_tasks)) + "elements")
        self.log("Queue length : " + str(len(tasks)))
        self.notify_all()
        condition.release();

    def notify_all(self):
        condition.notify_all()
        self.log("Aweking consumers")

    def sleep(self):
        self.log("Sleeping")
        time.sleep(1)

    def execute(self):
        while True:
            new_tasks = self.create_tasks()
            self.add_tasks(new_tasks)
            self.sleep()

    def run(self):
        self.log("Started")
        self.execute()

    def log(self, msg):
        print("[ Producer: {0} ] {1}".format(self.getName(), msg))


Producer().start()
Producer().start()

Consumer().start()
Consumer().start()
Consumer().start()

The application appears to work properly. 该应用程序似乎正常工作。 What I mean by this is that I haven't experienced any deadlock or weird exceptions. 我的意思是我没有经历任何僵局或奇怪的例外。 However, the result I've obtained is not what I was expecting as the work is not spread among the different consumers at all. 然而,我获得的结果并不是我所期望的,因为工作根本没有在不同的消费者中传播。 I was expecting that all three consumer thread would have done some work in parallel, but this didn't happen. 我期待所有三个消费者线程都能并行完成一些工作,但这并没有发生。 This is an extract from the logs : 这是日志的摘录:

[ Consumer: Thread-4 ] Consumed Completed task 42
 ... 98 times the above line with a different task ( always thread 4)  ...
[ Consumer: Thread-4 ] Consumed Completed task 22
[ Consumer: Thread-4 ] Consumed Nothing in the Queue... Sleeping!
[ Consumer: Thread-5 ] Consumed Nothing in the Queue... Sleeping!
[ Consumer: Thread-3 ] Consumed Nothing in the Queue... Sleeping!
[ Producer: Thread-1 ] Produced 100elements
[ Producer: Thread-1 ] Queue length : 100
[ Producer: Thread-1 ] Aweking consumers
[ Producer: Thread-1 ] Sleeping
[ Consumer: Thread-3 ] Consumed Completed task 87
 ... 98 times the above line with a different task ( always thread 3)...
[ Consumer: Thread-3 ] Consumed Completed task 20
[ Consumer: Thread-3 ] Consumed Nothing in the Queue... Sleeping!
[ Consumer: Thread-4 ] Consumed Nothing in the Queue... Sleeping!
[ Consumer: Thread-5 ] Consumed Nothing in the Queue... Sleeping!

As you can see from the above logs, all work is carried out by one single thread whereas the other 2 thread don't do anything. 从上面的日志中可以看出,所有工作都由一个单独的线程执行,而另外两个线程不执行任何操作。 It looks like they never had the chance to run. 看起来他们从来没有机会跑。 I tried to put asleep a consumer thread after it has executed a task, but the behaviour didn't change. 我尝试在执行任务后将消费者线程放入睡眠状态,但行为没有改变。

Is there a bug in my code that I don't see? 我的代码中是否有一个我看不到的错误? Could be this behaviour related to the famous "GIL"? 这种行为可能与着名的“GIL”有关吗?

Thank you for your help. 谢谢您的帮助。

What you're seeing isn't really a coding problem, its just that the same thread is usually winning the race to re-acquire the condition mutex. 你所看到的并不是一个真正的编码问题,它只是同一个线程通常赢得重新获得条件互斥的竞争。 There's enough time for the thread to run process_task and still win the race, because process_task is hardly doing anything. 线程有足够的时间来运行process_task并仍然赢得比赛,因为process_task几乎没有做任何事情。 I don't think this is actually related to the GIL; 我不认为这实际上与GIL有关; I ran the code in Jython, which has no GIL, and the same behavior occurred. 我在Jython中运行了代码,它没有GIL,并且发生了相同的行为。

There's definitely no bug, though. 但肯定没有错误。 When I ran your code in CPython, I occasionally had more than one thread end up consuming: 当我在CPython中运行你的代码时,我偶尔会遇到多个线程:

[ Producer: Thread-1 ] Started
[ Producer: Thread-1 ] Produced 100elements
[ Producer: Thread-1 ] Queue length : 100
[ Producer: Thread-1 ] Aweking consumers
[ Producer: Thread-1 ] Sleeping
[ Producer: Thread-2 ] Started
[ Consumer: Thread-3 ] Consumed Started
[ Producer: Thread-2 ] Produced 100elements
[ Producer: Thread-2 ] Queue length : 200
[ Consumer: Thread-5 ] Consumed Started
[ Producer: Thread-2 ] Aweking consumers
[ Consumer: Thread-4 ] Consumed Started
[ Producer: Thread-2 ] Sleeping
[ Consumer: Thread-5 ] Consumed Completed task 53
[ Consumer: Thread-4 ] Consumed Completed task 73
[ Consumer: Thread-5 ] Consumed Completed task 83
[ Consumer: Thread-4 ] Consumed Completed task 71
[ Consumer: Thread-5 ] Consumed Completed task 67
[ Consumer: Thread-4 ] Consumed Completed task 7
[ Consumer: Thread-5 ] Consumed Completed task 34
[ Consumer: Thread-4 ] Consumed Completed task 68
[ Consumer: Thread-5 ] Consumed Completed task 15
[ Consumer: Thread-4 ] Consumed Completed task 29
[ Consumer: Thread-5 ] Consumed Completed task 20
... (4 and 5 continue to switch off)

Also, I get very normal results if I call self.sleep() inside of process_task , which simulates some real work actually happening, and therefore makes for a much more realistic test: 另外,如果我在process_task调用self.sleep()来模拟一些实际发生的实际工作,那么我会得到非常正常的结果,因此可以进行更加真实的测试:

[ Producer: Thread-1 ] Started
[ Producer: Thread-2 ] Started
[ Producer: Thread-1 ] Produced 100elements
[ Producer: Thread-1 ] Queue length : 100
[ Producer: Thread-1 ] Aweking consumers
[ Consumer: Thread-5 ] Consumed Started
[ Consumer: Thread-3 ] Consumed Started
[ Producer: Thread-1 ] Sleeping
[ Producer: Thread-2 ] Produced 100elements
[ Consumer: Thread-4 ] Consumed Started
[ Producer: Thread-2 ] Queue length : 200
[ Producer: Thread-2 ] Aweking consumers
[ Producer: Thread-2 ] Sleeping
[ Consumer: Thread-3 ] Consumed Sleeping
[ Consumer: Thread-5 ] Consumed Sleeping
[ Consumer: Thread-4 ] Consumed Sleeping
[ Producer: Thread-1 ] Produced 100elements
[ Consumer: Thread-3 ] Consumed Completed task 85
[ Consumer: Thread-5 ] Consumed Completed task 31
[ Producer: Thread-1 ] Queue length : 297
[ Consumer: Thread-4 ] Consumed Completed task 62
[ Producer: Thread-1 ] Aweking consumers
[ Producer: Thread-1 ] Sleeping
[ Producer: Thread-2 ] Produced 100elements
[ Producer: Thread-2 ] Queue length : 397
[ Producer: Thread-2 ] Aweking consumers
[ Producer: Thread-2 ] Sleeping
[ Consumer: Thread-3 ] Consumed Sleeping
[ Consumer: Thread-5 ] Consumed Sleeping
[ Consumer: Thread-4 ] Consumed Sleeping
[ Producer: Thread-1 ] Produced 100elements
[ Producer: Thread-1 ] Queue length : 494
[ Consumer: Thread-3 ] Consumed Completed task 99
[ Consumer: Thread-4 ] Consumed Completed task 58
[ Producer: Thread-1 ] Aweking consumers
[ Consumer: Thread-5 ] Consumed Completed task 18

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM