Queue.task_done is not there for the workers’ benefit. It is there to support Queue.join.
If I give you a box of work assignments, do I care about when you’ve taken everything out of the box?
No. I care about when the work is done. Looking at an empty box doesn’t tell me that. You and 5 other guys might still be working on stuff you took out of the box.
Queue.task_done lets workers say when a task is done. Someone waiting for all the work to be done with Queue.join will wait until enough task_done calls have been made, not when the queue is empty.
eigenfield points out in the comments that it seems really weird for a queue to have task_done/join methods. That’s true, but it’s really a naming problem. The queue module has bad name choices that make it sound like a general-purpose queue library, when it’s really a thread communication library.
It’d be weird for a general-purpose queue to have task_done/join methods, but it’s entirely reasonable for an inter-thread message channel to have a way to indicate that messages have been processed. If the class was called thread_communication.MessageChannel instead of queue.Queue and task_done was called message_processed, the intent would be a lot clearer.
(If you need a general-purpose queue rather than an inter-thread message channel, use collections.deque.)