require 'benchmark'
require 'thread'
require_relative 'atomic_linked_queue'
require_relative 'two_lock_linked_queue'

thread_count = 50
iterations = 10_000

queue_klasses = [Queue, TwoLockLinkedQueue, AtomicLinkedQueue]
Thread.abort_on_exception = true

# this one tells all the threads when to start
$go = false

def setup(queue, writer_thread_count, reader_thread_count, iterations)
  tg = ThreadGroup.new
  
  # spawn writer threads
  writer_thread_count.times do
    t = Thread.new do
      # wait until the bm starts to do the work. This should
      # minimize variance.
      nil until $go
      iterations.times do
        queue.push('item')
      end
    end

    tg.add(t)
  end
  
  # spawn reader threads
  if queue.class == Queue
    # the Queue class gets special behaviour because its #pop
    # method is blocking by default.
    reader_thread_count.times do
      t = Thread.new do
        nil until $go
        iterations.times do
          begin
            queue.pop(:nonblocking)
          rescue
          end
        end
      end

      tg.add(t)
    end
  else
    reader_thread_count.times do
      t = Thread.new do
        nil until $go
        iterations.times do
          queue.pop
        end
      end

      tg.add(t)
    end
  end
  
  tg
end

def exercise(tg)
  $go = true
  tg.list.each(&:join)
  $go = false
end

3.times do
queue_klasses.each do |klass|
  Benchmark.bm(50) do |bm|
    queue = klass.new
    tg = setup(queue, thread_count, (thread_count * 0.6).to_i, iterations)
    bm.report("#{klass} - more writers than readers") { exercise(tg) }

    queue = klass.new
    tg = setup(queue, (thread_count * 0.6).to_i, thread_count, iterations)
    bm.report("#{klass} - more readers than writers") { exercise(tg) }

    queue = klass.new
    tg = setup(queue, thread_count, thread_count, iterations)
    bm.report("#{klass} - equal writers and readers") { exercise(tg) }
  end
end
end