Change worker-queue interaction, fix race condition

This reworks the `Worker` concept to fix a race condition that
could happen between multiple workers and the queue.

I noticed issues with the worker and it's work loop while using
`DatWorkerPool` with `Qs` (a background job processing library).
The problem was worker's were getting `nil` work items from the
queue, because of a race condition. Whenever work was added to
the queue, it would signal to wake up the first worker, so it
could run the work. The race condition occurred when a worker was
already active and work was added. It was possible for 2 workers
to be active, with only one piece of work to process. Because of
the mutexes, the work wasn't picked up twice, but one worker would
get the work and another would get `nil`. This caused extra cycles
of useless runs and could have called `nil` issues in applications
using `DatWorkerPool`.

This merges the "waiting" and "popping" behavior. This ensures
that the above scenario cannot occur. If the queue signals a
thread to wake up, it will wake up, grab the lock and pop the work
that was added. The already active thread would have to wait
because the thread that work up has the lock. I also modified the
queue's pop to re-check if the queue is empty when it is woken up.
This way, if somehow 2 threads are awake with only one piece of
work, one of them will aquire the lock and the other one will have to
wait. Then when the other one can aquire the lock, it will immediately
re-check if the queue is empty before trying to pop work from the
queue.

This should cover the race conditions and also removes some
awkward methods (`wait_for_work_item`). The `Worker` calling a "wait"
method on the `Queue` was a bad pattern. It's a much cleaner
implementation for this to be part of "popping".